1/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.support.v4.widget;
18
19import android.content.Context;
20import android.content.res.TypedArray;
21import android.graphics.Bitmap;
22import android.graphics.Canvas;
23import android.graphics.Paint;
24import android.graphics.PixelFormat;
25import android.graphics.PorterDuff;
26import android.graphics.PorterDuffColorFilter;
27import android.graphics.Rect;
28import android.graphics.drawable.Drawable;
29import android.os.Build;
30import android.os.Parcel;
31import android.os.Parcelable;
32import android.support.annotation.ColorInt;
33import android.support.annotation.DrawableRes;
34import android.support.v4.content.ContextCompat;
35import android.support.v4.os.ParcelableCompat;
36import android.support.v4.os.ParcelableCompatCreatorCallbacks;
37import android.support.v4.view.AbsSavedState;
38import android.support.v4.view.AccessibilityDelegateCompat;
39import android.support.v4.view.MotionEventCompat;
40import android.support.v4.view.ViewCompat;
41import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
42import android.util.AttributeSet;
43import android.util.Log;
44import android.view.MotionEvent;
45import android.view.View;
46import android.view.ViewConfiguration;
47import android.view.ViewGroup;
48import android.view.ViewParent;
49import android.view.accessibility.AccessibilityEvent;
50
51import java.lang.reflect.Field;
52import java.lang.reflect.Method;
53import java.util.ArrayList;
54
55/**
56 * SlidingPaneLayout provides a horizontal, multi-pane layout for use at the top level
57 * of a UI. A left (or first) pane is treated as a content list or browser, subordinate to a
58 * primary detail view for displaying content.
59 *
60 * <p>Child views may overlap if their combined width exceeds the available width
61 * in the SlidingPaneLayout. When this occurs the user may slide the topmost view out of the way
62 * by dragging it, or by navigating in the direction of the overlapped view using a keyboard.
63 * If the content of the dragged child view is itself horizontally scrollable, the user may
64 * grab it by the very edge.</p>
65 *
66 * <p>Thanks to this sliding behavior, SlidingPaneLayout may be suitable for creating layouts
67 * that can smoothly adapt across many different screen sizes, expanding out fully on larger
68 * screens and collapsing on smaller screens.</p>
69 *
70 * <p>SlidingPaneLayout is distinct from a navigation drawer as described in the design
71 * guide and should not be used in the same scenarios. SlidingPaneLayout should be thought
72 * of only as a way to allow a two-pane layout normally used on larger screens to adapt to smaller
73 * screens in a natural way. The interaction patterns expressed by SlidingPaneLayout imply
74 * a physicality and direct information hierarchy between panes that does not necessarily exist
75 * in a scenario where a navigation drawer should be used instead.</p>
76 *
77 * <p>Appropriate uses of SlidingPaneLayout include pairings of panes such as a contact list and
78 * subordinate interactions with those contacts, or an email thread list with the content pane
79 * displaying the contents of the selected thread. Inappropriate uses of SlidingPaneLayout include
80 * switching between disparate functions of your app, such as jumping from a social stream view
81 * to a view of your personal profile - cases such as this should use the navigation drawer
82 * pattern instead. ({@link DrawerLayout DrawerLayout} implements this pattern.)</p>
83 *
84 * <p>Like {@link android.widget.LinearLayout LinearLayout}, SlidingPaneLayout supports
85 * the use of the layout parameter <code>layout_weight</code> on child views to determine
86 * how to divide leftover space after measurement is complete. It is only relevant for width.
87 * When views do not overlap weight behaves as it does in a LinearLayout.</p>
88 *
89 * <p>When views do overlap, weight on a slideable pane indicates that the pane should be
90 * sized to fill all available space in the closed state. Weight on a pane that becomes covered
91 * indicates that the pane should be sized to fill all available space except a small minimum strip
92 * that the user may use to grab the slideable view and pull it back over into a closed state.</p>
93 */
94public class SlidingPaneLayout extends ViewGroup {
95    private static final String TAG = "SlidingPaneLayout";
96
97    /**
98     * Default size of the overhang for a pane in the open state.
99     * At least this much of a sliding pane will remain visible.
100     * This indicates that there is more content available and provides
101     * a "physical" edge to grab to pull it closed.
102     */
103    private static final int DEFAULT_OVERHANG_SIZE = 32; // dp;
104
105    /**
106     * If no fade color is given by default it will fade to 80% gray.
107     */
108    private static final int DEFAULT_FADE_COLOR = 0xcccccccc;
109
110    /**
111     * The fade color used for the sliding panel. 0 = no fading.
112     */
113    private int mSliderFadeColor = DEFAULT_FADE_COLOR;
114
115    /**
116     * Minimum velocity that will be detected as a fling
117     */
118    private static final int MIN_FLING_VELOCITY = 400; // dips per second
119
120    /**
121     * The fade color used for the panel covered by the slider. 0 = no fading.
122     */
123    private int mCoveredFadeColor;
124
125    /**
126     * Drawable used to draw the shadow between panes by default.
127     */
128    private Drawable mShadowDrawableLeft;
129
130    /**
131     * Drawable used to draw the shadow between panes to support RTL (right to left language).
132     */
133    private Drawable mShadowDrawableRight;
134
135    /**
136     * The size of the overhang in pixels.
137     * This is the minimum section of the sliding panel that will
138     * be visible in the open state to allow for a closing drag.
139     */
140    private final int mOverhangSize;
141
142    /**
143     * True if a panel can slide with the current measurements
144     */
145    private boolean mCanSlide;
146
147    /**
148     * The child view that can slide, if any.
149     */
150    View mSlideableView;
151
152    /**
153     * How far the panel is offset from its closed position.
154     * range [0, 1] where 0 = closed, 1 = open.
155     */
156    float mSlideOffset;
157
158    /**
159     * How far the non-sliding panel is parallaxed from its usual position when open.
160     * range [0, 1]
161     */
162    private float mParallaxOffset;
163
164    /**
165     * How far in pixels the slideable panel may move.
166     */
167    int mSlideRange;
168
169    /**
170     * A panel view is locked into internal scrolling or another condition that
171     * is preventing a drag.
172     */
173    boolean mIsUnableToDrag;
174
175    /**
176     * Distance in pixels to parallax the fixed pane by when fully closed
177     */
178    private int mParallaxBy;
179
180    private float mInitialMotionX;
181    private float mInitialMotionY;
182
183    private PanelSlideListener mPanelSlideListener;
184
185    final ViewDragHelper mDragHelper;
186
187    /**
188     * Stores whether or not the pane was open the last time it was slideable.
189     * If open/close operations are invoked this state is modified. Used by
190     * instance state save/restore.
191     */
192    boolean mPreservedOpenState;
193    private boolean mFirstLayout = true;
194
195    private final Rect mTmpRect = new Rect();
196
197    final ArrayList<DisableLayerRunnable> mPostedRunnables =
198            new ArrayList<DisableLayerRunnable>();
199
200    static final SlidingPanelLayoutImpl IMPL;
201
202    static {
203        final int deviceVersion = Build.VERSION.SDK_INT;
204        if (deviceVersion >= 17) {
205            IMPL = new SlidingPanelLayoutImplJBMR1();
206        } else if (deviceVersion >= 16) {
207            IMPL = new SlidingPanelLayoutImplJB();
208        } else {
209            IMPL = new SlidingPanelLayoutImplBase();
210        }
211    }
212
213    /**
214     * Listener for monitoring events about sliding panes.
215     */
216    public interface PanelSlideListener {
217        /**
218         * Called when a sliding pane's position changes.
219         * @param panel The child view that was moved
220         * @param slideOffset The new offset of this sliding pane within its range, from 0-1
221         */
222        void onPanelSlide(View panel, float slideOffset);
223        /**
224         * Called when a sliding pane becomes slid completely open. The pane may or may not
225         * be interactive at this point depending on how much of the pane is visible.
226         * @param panel The child view that was slid to an open position, revealing other panes
227         */
228        void onPanelOpened(View panel);
229
230        /**
231         * Called when a sliding pane becomes slid completely closed. The pane is now guaranteed
232         * to be interactive. It may now obscure other views in the layout.
233         * @param panel The child view that was slid to a closed position
234         */
235        void onPanelClosed(View panel);
236    }
237
238    /**
239     * No-op stubs for {@link PanelSlideListener}. If you only want to implement a subset
240     * of the listener methods you can extend this instead of implement the full interface.
241     */
242    public static class SimplePanelSlideListener implements PanelSlideListener {
243        @Override
244        public void onPanelSlide(View panel, float slideOffset) {
245        }
246        @Override
247        public void onPanelOpened(View panel) {
248        }
249        @Override
250        public void onPanelClosed(View panel) {
251        }
252    }
253
254    public SlidingPaneLayout(Context context) {
255        this(context, null);
256    }
257
258    public SlidingPaneLayout(Context context, AttributeSet attrs) {
259        this(context, attrs, 0);
260    }
261
262    public SlidingPaneLayout(Context context, AttributeSet attrs, int defStyle) {
263        super(context, attrs, defStyle);
264
265        final float density = context.getResources().getDisplayMetrics().density;
266        mOverhangSize = (int) (DEFAULT_OVERHANG_SIZE * density + 0.5f);
267
268        final ViewConfiguration viewConfig = ViewConfiguration.get(context);
269
270        setWillNotDraw(false);
271
272        ViewCompat.setAccessibilityDelegate(this, new AccessibilityDelegate());
273        ViewCompat.setImportantForAccessibility(this, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
274
275        mDragHelper = ViewDragHelper.create(this, 0.5f, new DragHelperCallback());
276        mDragHelper.setMinVelocity(MIN_FLING_VELOCITY * density);
277    }
278
279    /**
280     * Set a distance to parallax the lower pane by when the upper pane is in its
281     * fully closed state. The lower pane will scroll between this position and
282     * its fully open state.
283     *
284     * @param parallaxBy Distance to parallax by in pixels
285     */
286    public void setParallaxDistance(int parallaxBy) {
287        mParallaxBy = parallaxBy;
288        requestLayout();
289    }
290
291    /**
292     * @return The distance the lower pane will parallax by when the upper pane is fully closed.
293     *
294     * @see #setParallaxDistance(int)
295     */
296    public int getParallaxDistance() {
297        return mParallaxBy;
298    }
299
300    /**
301     * Set the color used to fade the sliding pane out when it is slid most of the way offscreen.
302     *
303     * @param color An ARGB-packed color value
304     */
305    public void setSliderFadeColor(@ColorInt int color) {
306        mSliderFadeColor = color;
307    }
308
309    /**
310     * @return The ARGB-packed color value used to fade the sliding pane
311     */
312    @ColorInt
313    public int getSliderFadeColor() {
314        return mSliderFadeColor;
315    }
316
317    /**
318     * Set the color used to fade the pane covered by the sliding pane out when the pane
319     * will become fully covered in the closed state.
320     *
321     * @param color An ARGB-packed color value
322     */
323    public void setCoveredFadeColor(@ColorInt int color) {
324        mCoveredFadeColor = color;
325    }
326
327    /**
328     * @return The ARGB-packed color value used to fade the fixed pane
329     */
330    @ColorInt
331    public int getCoveredFadeColor() {
332        return mCoveredFadeColor;
333    }
334
335    public void setPanelSlideListener(PanelSlideListener listener) {
336        mPanelSlideListener = listener;
337    }
338
339    void dispatchOnPanelSlide(View panel) {
340        if (mPanelSlideListener != null) {
341            mPanelSlideListener.onPanelSlide(panel, mSlideOffset);
342        }
343    }
344
345    void dispatchOnPanelOpened(View panel) {
346        if (mPanelSlideListener != null) {
347            mPanelSlideListener.onPanelOpened(panel);
348        }
349        sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
350    }
351
352    void dispatchOnPanelClosed(View panel) {
353        if (mPanelSlideListener != null) {
354            mPanelSlideListener.onPanelClosed(panel);
355        }
356        sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
357    }
358
359    void updateObscuredViewsVisibility(View panel) {
360        final boolean isLayoutRtl = isLayoutRtlSupport();
361        final int startBound = isLayoutRtl ? (getWidth() - getPaddingRight()) : getPaddingLeft();
362        final int endBound = isLayoutRtl ? getPaddingLeft() : (getWidth() - getPaddingRight());
363        final int topBound = getPaddingTop();
364        final int bottomBound = getHeight() - getPaddingBottom();
365        final int left;
366        final int right;
367        final int top;
368        final int bottom;
369        if (panel != null && viewIsOpaque(panel)) {
370            left = panel.getLeft();
371            right = panel.getRight();
372            top = panel.getTop();
373            bottom = panel.getBottom();
374        } else {
375            left = right = top = bottom = 0;
376        }
377
378        for (int i = 0, childCount = getChildCount(); i < childCount; i++) {
379            final View child = getChildAt(i);
380
381            if (child == panel) {
382                // There are still more children above the panel but they won't be affected.
383                break;
384            } else if (child.getVisibility() == GONE) {
385                continue;
386            }
387
388            final int clampedChildLeft = Math.max(
389                    (isLayoutRtl ? endBound : startBound), child.getLeft());
390            final int clampedChildTop = Math.max(topBound, child.getTop());
391            final int clampedChildRight = Math.min(
392                    (isLayoutRtl ? startBound : endBound), child.getRight());
393            final int clampedChildBottom = Math.min(bottomBound, child.getBottom());
394            final int vis;
395            if (clampedChildLeft >= left && clampedChildTop >= top
396                    && clampedChildRight <= right && clampedChildBottom <= bottom) {
397                vis = INVISIBLE;
398            } else {
399                vis = VISIBLE;
400            }
401            child.setVisibility(vis);
402        }
403    }
404
405    void setAllChildrenVisible() {
406        for (int i = 0, childCount = getChildCount(); i < childCount; i++) {
407            final View child = getChildAt(i);
408            if (child.getVisibility() == INVISIBLE) {
409                child.setVisibility(VISIBLE);
410            }
411        }
412    }
413
414    private static boolean viewIsOpaque(View v) {
415        if (v.isOpaque()) {
416            return true;
417        }
418
419        // View#isOpaque didn't take all valid opaque scrollbar modes into account
420        // before API 18 (JB-MR2). On newer devices rely solely on isOpaque above and return false
421        // here. On older devices, check the view's background drawable directly as a fallback.
422        if (Build.VERSION.SDK_INT >= 18) {
423            return false;
424        }
425
426        final Drawable bg = v.getBackground();
427        if (bg != null) {
428            return bg.getOpacity() == PixelFormat.OPAQUE;
429        }
430        return false;
431    }
432
433    @Override
434    protected void onAttachedToWindow() {
435        super.onAttachedToWindow();
436        mFirstLayout = true;
437    }
438
439    @Override
440    protected void onDetachedFromWindow() {
441        super.onDetachedFromWindow();
442        mFirstLayout = true;
443
444        for (int i = 0, count = mPostedRunnables.size(); i < count; i++) {
445            final DisableLayerRunnable dlr = mPostedRunnables.get(i);
446            dlr.run();
447        }
448        mPostedRunnables.clear();
449    }
450
451    @Override
452    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
453        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
454        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
455        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
456        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
457
458        if (widthMode != MeasureSpec.EXACTLY) {
459            if (isInEditMode()) {
460                // Don't crash the layout editor. Consume all of the space if specified
461                // or pick a magic number from thin air otherwise.
462                // TODO Better communication with tools of this bogus state.
463                // It will crash on a real device.
464                if (widthMode == MeasureSpec.AT_MOST) {
465                    widthMode = MeasureSpec.EXACTLY;
466                } else if (widthMode == MeasureSpec.UNSPECIFIED) {
467                    widthMode = MeasureSpec.EXACTLY;
468                    widthSize = 300;
469                }
470            } else {
471                throw new IllegalStateException("Width must have an exact value or MATCH_PARENT");
472            }
473        } else if (heightMode == MeasureSpec.UNSPECIFIED) {
474            if (isInEditMode()) {
475                // Don't crash the layout editor. Pick a magic number from thin air instead.
476                // TODO Better communication with tools of this bogus state.
477                // It will crash on a real device.
478                if (heightMode == MeasureSpec.UNSPECIFIED) {
479                    heightMode = MeasureSpec.AT_MOST;
480                    heightSize = 300;
481                }
482            } else {
483                throw new IllegalStateException("Height must not be UNSPECIFIED");
484            }
485        }
486
487        int layoutHeight = 0;
488        int maxLayoutHeight = -1;
489        switch (heightMode) {
490            case MeasureSpec.EXACTLY:
491                layoutHeight = maxLayoutHeight = heightSize - getPaddingTop() - getPaddingBottom();
492                break;
493            case MeasureSpec.AT_MOST:
494                maxLayoutHeight = heightSize - getPaddingTop() - getPaddingBottom();
495                break;
496        }
497
498        float weightSum = 0;
499        boolean canSlide = false;
500        final int widthAvailable = widthSize - getPaddingLeft() - getPaddingRight();
501        int widthRemaining = widthAvailable;
502        final int childCount = getChildCount();
503
504        if (childCount > 2) {
505            Log.e(TAG, "onMeasure: More than two child views are not supported.");
506        }
507
508        // We'll find the current one below.
509        mSlideableView = null;
510
511        // First pass. Measure based on child LayoutParams width/height.
512        // Weight will incur a second pass.
513        for (int i = 0; i < childCount; i++) {
514            final View child = getChildAt(i);
515            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
516
517            if (child.getVisibility() == GONE) {
518                lp.dimWhenOffset = false;
519                continue;
520            }
521
522            if (lp.weight > 0) {
523                weightSum += lp.weight;
524
525                // If we have no width, weight is the only contributor to the final size.
526                // Measure this view on the weight pass only.
527                if (lp.width == 0) continue;
528            }
529
530            int childWidthSpec;
531            final int horizontalMargin = lp.leftMargin + lp.rightMargin;
532            if (lp.width == LayoutParams.WRAP_CONTENT) {
533                childWidthSpec = MeasureSpec.makeMeasureSpec(widthAvailable - horizontalMargin,
534                        MeasureSpec.AT_MOST);
535            } else if (lp.width == LayoutParams.MATCH_PARENT) {
536                childWidthSpec = MeasureSpec.makeMeasureSpec(widthAvailable - horizontalMargin,
537                        MeasureSpec.EXACTLY);
538            } else {
539                childWidthSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
540            }
541
542            int childHeightSpec;
543            if (lp.height == LayoutParams.WRAP_CONTENT) {
544                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.AT_MOST);
545            } else if (lp.height == LayoutParams.MATCH_PARENT) {
546                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.EXACTLY);
547            } else {
548                childHeightSpec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
549            }
550
551            child.measure(childWidthSpec, childHeightSpec);
552            final int childWidth = child.getMeasuredWidth();
553            final int childHeight = child.getMeasuredHeight();
554
555            if (heightMode == MeasureSpec.AT_MOST && childHeight > layoutHeight) {
556                layoutHeight = Math.min(childHeight, maxLayoutHeight);
557            }
558
559            widthRemaining -= childWidth;
560            canSlide |= lp.slideable = widthRemaining < 0;
561            if (lp.slideable) {
562                mSlideableView = child;
563            }
564        }
565
566        // Resolve weight and make sure non-sliding panels are smaller than the full screen.
567        if (canSlide || weightSum > 0) {
568            final int fixedPanelWidthLimit = widthAvailable - mOverhangSize;
569
570            for (int i = 0; i < childCount; i++) {
571                final View child = getChildAt(i);
572
573                if (child.getVisibility() == GONE) {
574                    continue;
575                }
576
577                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
578
579                if (child.getVisibility() == GONE) {
580                    continue;
581                }
582
583                final boolean skippedFirstPass = lp.width == 0 && lp.weight > 0;
584                final int measuredWidth = skippedFirstPass ? 0 : child.getMeasuredWidth();
585                if (canSlide && child != mSlideableView) {
586                    if (lp.width < 0 && (measuredWidth > fixedPanelWidthLimit || lp.weight > 0)) {
587                        // Fixed panels in a sliding configuration should
588                        // be clamped to the fixed panel limit.
589                        final int childHeightSpec;
590                        if (skippedFirstPass) {
591                            // Do initial height measurement if we skipped measuring this view
592                            // the first time around.
593                            if (lp.height == LayoutParams.WRAP_CONTENT) {
594                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,
595                                        MeasureSpec.AT_MOST);
596                            } else if (lp.height == LayoutParams.MATCH_PARENT) {
597                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,
598                                        MeasureSpec.EXACTLY);
599                            } else {
600                                childHeightSpec = MeasureSpec.makeMeasureSpec(lp.height,
601                                        MeasureSpec.EXACTLY);
602                            }
603                        } else {
604                            childHeightSpec = MeasureSpec.makeMeasureSpec(
605                                    child.getMeasuredHeight(), MeasureSpec.EXACTLY);
606                        }
607                        final int childWidthSpec = MeasureSpec.makeMeasureSpec(
608                                fixedPanelWidthLimit, MeasureSpec.EXACTLY);
609                        child.measure(childWidthSpec, childHeightSpec);
610                    }
611                } else if (lp.weight > 0) {
612                    int childHeightSpec;
613                    if (lp.width == 0) {
614                        // This was skipped the first time; figure out a real height spec.
615                        if (lp.height == LayoutParams.WRAP_CONTENT) {
616                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,
617                                    MeasureSpec.AT_MOST);
618                        } else if (lp.height == LayoutParams.MATCH_PARENT) {
619                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,
620                                    MeasureSpec.EXACTLY);
621                        } else {
622                            childHeightSpec = MeasureSpec.makeMeasureSpec(lp.height,
623                                    MeasureSpec.EXACTLY);
624                        }
625                    } else {
626                        childHeightSpec = MeasureSpec.makeMeasureSpec(
627                                child.getMeasuredHeight(), MeasureSpec.EXACTLY);
628                    }
629
630                    if (canSlide) {
631                        // Consume available space
632                        final int horizontalMargin = lp.leftMargin + lp.rightMargin;
633                        final int newWidth = widthAvailable - horizontalMargin;
634                        final int childWidthSpec = MeasureSpec.makeMeasureSpec(
635                                newWidth, MeasureSpec.EXACTLY);
636                        if (measuredWidth != newWidth) {
637                            child.measure(childWidthSpec, childHeightSpec);
638                        }
639                    } else {
640                        // Distribute the extra width proportionally similar to LinearLayout
641                        final int widthToDistribute = Math.max(0, widthRemaining);
642                        final int addedWidth = (int) (lp.weight * widthToDistribute / weightSum);
643                        final int childWidthSpec = MeasureSpec.makeMeasureSpec(
644                                measuredWidth + addedWidth, MeasureSpec.EXACTLY);
645                        child.measure(childWidthSpec, childHeightSpec);
646                    }
647                }
648            }
649        }
650
651        final int measuredWidth = widthSize;
652        final int measuredHeight = layoutHeight + getPaddingTop() + getPaddingBottom();
653
654        setMeasuredDimension(measuredWidth, measuredHeight);
655        mCanSlide = canSlide;
656
657        if (mDragHelper.getViewDragState() != ViewDragHelper.STATE_IDLE && !canSlide) {
658            // Cancel scrolling in progress, it's no longer relevant.
659            mDragHelper.abort();
660        }
661    }
662
663    @Override
664    protected void onLayout(boolean changed, int l, int t, int r, int b) {
665        final boolean isLayoutRtl = isLayoutRtlSupport();
666        if (isLayoutRtl) {
667            mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_RIGHT);
668        } else {
669            mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
670        }
671        final int width = r - l;
672        final int paddingStart = isLayoutRtl ? getPaddingRight() : getPaddingLeft();
673        final int paddingEnd = isLayoutRtl ? getPaddingLeft() : getPaddingRight();
674        final int paddingTop = getPaddingTop();
675
676        final int childCount = getChildCount();
677        int xStart = paddingStart;
678        int nextXStart = xStart;
679
680        if (mFirstLayout) {
681            mSlideOffset = mCanSlide && mPreservedOpenState ? 1.f : 0.f;
682        }
683
684        for (int i = 0; i < childCount; i++) {
685            final View child = getChildAt(i);
686
687            if (child.getVisibility() == GONE) {
688                continue;
689            }
690
691            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
692
693            final int childWidth = child.getMeasuredWidth();
694            int offset = 0;
695
696            if (lp.slideable) {
697                final int margin = lp.leftMargin + lp.rightMargin;
698                final int range = Math.min(nextXStart,
699                        width - paddingEnd - mOverhangSize) - xStart - margin;
700                mSlideRange = range;
701                final int lpMargin = isLayoutRtl ? lp.rightMargin : lp.leftMargin;
702                lp.dimWhenOffset = xStart + lpMargin + range + childWidth / 2 > width - paddingEnd;
703                final int pos = (int) (range * mSlideOffset);
704                xStart += pos + lpMargin;
705                mSlideOffset = (float) pos / mSlideRange;
706            } else if (mCanSlide && mParallaxBy != 0) {
707                offset = (int) ((1 - mSlideOffset) * mParallaxBy);
708                xStart = nextXStart;
709            } else {
710                xStart = nextXStart;
711            }
712
713            final int childRight;
714            final int childLeft;
715            if (isLayoutRtl) {
716                childRight = width - xStart + offset;
717                childLeft = childRight - childWidth;
718            } else {
719                childLeft = xStart - offset;
720                childRight = childLeft + childWidth;
721            }
722
723            final int childTop = paddingTop;
724            final int childBottom = childTop + child.getMeasuredHeight();
725            child.layout(childLeft, paddingTop, childRight, childBottom);
726
727            nextXStart += child.getWidth();
728        }
729
730        if (mFirstLayout) {
731            if (mCanSlide) {
732                if (mParallaxBy != 0) {
733                    parallaxOtherViews(mSlideOffset);
734                }
735                if (((LayoutParams) mSlideableView.getLayoutParams()).dimWhenOffset) {
736                    dimChildView(mSlideableView, mSlideOffset, mSliderFadeColor);
737                }
738            } else {
739                // Reset the dim level of all children; it's irrelevant when nothing moves.
740                for (int i = 0; i < childCount; i++) {
741                    dimChildView(getChildAt(i), 0, mSliderFadeColor);
742                }
743            }
744            updateObscuredViewsVisibility(mSlideableView);
745        }
746
747        mFirstLayout = false;
748    }
749
750    @Override
751    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
752        super.onSizeChanged(w, h, oldw, oldh);
753        // Recalculate sliding panes and their details
754        if (w != oldw) {
755            mFirstLayout = true;
756        }
757    }
758
759    @Override
760    public void requestChildFocus(View child, View focused) {
761        super.requestChildFocus(child, focused);
762        if (!isInTouchMode() && !mCanSlide) {
763            mPreservedOpenState = child == mSlideableView;
764        }
765    }
766
767    @Override
768    public boolean onInterceptTouchEvent(MotionEvent ev) {
769        final int action = MotionEventCompat.getActionMasked(ev);
770
771        // Preserve the open state based on the last view that was touched.
772        if (!mCanSlide && action == MotionEvent.ACTION_DOWN && getChildCount() > 1) {
773            // After the first things will be slideable.
774            final View secondChild = getChildAt(1);
775            if (secondChild != null) {
776                mPreservedOpenState = !mDragHelper.isViewUnder(secondChild,
777                        (int) ev.getX(), (int) ev.getY());
778            }
779        }
780
781        if (!mCanSlide || (mIsUnableToDrag && action != MotionEvent.ACTION_DOWN)) {
782            mDragHelper.cancel();
783            return super.onInterceptTouchEvent(ev);
784        }
785
786        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
787            mDragHelper.cancel();
788            return false;
789        }
790
791        boolean interceptTap = false;
792
793        switch (action) {
794            case MotionEvent.ACTION_DOWN: {
795                mIsUnableToDrag = false;
796                final float x = ev.getX();
797                final float y = ev.getY();
798                mInitialMotionX = x;
799                mInitialMotionY = y;
800
801                if (mDragHelper.isViewUnder(mSlideableView, (int) x, (int) y)
802                        && isDimmed(mSlideableView)) {
803                    interceptTap = true;
804                }
805                break;
806            }
807
808            case MotionEvent.ACTION_MOVE: {
809                final float x = ev.getX();
810                final float y = ev.getY();
811                final float adx = Math.abs(x - mInitialMotionX);
812                final float ady = Math.abs(y - mInitialMotionY);
813                final int slop = mDragHelper.getTouchSlop();
814                if (adx > slop && ady > adx) {
815                    mDragHelper.cancel();
816                    mIsUnableToDrag = true;
817                    return false;
818                }
819            }
820        }
821
822        final boolean interceptForDrag = mDragHelper.shouldInterceptTouchEvent(ev);
823
824        return interceptForDrag || interceptTap;
825    }
826
827    @Override
828    public boolean onTouchEvent(MotionEvent ev) {
829        if (!mCanSlide) {
830            return super.onTouchEvent(ev);
831        }
832
833        mDragHelper.processTouchEvent(ev);
834
835        final int action = ev.getAction();
836        boolean wantTouchEvents = true;
837
838        switch (action & MotionEventCompat.ACTION_MASK) {
839            case MotionEvent.ACTION_DOWN: {
840                final float x = ev.getX();
841                final float y = ev.getY();
842                mInitialMotionX = x;
843                mInitialMotionY = y;
844                break;
845            }
846
847            case MotionEvent.ACTION_UP: {
848                if (isDimmed(mSlideableView)) {
849                    final float x = ev.getX();
850                    final float y = ev.getY();
851                    final float dx = x - mInitialMotionX;
852                    final float dy = y - mInitialMotionY;
853                    final int slop = mDragHelper.getTouchSlop();
854                    if (dx * dx + dy * dy < slop * slop
855                            && mDragHelper.isViewUnder(mSlideableView, (int) x, (int) y)) {
856                        // Taps close a dimmed open pane.
857                        closePane(mSlideableView, 0);
858                        break;
859                    }
860                }
861                break;
862            }
863        }
864
865        return wantTouchEvents;
866    }
867
868    private boolean closePane(View pane, int initialVelocity) {
869        if (mFirstLayout || smoothSlideTo(0.f, initialVelocity)) {
870            mPreservedOpenState = false;
871            return true;
872        }
873        return false;
874    }
875
876    private boolean openPane(View pane, int initialVelocity) {
877        if (mFirstLayout || smoothSlideTo(1.f, initialVelocity)) {
878            mPreservedOpenState = true;
879            return true;
880        }
881        return false;
882    }
883
884    /**
885     * @deprecated Renamed to {@link #openPane()} - this method is going away soon!
886     */
887    @Deprecated
888    public void smoothSlideOpen() {
889        openPane();
890    }
891
892    /**
893     * Open the sliding pane if it is currently slideable. If first layout
894     * has already completed this will animate.
895     *
896     * @return true if the pane was slideable and is now open/in the process of opening
897     */
898    public boolean openPane() {
899        return openPane(mSlideableView, 0);
900    }
901
902    /**
903     * @deprecated Renamed to {@link #closePane()} - this method is going away soon!
904     */
905    @Deprecated
906    public void smoothSlideClosed() {
907        closePane();
908    }
909
910    /**
911     * Close the sliding pane if it is currently slideable. If first layout
912     * has already completed this will animate.
913     *
914     * @return true if the pane was slideable and is now closed/in the process of closing
915     */
916    public boolean closePane() {
917        return closePane(mSlideableView, 0);
918    }
919
920    /**
921     * Check if the layout is completely open. It can be open either because the slider
922     * itself is open revealing the left pane, or if all content fits without sliding.
923     *
924     * @return true if sliding panels are completely open
925     */
926    public boolean isOpen() {
927        return !mCanSlide || mSlideOffset == 1;
928    }
929
930    /**
931     * @return true if content in this layout can be slid open and closed
932     * @deprecated Renamed to {@link #isSlideable()} - this method is going away soon!
933     */
934    @Deprecated
935    public boolean canSlide() {
936        return mCanSlide;
937    }
938
939    /**
940     * Check if the content in this layout cannot fully fit side by side and therefore
941     * the content pane can be slid back and forth.
942     *
943     * @return true if content in this layout can be slid open and closed
944     */
945    public boolean isSlideable() {
946        return mCanSlide;
947    }
948
949    void onPanelDragged(int newLeft) {
950        if (mSlideableView == null) {
951            // This can happen if we're aborting motion during layout because everything now fits.
952            mSlideOffset = 0;
953            return;
954        }
955        final boolean isLayoutRtl = isLayoutRtlSupport();
956        final LayoutParams lp = (LayoutParams) mSlideableView.getLayoutParams();
957
958        int childWidth = mSlideableView.getWidth();
959        final int newStart = isLayoutRtl ? getWidth() - newLeft - childWidth : newLeft;
960
961        final int paddingStart = isLayoutRtl ? getPaddingRight() : getPaddingLeft();
962        final int lpMargin = isLayoutRtl ? lp.rightMargin : lp.leftMargin;
963        final int startBound = paddingStart + lpMargin;
964
965        mSlideOffset = (float) (newStart - startBound) / mSlideRange;
966
967        if (mParallaxBy != 0) {
968            parallaxOtherViews(mSlideOffset);
969        }
970
971        if (lp.dimWhenOffset) {
972            dimChildView(mSlideableView, mSlideOffset, mSliderFadeColor);
973        }
974        dispatchOnPanelSlide(mSlideableView);
975    }
976
977    private void dimChildView(View v, float mag, int fadeColor) {
978        final LayoutParams lp = (LayoutParams) v.getLayoutParams();
979
980        if (mag > 0 && fadeColor != 0) {
981            final int baseAlpha = (fadeColor & 0xff000000) >>> 24;
982            int imag = (int) (baseAlpha * mag);
983            int color = imag << 24 | (fadeColor & 0xffffff);
984            if (lp.dimPaint == null) {
985                lp.dimPaint = new Paint();
986            }
987            lp.dimPaint.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_OVER));
988            if (ViewCompat.getLayerType(v) != ViewCompat.LAYER_TYPE_HARDWARE) {
989                ViewCompat.setLayerType(v, ViewCompat.LAYER_TYPE_HARDWARE, lp.dimPaint);
990            }
991            invalidateChildRegion(v);
992        } else if (ViewCompat.getLayerType(v) != ViewCompat.LAYER_TYPE_NONE) {
993            if (lp.dimPaint != null) {
994                lp.dimPaint.setColorFilter(null);
995            }
996            final DisableLayerRunnable dlr = new DisableLayerRunnable(v);
997            mPostedRunnables.add(dlr);
998            ViewCompat.postOnAnimation(this, dlr);
999        }
1000    }
1001
1002    @Override
1003    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
1004        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1005        boolean result;
1006        final int save = canvas.save(Canvas.CLIP_SAVE_FLAG);
1007
1008        if (mCanSlide && !lp.slideable && mSlideableView != null) {
1009            // Clip against the slider; no sense drawing what will immediately be covered.
1010            canvas.getClipBounds(mTmpRect);
1011            if (isLayoutRtlSupport()) {
1012                mTmpRect.left = Math.max(mTmpRect.left, mSlideableView.getRight());
1013            } else {
1014                mTmpRect.right = Math.min(mTmpRect.right, mSlideableView.getLeft());
1015            }
1016            canvas.clipRect(mTmpRect);
1017        }
1018
1019        if (Build.VERSION.SDK_INT >= 11) { // HC
1020            result = super.drawChild(canvas, child, drawingTime);
1021        } else {
1022            if (lp.dimWhenOffset && mSlideOffset > 0) {
1023                if (!child.isDrawingCacheEnabled()) {
1024                    child.setDrawingCacheEnabled(true);
1025                }
1026                final Bitmap cache = child.getDrawingCache();
1027                if (cache != null) {
1028                    canvas.drawBitmap(cache, child.getLeft(), child.getTop(), lp.dimPaint);
1029                    result = false;
1030                } else {
1031                    Log.e(TAG, "drawChild: child view " + child + " returned null drawing cache");
1032                    result = super.drawChild(canvas, child, drawingTime);
1033                }
1034            } else {
1035                if (child.isDrawingCacheEnabled()) {
1036                    child.setDrawingCacheEnabled(false);
1037                }
1038                result = super.drawChild(canvas, child, drawingTime);
1039            }
1040        }
1041
1042        canvas.restoreToCount(save);
1043
1044        return result;
1045    }
1046
1047    void invalidateChildRegion(View v) {
1048        IMPL.invalidateChildRegion(this, v);
1049    }
1050
1051    /**
1052     * Smoothly animate mDraggingPane to the target X position within its range.
1053     *
1054     * @param slideOffset position to animate to
1055     * @param velocity initial velocity in case of fling, or 0.
1056     */
1057    boolean smoothSlideTo(float slideOffset, int velocity) {
1058        if (!mCanSlide) {
1059            // Nothing to do.
1060            return false;
1061        }
1062
1063        final boolean isLayoutRtl = isLayoutRtlSupport();
1064        final LayoutParams lp = (LayoutParams) mSlideableView.getLayoutParams();
1065
1066        int x;
1067        if (isLayoutRtl) {
1068            int startBound = getPaddingRight() + lp.rightMargin;
1069            int childWidth = mSlideableView.getWidth();
1070            x = (int) (getWidth() - (startBound + slideOffset * mSlideRange + childWidth));
1071        } else {
1072            int startBound = getPaddingLeft() + lp.leftMargin;
1073            x = (int) (startBound + slideOffset * mSlideRange);
1074        }
1075
1076        if (mDragHelper.smoothSlideViewTo(mSlideableView, x, mSlideableView.getTop())) {
1077            setAllChildrenVisible();
1078            ViewCompat.postInvalidateOnAnimation(this);
1079            return true;
1080        }
1081        return false;
1082    }
1083
1084    @Override
1085    public void computeScroll() {
1086        if (mDragHelper.continueSettling(true)) {
1087            if (!mCanSlide) {
1088                mDragHelper.abort();
1089                return;
1090            }
1091
1092            ViewCompat.postInvalidateOnAnimation(this);
1093        }
1094    }
1095
1096    /**
1097     * @deprecated Renamed to {@link #setShadowDrawableLeft(Drawable d)} to support LTR (left to
1098     * right language) and {@link #setShadowDrawableRight(Drawable d)} to support RTL (right to left
1099     * language) during opening/closing.
1100     *
1101     * @param d drawable to use as a shadow
1102     */
1103    @Deprecated
1104    public void setShadowDrawable(Drawable d) {
1105        setShadowDrawableLeft(d);
1106    }
1107
1108    /**
1109     * Set a drawable to use as a shadow cast by the right pane onto the left pane
1110     * during opening/closing.
1111     *
1112     * @param d drawable to use as a shadow
1113     */
1114    public void setShadowDrawableLeft(Drawable d) {
1115        mShadowDrawableLeft = d;
1116    }
1117
1118    /**
1119     * Set a drawable to use as a shadow cast by the left pane onto the right pane
1120     * during opening/closing to support right to left language.
1121     *
1122     * @param d drawable to use as a shadow
1123     */
1124    public void setShadowDrawableRight(Drawable d) {
1125        mShadowDrawableRight = d;
1126    }
1127
1128    /**
1129     * Set a drawable to use as a shadow cast by the right pane onto the left pane
1130     * during opening/closing.
1131     *
1132     * @param resId Resource ID of a drawable to use
1133     * @deprecated Renamed to {@link #setShadowResourceLeft(int)} to support LTR (left to
1134     * right language) and {@link #setShadowResourceRight(int)} to support RTL (right to left
1135     * language) during opening/closing.
1136     */
1137    @Deprecated
1138    public void setShadowResource(@DrawableRes int resId) {
1139        setShadowDrawable(getResources().getDrawable(resId));
1140    }
1141
1142    /**
1143     * Set a drawable to use as a shadow cast by the right pane onto the left pane
1144     * during opening/closing.
1145     *
1146     * @param resId Resource ID of a drawable to use
1147     */
1148    public void setShadowResourceLeft(int resId) {
1149        setShadowDrawableLeft(ContextCompat.getDrawable(getContext(), resId));
1150    }
1151
1152    /**
1153     * Set a drawable to use as a shadow cast by the left pane onto the right pane
1154     * during opening/closing to support right to left language.
1155     *
1156     * @param resId Resource ID of a drawable to use
1157     */
1158    public void setShadowResourceRight(int resId) {
1159        setShadowDrawableRight(ContextCompat.getDrawable(getContext(), resId));
1160    }
1161
1162    @Override
1163    public void draw(Canvas c) {
1164        super.draw(c);
1165        final boolean isLayoutRtl = isLayoutRtlSupport();
1166        Drawable shadowDrawable;
1167        if (isLayoutRtl) {
1168            shadowDrawable = mShadowDrawableRight;
1169        } else {
1170            shadowDrawable = mShadowDrawableLeft;
1171        }
1172
1173        final View shadowView = getChildCount() > 1 ? getChildAt(1) : null;
1174        if (shadowView == null || shadowDrawable == null) {
1175            // No need to draw a shadow if we don't have one.
1176            return;
1177        }
1178
1179        final int top = shadowView.getTop();
1180        final int bottom = shadowView.getBottom();
1181
1182        final int shadowWidth = shadowDrawable.getIntrinsicWidth();
1183        final int left;
1184        final int right;
1185        if (isLayoutRtlSupport()) {
1186            left = shadowView.getRight();
1187            right = left + shadowWidth;
1188        } else {
1189            right = shadowView.getLeft();
1190            left = right - shadowWidth;
1191        }
1192
1193        shadowDrawable.setBounds(left, top, right, bottom);
1194        shadowDrawable.draw(c);
1195    }
1196
1197    private void parallaxOtherViews(float slideOffset) {
1198        final boolean isLayoutRtl = isLayoutRtlSupport();
1199        final LayoutParams slideLp = (LayoutParams) mSlideableView.getLayoutParams();
1200        final boolean dimViews = slideLp.dimWhenOffset
1201                && (isLayoutRtl ? slideLp.rightMargin : slideLp.leftMargin) <= 0;
1202        final int childCount = getChildCount();
1203        for (int i = 0; i < childCount; i++) {
1204            final View v = getChildAt(i);
1205            if (v == mSlideableView) continue;
1206
1207            final int oldOffset = (int) ((1 - mParallaxOffset) * mParallaxBy);
1208            mParallaxOffset = slideOffset;
1209            final int newOffset = (int) ((1 - slideOffset) * mParallaxBy);
1210            final int dx = oldOffset - newOffset;
1211
1212            v.offsetLeftAndRight(isLayoutRtl ? -dx : dx);
1213
1214            if (dimViews) {
1215                dimChildView(v, isLayoutRtl ? mParallaxOffset - 1
1216                        : 1 - mParallaxOffset, mCoveredFadeColor);
1217            }
1218        }
1219    }
1220
1221    /**
1222     * Tests scrollability within child views of v given a delta of dx.
1223     *
1224     * @param v View to test for horizontal scrollability
1225     * @param checkV Whether the view v passed should itself be checked for scrollability (true),
1226     *               or just its children (false).
1227     * @param dx Delta scrolled in pixels
1228     * @param x X coordinate of the active touch point
1229     * @param y Y coordinate of the active touch point
1230     * @return true if child views of v can be scrolled by delta of dx.
1231     */
1232    protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
1233        if (v instanceof ViewGroup) {
1234            final ViewGroup group = (ViewGroup) v;
1235            final int scrollX = v.getScrollX();
1236            final int scrollY = v.getScrollY();
1237            final int count = group.getChildCount();
1238            // Count backwards - let topmost views consume scroll distance first.
1239            for (int i = count - 1; i >= 0; i--) {
1240                // TODO: Add versioned support here for transformed views.
1241                // This will not work for transformed views in Honeycomb+
1242                final View child = group.getChildAt(i);
1243                if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight()
1244                        && y + scrollY >= child.getTop() && y + scrollY < child.getBottom()
1245                        && canScroll(child, true, dx, x + scrollX - child.getLeft(),
1246                                y + scrollY - child.getTop())) {
1247                    return true;
1248                }
1249            }
1250        }
1251
1252        return checkV && ViewCompat.canScrollHorizontally(v, (isLayoutRtlSupport() ? dx : -dx));
1253    }
1254
1255    boolean isDimmed(View child) {
1256        if (child == null) {
1257            return false;
1258        }
1259        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1260        return mCanSlide && lp.dimWhenOffset && mSlideOffset > 0;
1261    }
1262
1263    @Override
1264    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
1265        return new LayoutParams();
1266    }
1267
1268    @Override
1269    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
1270        return p instanceof MarginLayoutParams
1271                ? new LayoutParams((MarginLayoutParams) p)
1272                : new LayoutParams(p);
1273    }
1274
1275    @Override
1276    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
1277        return p instanceof LayoutParams && super.checkLayoutParams(p);
1278    }
1279
1280    @Override
1281    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
1282        return new LayoutParams(getContext(), attrs);
1283    }
1284
1285    @Override
1286    protected Parcelable onSaveInstanceState() {
1287        Parcelable superState = super.onSaveInstanceState();
1288
1289        SavedState ss = new SavedState(superState);
1290        ss.isOpen = isSlideable() ? isOpen() : mPreservedOpenState;
1291
1292        return ss;
1293    }
1294
1295    @Override
1296    protected void onRestoreInstanceState(Parcelable state) {
1297        if (!(state instanceof SavedState)) {
1298            super.onRestoreInstanceState(state);
1299            return;
1300        }
1301
1302        SavedState ss = (SavedState) state;
1303        super.onRestoreInstanceState(ss.getSuperState());
1304
1305        if (ss.isOpen) {
1306            openPane();
1307        } else {
1308            closePane();
1309        }
1310        mPreservedOpenState = ss.isOpen;
1311    }
1312
1313    private class DragHelperCallback extends ViewDragHelper.Callback {
1314
1315        DragHelperCallback() {
1316        }
1317
1318        @Override
1319        public boolean tryCaptureView(View child, int pointerId) {
1320            if (mIsUnableToDrag) {
1321                return false;
1322            }
1323
1324            return ((LayoutParams) child.getLayoutParams()).slideable;
1325        }
1326
1327        @Override
1328        public void onViewDragStateChanged(int state) {
1329            if (mDragHelper.getViewDragState() == ViewDragHelper.STATE_IDLE) {
1330                if (mSlideOffset == 0) {
1331                    updateObscuredViewsVisibility(mSlideableView);
1332                    dispatchOnPanelClosed(mSlideableView);
1333                    mPreservedOpenState = false;
1334                } else {
1335                    dispatchOnPanelOpened(mSlideableView);
1336                    mPreservedOpenState = true;
1337                }
1338            }
1339        }
1340
1341        @Override
1342        public void onViewCaptured(View capturedChild, int activePointerId) {
1343            // Make all child views visible in preparation for sliding things around
1344            setAllChildrenVisible();
1345        }
1346
1347        @Override
1348        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
1349            onPanelDragged(left);
1350            invalidate();
1351        }
1352
1353        @Override
1354        public void onViewReleased(View releasedChild, float xvel, float yvel) {
1355            final LayoutParams lp = (LayoutParams) releasedChild.getLayoutParams();
1356
1357            int left;
1358            if (isLayoutRtlSupport()) {
1359                int startToRight =  getPaddingRight() + lp.rightMargin;
1360                if (xvel < 0 || (xvel == 0 && mSlideOffset > 0.5f)) {
1361                    startToRight += mSlideRange;
1362                }
1363                int childWidth = mSlideableView.getWidth();
1364                left = getWidth() - startToRight - childWidth;
1365            } else {
1366                left = getPaddingLeft() + lp.leftMargin;
1367                if (xvel > 0 || (xvel == 0 && mSlideOffset > 0.5f)) {
1368                    left += mSlideRange;
1369                }
1370            }
1371            mDragHelper.settleCapturedViewAt(left, releasedChild.getTop());
1372            invalidate();
1373        }
1374
1375        @Override
1376        public int getViewHorizontalDragRange(View child) {
1377            return mSlideRange;
1378        }
1379
1380        @Override
1381        public int clampViewPositionHorizontal(View child, int left, int dx) {
1382            final LayoutParams lp = (LayoutParams) mSlideableView.getLayoutParams();
1383
1384            final int newLeft;
1385            if (isLayoutRtlSupport()) {
1386                int startBound = getWidth()
1387                        - (getPaddingRight() + lp.rightMargin + mSlideableView.getWidth());
1388                int endBound =  startBound - mSlideRange;
1389                newLeft = Math.max(Math.min(left, startBound), endBound);
1390            } else {
1391                int startBound = getPaddingLeft() + lp.leftMargin;
1392                int endBound = startBound + mSlideRange;
1393                newLeft = Math.min(Math.max(left, startBound), endBound);
1394            }
1395            return newLeft;
1396        }
1397
1398        @Override
1399        public int clampViewPositionVertical(View child, int top, int dy) {
1400            // Make sure we never move views vertically.
1401            // This could happen if the child has less height than its parent.
1402            return child.getTop();
1403        }
1404
1405        @Override
1406        public void onEdgeDragStarted(int edgeFlags, int pointerId) {
1407            mDragHelper.captureChildView(mSlideableView, pointerId);
1408        }
1409    }
1410
1411    public static class LayoutParams extends ViewGroup.MarginLayoutParams {
1412        private static final int[] ATTRS = new int[] {
1413            android.R.attr.layout_weight
1414        };
1415
1416        /**
1417         * The weighted proportion of how much of the leftover space
1418         * this child should consume after measurement.
1419         */
1420        public float weight = 0;
1421
1422        /**
1423         * True if this pane is the slideable pane in the layout.
1424         */
1425        boolean slideable;
1426
1427        /**
1428         * True if this view should be drawn dimmed
1429         * when it's been offset from its default position.
1430         */
1431        boolean dimWhenOffset;
1432
1433        Paint dimPaint;
1434
1435        public LayoutParams() {
1436            super(MATCH_PARENT, MATCH_PARENT);
1437        }
1438
1439        public LayoutParams(int width, int height) {
1440            super(width, height);
1441        }
1442
1443        public LayoutParams(android.view.ViewGroup.LayoutParams source) {
1444            super(source);
1445        }
1446
1447        public LayoutParams(MarginLayoutParams source) {
1448            super(source);
1449        }
1450
1451        public LayoutParams(LayoutParams source) {
1452            super(source);
1453            this.weight = source.weight;
1454        }
1455
1456        public LayoutParams(Context c, AttributeSet attrs) {
1457            super(c, attrs);
1458
1459            final TypedArray a = c.obtainStyledAttributes(attrs, ATTRS);
1460            this.weight = a.getFloat(0, 0);
1461            a.recycle();
1462        }
1463
1464    }
1465
1466    static class SavedState extends AbsSavedState {
1467        boolean isOpen;
1468
1469        SavedState(Parcelable superState) {
1470            super(superState);
1471        }
1472
1473        SavedState(Parcel in, ClassLoader loader) {
1474            super(in, loader);
1475            isOpen = in.readInt() != 0;
1476        }
1477
1478        @Override
1479        public void writeToParcel(Parcel out, int flags) {
1480            super.writeToParcel(out, flags);
1481            out.writeInt(isOpen ? 1 : 0);
1482        }
1483
1484        public static final Creator<SavedState> CREATOR = ParcelableCompat.newCreator(
1485                new ParcelableCompatCreatorCallbacks<SavedState>() {
1486                    @Override
1487                    public SavedState createFromParcel(Parcel in, ClassLoader loader) {
1488                        return new SavedState(in, loader);
1489                    }
1490
1491                    @Override
1492                    public SavedState[] newArray(int size) {
1493                        return new SavedState[size];
1494                    }
1495                });
1496    }
1497
1498    interface SlidingPanelLayoutImpl {
1499        void invalidateChildRegion(SlidingPaneLayout parent, View child);
1500    }
1501
1502    static class SlidingPanelLayoutImplBase implements SlidingPanelLayoutImpl {
1503        @Override
1504        public void invalidateChildRegion(SlidingPaneLayout parent, View child) {
1505            ViewCompat.postInvalidateOnAnimation(parent, child.getLeft(), child.getTop(),
1506                    child.getRight(), child.getBottom());
1507        }
1508    }
1509
1510    static class SlidingPanelLayoutImplJB extends SlidingPanelLayoutImplBase {
1511        /*
1512         * Private API hacks! Nasty! Bad!
1513         *
1514         * In Jellybean, some optimizations in the hardware UI renderer
1515         * prevent a changed Paint on a View using a hardware layer from having
1516         * the intended effect. This twiddles some internal bits on the view to force
1517         * it to recreate the display list.
1518         */
1519        private Method mGetDisplayList;
1520        private Field mRecreateDisplayList;
1521
1522        SlidingPanelLayoutImplJB() {
1523            try {
1524                mGetDisplayList = View.class.getDeclaredMethod("getDisplayList", (Class[]) null);
1525            } catch (NoSuchMethodException e) {
1526                Log.e(TAG, "Couldn't fetch getDisplayList method; dimming won't work right.", e);
1527            }
1528            try {
1529                mRecreateDisplayList = View.class.getDeclaredField("mRecreateDisplayList");
1530                mRecreateDisplayList.setAccessible(true);
1531            } catch (NoSuchFieldException e) {
1532                Log.e(TAG, "Couldn't fetch mRecreateDisplayList field; dimming will be slow.", e);
1533            }
1534        }
1535
1536        @Override
1537        public void invalidateChildRegion(SlidingPaneLayout parent, View child) {
1538            if (mGetDisplayList != null && mRecreateDisplayList != null) {
1539                try {
1540                    mRecreateDisplayList.setBoolean(child, true);
1541                    mGetDisplayList.invoke(child, (Object[]) null);
1542                } catch (Exception e) {
1543                    Log.e(TAG, "Error refreshing display list state", e);
1544                }
1545            } else {
1546                // Slow path. REALLY slow path. Let's hope we don't get here.
1547                child.invalidate();
1548                return;
1549            }
1550            super.invalidateChildRegion(parent, child);
1551        }
1552    }
1553
1554    static class SlidingPanelLayoutImplJBMR1 extends SlidingPanelLayoutImplBase {
1555        @Override
1556        public void invalidateChildRegion(SlidingPaneLayout parent, View child) {
1557            ViewCompat.setLayerPaint(child, ((LayoutParams) child.getLayoutParams()).dimPaint);
1558        }
1559    }
1560
1561    class AccessibilityDelegate extends AccessibilityDelegateCompat {
1562        private final Rect mTmpRect = new Rect();
1563
1564        @Override
1565        public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
1566            final AccessibilityNodeInfoCompat superNode = AccessibilityNodeInfoCompat.obtain(info);
1567            super.onInitializeAccessibilityNodeInfo(host, superNode);
1568            copyNodeInfoNoChildren(info, superNode);
1569            superNode.recycle();
1570
1571            info.setClassName(SlidingPaneLayout.class.getName());
1572            info.setSource(host);
1573
1574            final ViewParent parent = ViewCompat.getParentForAccessibility(host);
1575            if (parent instanceof View) {
1576                info.setParent((View) parent);
1577            }
1578
1579            // This is a best-approximation of addChildrenForAccessibility()
1580            // that accounts for filtering.
1581            final int childCount = getChildCount();
1582            for (int i = 0; i < childCount; i++) {
1583                final View child = getChildAt(i);
1584                if (!filter(child) && (child.getVisibility() == View.VISIBLE)) {
1585                    // Force importance to "yes" since we can't read the value.
1586                    ViewCompat.setImportantForAccessibility(
1587                            child, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
1588                    info.addChild(child);
1589                }
1590            }
1591        }
1592
1593        @Override
1594        public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
1595            super.onInitializeAccessibilityEvent(host, event);
1596
1597            event.setClassName(SlidingPaneLayout.class.getName());
1598        }
1599
1600        @Override
1601        public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child,
1602                AccessibilityEvent event) {
1603            if (!filter(child)) {
1604                return super.onRequestSendAccessibilityEvent(host, child, event);
1605            }
1606            return false;
1607        }
1608
1609        public boolean filter(View child) {
1610            return isDimmed(child);
1611        }
1612
1613        /**
1614         * This should really be in AccessibilityNodeInfoCompat, but there unfortunately
1615         * seem to be a few elements that are not easily cloneable using the underlying API.
1616         * Leave it private here as it's not general-purpose useful.
1617         */
1618        private void copyNodeInfoNoChildren(AccessibilityNodeInfoCompat dest,
1619                AccessibilityNodeInfoCompat src) {
1620            final Rect rect = mTmpRect;
1621
1622            src.getBoundsInParent(rect);
1623            dest.setBoundsInParent(rect);
1624
1625            src.getBoundsInScreen(rect);
1626            dest.setBoundsInScreen(rect);
1627
1628            dest.setVisibleToUser(src.isVisibleToUser());
1629            dest.setPackageName(src.getPackageName());
1630            dest.setClassName(src.getClassName());
1631            dest.setContentDescription(src.getContentDescription());
1632
1633            dest.setEnabled(src.isEnabled());
1634            dest.setClickable(src.isClickable());
1635            dest.setFocusable(src.isFocusable());
1636            dest.setFocused(src.isFocused());
1637            dest.setAccessibilityFocused(src.isAccessibilityFocused());
1638            dest.setSelected(src.isSelected());
1639            dest.setLongClickable(src.isLongClickable());
1640
1641            dest.addAction(src.getActions());
1642
1643            dest.setMovementGranularities(src.getMovementGranularities());
1644        }
1645    }
1646
1647    private class DisableLayerRunnable implements Runnable {
1648        final View mChildView;
1649
1650        DisableLayerRunnable(View childView) {
1651            mChildView = childView;
1652        }
1653
1654        @Override
1655        public void run() {
1656            if (mChildView.getParent() == SlidingPaneLayout.this) {
1657                ViewCompat.setLayerType(mChildView, ViewCompat.LAYER_TYPE_NONE, null);
1658                invalidateChildRegion(mChildView);
1659            }
1660            mPostedRunnables.remove(this);
1661        }
1662    }
1663
1664    boolean isLayoutRtlSupport() {
1665        return ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL;
1666    }
1667}
1668