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