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