• Home
  • 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.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         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         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         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()) : getPaddingLeft();
361         final int endBound = isLayoutRtl ? getPaddingLeft() : (getWidth() - getPaddingRight());
362         final int topBound = getPaddingTop();
363         final int bottomBound = getHeight() - getPaddingBottom();
364         final int left;
365         final int right;
366         final int top;
367         final int bottom;
368         if (panel != null && viewIsOpaque(panel)) {
369             left = panel.getLeft();
370             right = panel.getRight();
371             top = panel.getTop();
372             bottom = panel.getBottom();
373         } else {
374             left = right = top = bottom = 0;
375         }
376 
377         for (int i = 0, childCount = getChildCount(); i < childCount; i++) {
378             final View child = getChildAt(i);
379 
380             if (child == panel) {
381                 // There are still more children above the panel but they won't be affected.
382                 break;
383             } else if (child.getVisibility() == GONE) {
384                 continue;
385             }
386 
387             final int clampedChildLeft = Math.max(
388                     (isLayoutRtl ? endBound : startBound), child.getLeft());
389             final int clampedChildTop = Math.max(topBound, child.getTop());
390             final int clampedChildRight = Math.min(
391                     (isLayoutRtl ? startBound : 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 (v.isOpaque()) {
415             return true;
416         }
417 
418         // View#isOpaque didn't take all valid opaque scrollbar modes into account
419         // before API 18 (JB-MR2). On newer devices rely solely on isOpaque above and return false
420         // here. On older devices, check the view's background drawable directly as a fallback.
421         if (Build.VERSION.SDK_INT >= 18) {
422             return false;
423         }
424 
425         final Drawable bg = v.getBackground();
426         if (bg != null) {
427             return bg.getOpacity() == PixelFormat.OPAQUE;
428         }
429         return false;
430     }
431 
432     @Override
onAttachedToWindow()433     protected void onAttachedToWindow() {
434         super.onAttachedToWindow();
435         mFirstLayout = true;
436     }
437 
438     @Override
onDetachedFromWindow()439     protected void onDetachedFromWindow() {
440         super.onDetachedFromWindow();
441         mFirstLayout = true;
442 
443         for (int i = 0, count = mPostedRunnables.size(); i < count; i++) {
444             final DisableLayerRunnable dlr = mPostedRunnables.get(i);
445             dlr.run();
446         }
447         mPostedRunnables.clear();
448     }
449 
450     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)451     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
452         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
453         int widthSize = MeasureSpec.getSize(widthMeasureSpec);
454         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
455         int heightSize = MeasureSpec.getSize(heightMeasureSpec);
456 
457         if (widthMode != MeasureSpec.EXACTLY) {
458             if (isInEditMode()) {
459                 // Don't crash the layout editor. Consume all of the space if specified
460                 // or pick a magic number from thin air otherwise.
461                 // TODO Better communication with tools of this bogus state.
462                 // It will crash on a real device.
463                 if (widthMode == MeasureSpec.AT_MOST) {
464                     widthMode = MeasureSpec.EXACTLY;
465                 } else if (widthMode == MeasureSpec.UNSPECIFIED) {
466                     widthMode = MeasureSpec.EXACTLY;
467                     widthSize = 300;
468                 }
469             } else {
470                 throw new IllegalStateException("Width must have an exact value or MATCH_PARENT");
471             }
472         } else if (heightMode == MeasureSpec.UNSPECIFIED) {
473             if (isInEditMode()) {
474                 // Don't crash the layout editor. Pick a magic number from thin air instead.
475                 // TODO Better communication with tools of this bogus state.
476                 // It will crash on a real device.
477                 if (heightMode == MeasureSpec.UNSPECIFIED) {
478                     heightMode = MeasureSpec.AT_MOST;
479                     heightSize = 300;
480                 }
481             } else {
482                 throw new IllegalStateException("Height must not be UNSPECIFIED");
483             }
484         }
485 
486         int layoutHeight = 0;
487         int maxLayoutHeight = -1;
488         switch (heightMode) {
489             case MeasureSpec.EXACTLY:
490                 layoutHeight = maxLayoutHeight = heightSize - getPaddingTop() - getPaddingBottom();
491                 break;
492             case MeasureSpec.AT_MOST:
493                 maxLayoutHeight = heightSize - getPaddingTop() - getPaddingBottom();
494                 break;
495         }
496 
497         float weightSum = 0;
498         boolean canSlide = false;
499         final int widthAvailable = widthSize - getPaddingLeft() - getPaddingRight();
500         int widthRemaining = widthAvailable;
501         final int childCount = getChildCount();
502 
503         if (childCount > 2) {
504             Log.e(TAG, "onMeasure: More than two child views are not supported.");
505         }
506 
507         // We'll find the current one below.
508         mSlideableView = null;
509 
510         // First pass. Measure based on child LayoutParams width/height.
511         // Weight will incur a second pass.
512         for (int i = 0; i < childCount; i++) {
513             final View child = getChildAt(i);
514             final LayoutParams lp = (LayoutParams) child.getLayoutParams();
515 
516             if (child.getVisibility() == GONE) {
517                 lp.dimWhenOffset = false;
518                 continue;
519             }
520 
521             if (lp.weight > 0) {
522                 weightSum += lp.weight;
523 
524                 // If we have no width, weight is the only contributor to the final size.
525                 // Measure this view on the weight pass only.
526                 if (lp.width == 0) continue;
527             }
528 
529             int childWidthSpec;
530             final int horizontalMargin = lp.leftMargin + lp.rightMargin;
531             if (lp.width == LayoutParams.WRAP_CONTENT) {
532                 childWidthSpec = MeasureSpec.makeMeasureSpec(widthAvailable - horizontalMargin,
533                         MeasureSpec.AT_MOST);
534             } else if (lp.width == LayoutParams.MATCH_PARENT) {
535                 childWidthSpec = MeasureSpec.makeMeasureSpec(widthAvailable - horizontalMargin,
536                         MeasureSpec.EXACTLY);
537             } else {
538                 childWidthSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
539             }
540 
541             int childHeightSpec;
542             if (lp.height == LayoutParams.WRAP_CONTENT) {
543                 childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.AT_MOST);
544             } else if (lp.height == LayoutParams.MATCH_PARENT) {
545                 childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.EXACTLY);
546             } else {
547                 childHeightSpec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
548             }
549 
550             child.measure(childWidthSpec, childHeightSpec);
551             final int childWidth = child.getMeasuredWidth();
552             final int childHeight = child.getMeasuredHeight();
553 
554             if (heightMode == MeasureSpec.AT_MOST && childHeight > layoutHeight) {
555                 layoutHeight = Math.min(childHeight, maxLayoutHeight);
556             }
557 
558             widthRemaining -= childWidth;
559             canSlide |= lp.slideable = widthRemaining < 0;
560             if (lp.slideable) {
561                 mSlideableView = child;
562             }
563         }
564 
565         // Resolve weight and make sure non-sliding panels are smaller than the full screen.
566         if (canSlide || weightSum > 0) {
567             final int fixedPanelWidthLimit = widthAvailable - mOverhangSize;
568 
569             for (int i = 0; i < childCount; i++) {
570                 final View child = getChildAt(i);
571 
572                 if (child.getVisibility() == GONE) {
573                     continue;
574                 }
575 
576                 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
577 
578                 if (child.getVisibility() == GONE) {
579                     continue;
580                 }
581 
582                 final boolean skippedFirstPass = lp.width == 0 && lp.weight > 0;
583                 final int measuredWidth = skippedFirstPass ? 0 : child.getMeasuredWidth();
584                 if (canSlide && child != mSlideableView) {
585                     if (lp.width < 0 && (measuredWidth > fixedPanelWidthLimit || lp.weight > 0)) {
586                         // Fixed panels in a sliding configuration should
587                         // be clamped to the fixed panel limit.
588                         final int childHeightSpec;
589                         if (skippedFirstPass) {
590                             // Do initial height measurement if we skipped measuring this view
591                             // the first time around.
592                             if (lp.height == LayoutParams.WRAP_CONTENT) {
593                                 childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,
594                                         MeasureSpec.AT_MOST);
595                             } else if (lp.height == LayoutParams.MATCH_PARENT) {
596                                 childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,
597                                         MeasureSpec.EXACTLY);
598                             } else {
599                                 childHeightSpec = MeasureSpec.makeMeasureSpec(lp.height,
600                                         MeasureSpec.EXACTLY);
601                             }
602                         } else {
603                             childHeightSpec = MeasureSpec.makeMeasureSpec(
604                                     child.getMeasuredHeight(), MeasureSpec.EXACTLY);
605                         }
606                         final int childWidthSpec = MeasureSpec.makeMeasureSpec(
607                                 fixedPanelWidthLimit, MeasureSpec.EXACTLY);
608                         child.measure(childWidthSpec, childHeightSpec);
609                     }
610                 } else if (lp.weight > 0) {
611                     int childHeightSpec;
612                     if (lp.width == 0) {
613                         // This was skipped the first time; figure out a real height spec.
614                         if (lp.height == LayoutParams.WRAP_CONTENT) {
615                             childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,
616                                     MeasureSpec.AT_MOST);
617                         } else if (lp.height == LayoutParams.MATCH_PARENT) {
618                             childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,
619                                     MeasureSpec.EXACTLY);
620                         } else {
621                             childHeightSpec = MeasureSpec.makeMeasureSpec(lp.height,
622                                     MeasureSpec.EXACTLY);
623                         }
624                     } else {
625                         childHeightSpec = MeasureSpec.makeMeasureSpec(
626                                 child.getMeasuredHeight(), MeasureSpec.EXACTLY);
627                     }
628 
629                     if (canSlide) {
630                         // Consume available space
631                         final int horizontalMargin = lp.leftMargin + lp.rightMargin;
632                         final int newWidth = widthAvailable - horizontalMargin;
633                         final int childWidthSpec = MeasureSpec.makeMeasureSpec(
634                                 newWidth, MeasureSpec.EXACTLY);
635                         if (measuredWidth != newWidth) {
636                             child.measure(childWidthSpec, childHeightSpec);
637                         }
638                     } else {
639                         // Distribute the extra width proportionally similar to LinearLayout
640                         final int widthToDistribute = Math.max(0, widthRemaining);
641                         final int addedWidth = (int) (lp.weight * widthToDistribute / weightSum);
642                         final int childWidthSpec = MeasureSpec.makeMeasureSpec(
643                                 measuredWidth + addedWidth, MeasureSpec.EXACTLY);
644                         child.measure(childWidthSpec, childHeightSpec);
645                     }
646                 }
647             }
648         }
649 
650         final int measuredWidth = widthSize;
651         final int measuredHeight = layoutHeight + getPaddingTop() + getPaddingBottom();
652 
653         setMeasuredDimension(measuredWidth, measuredHeight);
654         mCanSlide = canSlide;
655 
656         if (mDragHelper.getViewDragState() != ViewDragHelper.STATE_IDLE && !canSlide) {
657             // Cancel scrolling in progress, it's no longer relevant.
658             mDragHelper.abort();
659         }
660     }
661 
662     @Override
onLayout(boolean changed, int l, int t, int r, int b)663     protected void onLayout(boolean changed, int l, int t, int r, int b) {
664         final boolean isLayoutRtl = isLayoutRtlSupport();
665         if (isLayoutRtl) {
666             mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_RIGHT);
667         } else {
668             mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
669         }
670         final int width = r - l;
671         final int paddingStart = isLayoutRtl ? getPaddingRight() : getPaddingLeft();
672         final int paddingEnd = isLayoutRtl ? getPaddingLeft() : getPaddingRight();
673         final int paddingTop = getPaddingTop();
674 
675         final int childCount = getChildCount();
676         int xStart = paddingStart;
677         int nextXStart = xStart;
678 
679         if (mFirstLayout) {
680             mSlideOffset = mCanSlide && mPreservedOpenState ? 1.f : 0.f;
681         }
682 
683         for (int i = 0; i < childCount; i++) {
684             final View child = getChildAt(i);
685 
686             if (child.getVisibility() == GONE) {
687                 continue;
688             }
689 
690             final LayoutParams lp = (LayoutParams) child.getLayoutParams();
691 
692             final int childWidth = child.getMeasuredWidth();
693             int offset = 0;
694 
695             if (lp.slideable) {
696                 final int margin = lp.leftMargin + lp.rightMargin;
697                 final int range = Math.min(nextXStart,
698                         width - paddingEnd - mOverhangSize) - xStart - margin;
699                 mSlideRange = range;
700                 final int lpMargin = isLayoutRtl ? lp.rightMargin : lp.leftMargin;
701                 lp.dimWhenOffset = xStart + lpMargin + range + childWidth / 2 > width - paddingEnd;
702                 final int pos = (int) (range * mSlideOffset);
703                 xStart += pos + lpMargin;
704                 mSlideOffset = (float) pos / mSlideRange;
705             } else if (mCanSlide && mParallaxBy != 0) {
706                 offset = (int) ((1 - mSlideOffset) * mParallaxBy);
707                 xStart = nextXStart;
708             } else {
709                 xStart = nextXStart;
710             }
711 
712             final int childRight;
713             final int childLeft;
714             if (isLayoutRtl) {
715                 childRight = width - xStart + offset;
716                 childLeft = childRight - childWidth;
717             } else {
718                 childLeft = xStart - offset;
719                 childRight = childLeft + childWidth;
720             }
721 
722             final int childTop = paddingTop;
723             final int childBottom = childTop + child.getMeasuredHeight();
724             child.layout(childLeft, paddingTop, childRight, childBottom);
725 
726             nextXStart += child.getWidth();
727         }
728 
729         if (mFirstLayout) {
730             if (mCanSlide) {
731                 if (mParallaxBy != 0) {
732                     parallaxOtherViews(mSlideOffset);
733                 }
734                 if (((LayoutParams) mSlideableView.getLayoutParams()).dimWhenOffset) {
735                     dimChildView(mSlideableView, mSlideOffset, mSliderFadeColor);
736                 }
737             } else {
738                 // Reset the dim level of all children; it's irrelevant when nothing moves.
739                 for (int i = 0; i < childCount; i++) {
740                     dimChildView(getChildAt(i), 0, mSliderFadeColor);
741                 }
742             }
743             updateObscuredViewsVisibility(mSlideableView);
744         }
745 
746         mFirstLayout = false;
747     }
748 
749     @Override
onSizeChanged(int w, int h, int oldw, int oldh)750     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
751         super.onSizeChanged(w, h, oldw, oldh);
752         // Recalculate sliding panes and their details
753         if (w != oldw) {
754             mFirstLayout = true;
755         }
756     }
757 
758     @Override
requestChildFocus(View child, View focused)759     public void requestChildFocus(View child, View focused) {
760         super.requestChildFocus(child, focused);
761         if (!isInTouchMode() && !mCanSlide) {
762             mPreservedOpenState = child == mSlideableView;
763         }
764     }
765 
766     @Override
onInterceptTouchEvent(MotionEvent ev)767     public boolean onInterceptTouchEvent(MotionEvent ev) {
768         final int action = MotionEventCompat.getActionMasked(ev);
769 
770         // Preserve the open state based on the last view that was touched.
771         if (!mCanSlide && action == MotionEvent.ACTION_DOWN && getChildCount() > 1) {
772             // After the first things will be slideable.
773             final View secondChild = getChildAt(1);
774             if (secondChild != null) {
775                 mPreservedOpenState = !mDragHelper.isViewUnder(secondChild,
776                         (int) ev.getX(), (int) ev.getY());
777             }
778         }
779 
780         if (!mCanSlide || (mIsUnableToDrag && action != MotionEvent.ACTION_DOWN)) {
781             mDragHelper.cancel();
782             return super.onInterceptTouchEvent(ev);
783         }
784 
785         if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
786             mDragHelper.cancel();
787             return false;
788         }
789 
790         boolean interceptTap = false;
791 
792         switch (action) {
793             case MotionEvent.ACTION_DOWN: {
794                 mIsUnableToDrag = false;
795                 final float x = ev.getX();
796                 final float y = ev.getY();
797                 mInitialMotionX = x;
798                 mInitialMotionY = y;
799 
800                 if (mDragHelper.isViewUnder(mSlideableView, (int) x, (int) y)
801                         && isDimmed(mSlideableView)) {
802                     interceptTap = true;
803                 }
804                 break;
805             }
806 
807             case MotionEvent.ACTION_MOVE: {
808                 final float x = ev.getX();
809                 final float y = ev.getY();
810                 final float adx = Math.abs(x - mInitialMotionX);
811                 final float ady = Math.abs(y - mInitialMotionY);
812                 final int slop = mDragHelper.getTouchSlop();
813                 if (adx > slop && ady > adx) {
814                     mDragHelper.cancel();
815                     mIsUnableToDrag = true;
816                     return false;
817                 }
818             }
819         }
820 
821         final boolean interceptForDrag = mDragHelper.shouldInterceptTouchEvent(ev);
822 
823         return interceptForDrag || interceptTap;
824     }
825 
826     @Override
onTouchEvent(MotionEvent ev)827     public boolean onTouchEvent(MotionEvent ev) {
828         if (!mCanSlide) {
829             return super.onTouchEvent(ev);
830         }
831 
832         mDragHelper.processTouchEvent(ev);
833 
834         final int action = ev.getAction();
835         boolean wantTouchEvents = true;
836 
837         switch (action & MotionEventCompat.ACTION_MASK) {
838             case MotionEvent.ACTION_DOWN: {
839                 final float x = ev.getX();
840                 final float y = ev.getY();
841                 mInitialMotionX = x;
842                 mInitialMotionY = y;
843                 break;
844             }
845 
846             case MotionEvent.ACTION_UP: {
847                 if (isDimmed(mSlideableView)) {
848                     final float x = ev.getX();
849                     final float y = ev.getY();
850                     final float dx = x - mInitialMotionX;
851                     final float dy = y - mInitialMotionY;
852                     final int slop = mDragHelper.getTouchSlop();
853                     if (dx * dx + dy * dy < slop * slop
854                             && mDragHelper.isViewUnder(mSlideableView, (int) x, (int) y)) {
855                         // Taps close a dimmed open pane.
856                         closePane(mSlideableView, 0);
857                         break;
858                     }
859                 }
860                 break;
861             }
862         }
863 
864         return wantTouchEvents;
865     }
866 
closePane(View pane, int initialVelocity)867     private boolean closePane(View pane, int initialVelocity) {
868         if (mFirstLayout || smoothSlideTo(0.f, initialVelocity)) {
869             mPreservedOpenState = false;
870             return true;
871         }
872         return false;
873     }
874 
openPane(View pane, int initialVelocity)875     private boolean openPane(View pane, int initialVelocity) {
876         if (mFirstLayout || smoothSlideTo(1.f, initialVelocity)) {
877             mPreservedOpenState = true;
878             return true;
879         }
880         return false;
881     }
882 
883     /**
884      * @deprecated Renamed to {@link #openPane()} - this method is going away soon!
885      */
886     @Deprecated
smoothSlideOpen()887     public void smoothSlideOpen() {
888         openPane();
889     }
890 
891     /**
892      * Open the sliding pane if it is currently slideable. If first layout
893      * has already completed this will animate.
894      *
895      * @return true if the pane was slideable and is now open/in the process of opening
896      */
openPane()897     public boolean openPane() {
898         return openPane(mSlideableView, 0);
899     }
900 
901     /**
902      * @deprecated Renamed to {@link #closePane()} - this method is going away soon!
903      */
904     @Deprecated
smoothSlideClosed()905     public void smoothSlideClosed() {
906         closePane();
907     }
908 
909     /**
910      * Close the sliding pane if it is currently slideable. If first layout
911      * has already completed this will animate.
912      *
913      * @return true if the pane was slideable and is now closed/in the process of closing
914      */
closePane()915     public boolean closePane() {
916         return closePane(mSlideableView, 0);
917     }
918 
919     /**
920      * Check if the layout is completely open. It can be open either because the slider
921      * itself is open revealing the left pane, or if all content fits without sliding.
922      *
923      * @return true if sliding panels are completely open
924      */
isOpen()925     public boolean isOpen() {
926         return !mCanSlide || mSlideOffset == 1;
927     }
928 
929     /**
930      * @return true if content in this layout can be slid open and closed
931      * @deprecated Renamed to {@link #isSlideable()} - this method is going away soon!
932      */
933     @Deprecated
canSlide()934     public boolean canSlide() {
935         return mCanSlide;
936     }
937 
938     /**
939      * Check if the content in this layout cannot fully fit side by side and therefore
940      * the content pane can be slid back and forth.
941      *
942      * @return true if content in this layout can be slid open and closed
943      */
isSlideable()944     public boolean isSlideable() {
945         return mCanSlide;
946     }
947 
onPanelDragged(int newLeft)948     private void onPanelDragged(int newLeft) {
949         if (mSlideableView == null) {
950             // This can happen if we're aborting motion during layout because everything now fits.
951             mSlideOffset = 0;
952             return;
953         }
954         final boolean isLayoutRtl = isLayoutRtlSupport();
955         final LayoutParams lp = (LayoutParams) mSlideableView.getLayoutParams();
956 
957         int childWidth = mSlideableView.getWidth();
958         final int newStart = isLayoutRtl ? getWidth() - newLeft - childWidth : newLeft;
959 
960         final int paddingStart = isLayoutRtl ? getPaddingRight() : getPaddingLeft();
961         final int lpMargin = isLayoutRtl ? lp.rightMargin : lp.leftMargin;
962         final int startBound = paddingStart + lpMargin;
963 
964         mSlideOffset = (float) (newStart - startBound) / mSlideRange;
965 
966         if (mParallaxBy != 0) {
967             parallaxOtherViews(mSlideOffset);
968         }
969 
970         if (lp.dimWhenOffset) {
971             dimChildView(mSlideableView, mSlideOffset, mSliderFadeColor);
972         }
973         dispatchOnPanelSlide(mSlideableView);
974     }
975 
dimChildView(View v, float mag, int fadeColor)976     private void dimChildView(View v, float mag, int fadeColor) {
977         final LayoutParams lp = (LayoutParams) v.getLayoutParams();
978 
979         if (mag > 0 && fadeColor != 0) {
980             final int baseAlpha = (fadeColor & 0xff000000) >>> 24;
981             int imag = (int) (baseAlpha * mag);
982             int color = imag << 24 | (fadeColor & 0xffffff);
983             if (lp.dimPaint == null) {
984                 lp.dimPaint = new Paint();
985             }
986             lp.dimPaint.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_OVER));
987             if (ViewCompat.getLayerType(v) != ViewCompat.LAYER_TYPE_HARDWARE) {
988                 ViewCompat.setLayerType(v, ViewCompat.LAYER_TYPE_HARDWARE, lp.dimPaint);
989             }
990             invalidateChildRegion(v);
991         } else if (ViewCompat.getLayerType(v) != ViewCompat.LAYER_TYPE_NONE) {
992             if (lp.dimPaint != null) {
993                 lp.dimPaint.setColorFilter(null);
994             }
995             final DisableLayerRunnable dlr = new DisableLayerRunnable(v);
996             mPostedRunnables.add(dlr);
997             ViewCompat.postOnAnimation(this, dlr);
998         }
999     }
1000 
1001     @Override
drawChild(Canvas canvas, View child, long drawingTime)1002     protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
1003         final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1004         boolean result;
1005         final int save = canvas.save(Canvas.CLIP_SAVE_FLAG);
1006 
1007         if (mCanSlide && !lp.slideable && mSlideableView != null) {
1008             // Clip against the slider; no sense drawing what will immediately be covered.
1009             canvas.getClipBounds(mTmpRect);
1010             if (isLayoutRtlSupport()) {
1011                 mTmpRect.left = Math.max(mTmpRect.left, mSlideableView.getRight());
1012             } else {
1013                 mTmpRect.right = Math.min(mTmpRect.right, mSlideableView.getLeft());
1014             }
1015             canvas.clipRect(mTmpRect);
1016         }
1017 
1018         if (Build.VERSION.SDK_INT >= 11) { // HC
1019             result = super.drawChild(canvas, child, drawingTime);
1020         } else {
1021             if (lp.dimWhenOffset && mSlideOffset > 0) {
1022                 if (!child.isDrawingCacheEnabled()) {
1023                     child.setDrawingCacheEnabled(true);
1024                 }
1025                 final Bitmap cache = child.getDrawingCache();
1026                 if (cache != null) {
1027                     canvas.drawBitmap(cache, child.getLeft(), child.getTop(), lp.dimPaint);
1028                     result = false;
1029                 } else {
1030                     Log.e(TAG, "drawChild: child view " + child + " returned null drawing cache");
1031                     result = super.drawChild(canvas, child, drawingTime);
1032                 }
1033             } else {
1034                 if (child.isDrawingCacheEnabled()) {
1035                     child.setDrawingCacheEnabled(false);
1036                 }
1037                 result = super.drawChild(canvas, child, drawingTime);
1038             }
1039         }
1040 
1041         canvas.restoreToCount(save);
1042 
1043         return result;
1044     }
1045 
invalidateChildRegion(View v)1046     private void invalidateChildRegion(View v) {
1047         IMPL.invalidateChildRegion(this, v);
1048     }
1049 
1050     /**
1051      * Smoothly animate mDraggingPane to the target X position within its range.
1052      *
1053      * @param slideOffset position to animate to
1054      * @param velocity initial velocity in case of fling, or 0.
1055      */
smoothSlideTo(float slideOffset, int velocity)1056     boolean smoothSlideTo(float slideOffset, int velocity) {
1057         if (!mCanSlide) {
1058             // Nothing to do.
1059             return false;
1060         }
1061 
1062         final boolean isLayoutRtl = isLayoutRtlSupport();
1063         final LayoutParams lp = (LayoutParams) mSlideableView.getLayoutParams();
1064 
1065         int x;
1066         if (isLayoutRtl) {
1067             int startBound = getPaddingRight() + lp.rightMargin;
1068             int childWidth = mSlideableView.getWidth();
1069             x = (int) (getWidth() - (startBound + slideOffset * mSlideRange + childWidth));
1070         } else {
1071             int startBound = getPaddingLeft() + lp.leftMargin;
1072             x = (int) (startBound + slideOffset * mSlideRange);
1073         }
1074 
1075         if (mDragHelper.smoothSlideViewTo(mSlideableView, x, mSlideableView.getTop())) {
1076             setAllChildrenVisible();
1077             ViewCompat.postInvalidateOnAnimation(this);
1078             return true;
1079         }
1080         return false;
1081     }
1082 
1083     @Override
computeScroll()1084     public void computeScroll() {
1085         if (mDragHelper.continueSettling(true)) {
1086             if (!mCanSlide) {
1087                 mDragHelper.abort();
1088                 return;
1089             }
1090 
1091             ViewCompat.postInvalidateOnAnimation(this);
1092         }
1093     }
1094 
1095     /**
1096      * @deprecated Renamed to {@link #setShadowDrawableLeft(Drawable d)} to support LTR (left to
1097      * right language) and {@link #setShadowDrawableRight(Drawable d)} to support RTL (right to left
1098      * language) during opening/closing.
1099      *
1100      * @param d drawable to use as a shadow
1101      */
1102     @Deprecated
setShadowDrawable(Drawable d)1103     public void setShadowDrawable(Drawable d) {
1104         setShadowDrawableLeft(d);
1105     }
1106 
1107     /**
1108      * Set a drawable to use as a shadow cast by the right pane onto the left pane
1109      * during opening/closing.
1110      *
1111      * @param d drawable to use as a shadow
1112      */
setShadowDrawableLeft(Drawable d)1113     public void setShadowDrawableLeft(Drawable d) {
1114         mShadowDrawableLeft = d;
1115     }
1116 
1117     /**
1118      * Set a drawable to use as a shadow cast by the left pane onto the right pane
1119      * during opening/closing to support right to left language.
1120      *
1121      * @param d drawable to use as a shadow
1122      */
setShadowDrawableRight(Drawable d)1123     public void setShadowDrawableRight(Drawable d) {
1124         mShadowDrawableRight = d;
1125     }
1126 
1127     /**
1128      * Set a drawable to use as a shadow cast by the right pane onto the left pane
1129      * during opening/closing.
1130      *
1131      * @param resId Resource ID of a drawable to use
1132      * @deprecated Renamed to {@link #setShadowResourceLeft(int)} to support LTR (left to
1133      * right language) and {@link #setShadowResourceRight(int)} to support RTL (right to left
1134      * language) during opening/closing.
1135      */
1136     @Deprecated
setShadowResource(@rawableRes int resId)1137     public void setShadowResource(@DrawableRes int resId) {
1138         setShadowDrawable(getResources().getDrawable(resId));
1139     }
1140 
1141     /**
1142      * Set a drawable to use as a shadow cast by the right pane onto the left pane
1143      * during opening/closing.
1144      *
1145      * @param resId Resource ID of a drawable to use
1146      */
setShadowResourceLeft(int resId)1147     public void setShadowResourceLeft(int resId) {
1148         setShadowDrawableLeft(getResources().getDrawable(resId));
1149     }
1150 
1151     /**
1152      * Set a drawable to use as a shadow cast by the left pane onto the right pane
1153      * during opening/closing to support right to left language.
1154      *
1155      * @param resId Resource ID of a drawable to use
1156      */
setShadowResourceRight(int resId)1157     public void setShadowResourceRight(int resId) {
1158         setShadowDrawableRight(getResources().getDrawable(resId));
1159     }
1160 
1161 
1162     @Override
draw(Canvas c)1163     public void draw(Canvas c) {
1164         super.draw(c);
1165         final boolean isLayoutRtl = isLayoutRtlSupport();
1166         Drawable shadowDrawable;
1167         if (isLayoutRtl) {
1168             shadowDrawable = mShadowDrawableRight;
1169         } else {
1170             shadowDrawable = mShadowDrawableLeft;
1171         }
1172 
1173         final View shadowView = getChildCount() > 1 ? getChildAt(1) : null;
1174         if (shadowView == null || shadowDrawable == null) {
1175             // No need to draw a shadow if we don't have one.
1176             return;
1177         }
1178 
1179         final int top = shadowView.getTop();
1180         final int bottom = shadowView.getBottom();
1181 
1182         final int shadowWidth = shadowDrawable.getIntrinsicWidth();
1183         final int left;
1184         final int right;
1185         if (isLayoutRtlSupport()) {
1186             left = shadowView.getRight();
1187             right = left + shadowWidth;
1188         } else {
1189             right = shadowView.getLeft();
1190             left = right - shadowWidth;
1191         }
1192 
1193         shadowDrawable.setBounds(left, top, right, bottom);
1194         shadowDrawable.draw(c);
1195     }
1196 
parallaxOtherViews(float slideOffset)1197     private void parallaxOtherViews(float slideOffset) {
1198         final boolean isLayoutRtl = isLayoutRtlSupport();
1199         final LayoutParams slideLp = (LayoutParams) mSlideableView.getLayoutParams();
1200         final boolean dimViews = slideLp.dimWhenOffset
1201                 && (isLayoutRtl ? slideLp.rightMargin : slideLp.leftMargin) <= 0;
1202         final int childCount = getChildCount();
1203         for (int i = 0; i < childCount; i++) {
1204             final View v = getChildAt(i);
1205             if (v == mSlideableView) continue;
1206 
1207             final int oldOffset = (int) ((1 - mParallaxOffset) * mParallaxBy);
1208             mParallaxOffset = slideOffset;
1209             final int newOffset = (int) ((1 - slideOffset) * mParallaxBy);
1210             final int dx = oldOffset - newOffset;
1211 
1212             v.offsetLeftAndRight(isLayoutRtl ? -dx : dx);
1213 
1214             if (dimViews) {
1215                 dimChildView(v, isLayoutRtl ? mParallaxOffset - 1
1216                         : 1 - mParallaxOffset, mCoveredFadeColor);
1217             }
1218         }
1219     }
1220 
1221     /**
1222      * Tests scrollability within child views of v given a delta of dx.
1223      *
1224      * @param v View to test for horizontal scrollability
1225      * @param checkV Whether the view v passed should itself be checked for scrollability (true),
1226      *               or just its children (false).
1227      * @param dx Delta scrolled in pixels
1228      * @param x X coordinate of the active touch point
1229      * @param y Y coordinate of the active touch point
1230      * @return true if child views of v can be scrolled by delta of dx.
1231      */
canScroll(View v, boolean checkV, int dx, int x, int y)1232     protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
1233         if (v instanceof ViewGroup) {
1234             final ViewGroup group = (ViewGroup) v;
1235             final int scrollX = v.getScrollX();
1236             final int scrollY = v.getScrollY();
1237             final int count = group.getChildCount();
1238             // Count backwards - let topmost views consume scroll distance first.
1239             for (int i = count - 1; i >= 0; i--) {
1240                 // TODO: Add versioned support here for transformed views.
1241                 // This will not work for transformed views in Honeycomb+
1242                 final View child = group.getChildAt(i);
1243                 if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight()
1244                         && y + scrollY >= child.getTop() && y + scrollY < child.getBottom()
1245                         && canScroll(child, true, dx, x + scrollX - child.getLeft(),
1246                                 y + scrollY - child.getTop())) {
1247                     return true;
1248                 }
1249             }
1250         }
1251 
1252         return checkV && ViewCompat.canScrollHorizontally(v, (isLayoutRtlSupport() ? dx : -dx));
1253     }
1254 
isDimmed(View child)1255     boolean isDimmed(View child) {
1256         if (child == null) {
1257             return false;
1258         }
1259         final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1260         return mCanSlide && lp.dimWhenOffset && mSlideOffset > 0;
1261     }
1262 
1263     @Override
generateDefaultLayoutParams()1264     protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
1265         return new LayoutParams();
1266     }
1267 
1268     @Override
generateLayoutParams(ViewGroup.LayoutParams p)1269     protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
1270         return p instanceof MarginLayoutParams
1271                 ? new LayoutParams((MarginLayoutParams) p)
1272                 : new LayoutParams(p);
1273     }
1274 
1275     @Override
checkLayoutParams(ViewGroup.LayoutParams p)1276     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
1277         return p instanceof LayoutParams && super.checkLayoutParams(p);
1278     }
1279 
1280     @Override
generateLayoutParams(AttributeSet attrs)1281     public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
1282         return new LayoutParams(getContext(), attrs);
1283     }
1284 
1285     @Override
onSaveInstanceState()1286     protected Parcelable onSaveInstanceState() {
1287         Parcelable superState = super.onSaveInstanceState();
1288 
1289         SavedState ss = new SavedState(superState);
1290         ss.isOpen = isSlideable() ? isOpen() : mPreservedOpenState;
1291 
1292         return ss;
1293     }
1294 
1295     @Override
onRestoreInstanceState(Parcelable state)1296     protected void onRestoreInstanceState(Parcelable state) {
1297         if (!(state instanceof SavedState)) {
1298             super.onRestoreInstanceState(state);
1299             return;
1300         }
1301 
1302         SavedState ss = (SavedState) state;
1303         super.onRestoreInstanceState(ss.getSuperState());
1304 
1305         if (ss.isOpen) {
1306             openPane();
1307         } else {
1308             closePane();
1309         }
1310         mPreservedOpenState = ss.isOpen;
1311     }
1312 
1313     private class DragHelperCallback extends ViewDragHelper.Callback {
1314 
1315         @Override
tryCaptureView(View child, int pointerId)1316         public boolean tryCaptureView(View child, int pointerId) {
1317             if (mIsUnableToDrag) {
1318                 return false;
1319             }
1320 
1321             return ((LayoutParams) child.getLayoutParams()).slideable;
1322         }
1323 
1324         @Override
onViewDragStateChanged(int state)1325         public void onViewDragStateChanged(int state) {
1326             if (mDragHelper.getViewDragState() == ViewDragHelper.STATE_IDLE) {
1327                 if (mSlideOffset == 0) {
1328                     updateObscuredViewsVisibility(mSlideableView);
1329                     dispatchOnPanelClosed(mSlideableView);
1330                     mPreservedOpenState = false;
1331                 } else {
1332                     dispatchOnPanelOpened(mSlideableView);
1333                     mPreservedOpenState = true;
1334                 }
1335             }
1336         }
1337 
1338         @Override
onViewCaptured(View capturedChild, int activePointerId)1339         public void onViewCaptured(View capturedChild, int activePointerId) {
1340             // Make all child views visible in preparation for sliding things around
1341             setAllChildrenVisible();
1342         }
1343 
1344         @Override
onViewPositionChanged(View changedView, int left, int top, int dx, int dy)1345         public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
1346             onPanelDragged(left);
1347             invalidate();
1348         }
1349 
1350         @Override
onViewReleased(View releasedChild, float xvel, float yvel)1351         public void onViewReleased(View releasedChild, float xvel, float yvel) {
1352             final LayoutParams lp = (LayoutParams) releasedChild.getLayoutParams();
1353 
1354             int left;
1355             if (isLayoutRtlSupport()) {
1356                 int startToRight =  getPaddingRight() + lp.rightMargin;
1357                 if (xvel < 0 || (xvel == 0 && mSlideOffset > 0.5f)) {
1358                     startToRight += mSlideRange;
1359                 }
1360                 int childWidth = mSlideableView.getWidth();
1361                 left = getWidth() - startToRight - childWidth;
1362             } else {
1363                 left = getPaddingLeft() + lp.leftMargin;
1364                 if (xvel > 0 || (xvel == 0 && mSlideOffset > 0.5f)) {
1365                     left += mSlideRange;
1366                 }
1367             }
1368             mDragHelper.settleCapturedViewAt(left, releasedChild.getTop());
1369             invalidate();
1370         }
1371 
1372         @Override
getViewHorizontalDragRange(View child)1373         public int getViewHorizontalDragRange(View child) {
1374             return mSlideRange;
1375         }
1376 
1377         @Override
clampViewPositionHorizontal(View child, int left, int dx)1378         public int clampViewPositionHorizontal(View child, int left, int dx) {
1379             final LayoutParams lp = (LayoutParams) mSlideableView.getLayoutParams();
1380 
1381             final int newLeft;
1382             if (isLayoutRtlSupport()) {
1383                 int startBound = getWidth()
1384                         - (getPaddingRight() + lp.rightMargin + mSlideableView.getWidth());
1385                 int endBound =  startBound - mSlideRange;
1386                 newLeft = Math.max(Math.min(left, startBound), endBound);
1387             } else {
1388                 int startBound = getPaddingLeft() + lp.leftMargin;
1389                 int endBound = startBound + mSlideRange;
1390                 newLeft = Math.min(Math.max(left, startBound), endBound);
1391             }
1392             return newLeft;
1393         }
1394 
1395         @Override
clampViewPositionVertical(View child, int top, int dy)1396         public int clampViewPositionVertical(View child, int top, int dy) {
1397             // Make sure we never move views vertically.
1398             // This could happen if the child has less height than its parent.
1399             return child.getTop();
1400         }
1401 
1402         @Override
onEdgeDragStarted(int edgeFlags, int pointerId)1403         public void onEdgeDragStarted(int edgeFlags, int pointerId) {
1404             mDragHelper.captureChildView(mSlideableView, pointerId);
1405         }
1406     }
1407 
1408     public static class LayoutParams extends ViewGroup.MarginLayoutParams {
1409         private static final int[] ATTRS = new int[] {
1410             android.R.attr.layout_weight
1411         };
1412 
1413         /**
1414          * The weighted proportion of how much of the leftover space
1415          * this child should consume after measurement.
1416          */
1417         public float weight = 0;
1418 
1419         /**
1420          * True if this pane is the slideable pane in the layout.
1421          */
1422         boolean slideable;
1423 
1424         /**
1425          * True if this view should be drawn dimmed
1426          * when it's been offset from its default position.
1427          */
1428         boolean dimWhenOffset;
1429 
1430         Paint dimPaint;
1431 
LayoutParams()1432         public LayoutParams() {
1433             super(MATCH_PARENT, MATCH_PARENT);
1434         }
1435 
LayoutParams(int width, int height)1436         public LayoutParams(int width, int height) {
1437             super(width, height);
1438         }
1439 
LayoutParams(android.view.ViewGroup.LayoutParams source)1440         public LayoutParams(android.view.ViewGroup.LayoutParams source) {
1441             super(source);
1442         }
1443 
LayoutParams(MarginLayoutParams source)1444         public LayoutParams(MarginLayoutParams source) {
1445             super(source);
1446         }
1447 
LayoutParams(LayoutParams source)1448         public LayoutParams(LayoutParams source) {
1449             super(source);
1450             this.weight = source.weight;
1451         }
1452 
LayoutParams(Context c, AttributeSet attrs)1453         public LayoutParams(Context c, AttributeSet attrs) {
1454             super(c, attrs);
1455 
1456             final TypedArray a = c.obtainStyledAttributes(attrs, ATTRS);
1457             this.weight = a.getFloat(0, 0);
1458             a.recycle();
1459         }
1460 
1461     }
1462 
1463     static class SavedState extends AbsSavedState {
1464         boolean isOpen;
1465 
SavedState(Parcelable superState)1466         SavedState(Parcelable superState) {
1467             super(superState);
1468         }
1469 
SavedState(Parcel in, ClassLoader loader)1470         private SavedState(Parcel in, ClassLoader loader) {
1471             super(in, loader);
1472             isOpen = in.readInt() != 0;
1473         }
1474 
1475         @Override
writeToParcel(Parcel out, int flags)1476         public void writeToParcel(Parcel out, int flags) {
1477             super.writeToParcel(out, flags);
1478             out.writeInt(isOpen ? 1 : 0);
1479         }
1480 
1481         public static final Creator<SavedState> CREATOR = ParcelableCompat.newCreator(
1482                 new ParcelableCompatCreatorCallbacks<SavedState>() {
1483                     @Override
1484                     public SavedState createFromParcel(Parcel in, ClassLoader loader) {
1485                         return new SavedState(in, loader);
1486                     }
1487 
1488                     @Override
1489                     public SavedState[] newArray(int size) {
1490                         return new SavedState[size];
1491                     }
1492                 });
1493     }
1494 
1495     interface SlidingPanelLayoutImpl {
1496         void invalidateChildRegion(SlidingPaneLayout parent, View child);
1497     }
1498 
1499     static class SlidingPanelLayoutImplBase implements SlidingPanelLayoutImpl {
1500         @Override
invalidateChildRegion(SlidingPaneLayout parent, View child)1501         public void invalidateChildRegion(SlidingPaneLayout parent, View child) {
1502             ViewCompat.postInvalidateOnAnimation(parent, child.getLeft(), child.getTop(),
1503                     child.getRight(), child.getBottom());
1504         }
1505     }
1506 
1507     static class SlidingPanelLayoutImplJB extends SlidingPanelLayoutImplBase {
1508         /*
1509          * Private API hacks! Nasty! Bad!
1510          *
1511          * In Jellybean, some optimizations in the hardware UI renderer
1512          * prevent a changed Paint on a View using a hardware layer from having
1513          * the intended effect. This twiddles some internal bits on the view to force
1514          * it to recreate the display list.
1515          */
1516         private Method mGetDisplayList;
1517         private Field mRecreateDisplayList;
1518 
SlidingPanelLayoutImplJB()1519         SlidingPanelLayoutImplJB() {
1520             try {
1521                 mGetDisplayList = View.class.getDeclaredMethod("getDisplayList", (Class[]) null);
1522             } catch (NoSuchMethodException e) {
1523                 Log.e(TAG, "Couldn't fetch getDisplayList method; dimming won't work right.", e);
1524             }
1525             try {
1526                 mRecreateDisplayList = View.class.getDeclaredField("mRecreateDisplayList");
1527                 mRecreateDisplayList.setAccessible(true);
1528             } catch (NoSuchFieldException e) {
1529                 Log.e(TAG, "Couldn't fetch mRecreateDisplayList field; dimming will be slow.", e);
1530             }
1531         }
1532 
1533         @Override
invalidateChildRegion(SlidingPaneLayout parent, View child)1534         public void invalidateChildRegion(SlidingPaneLayout parent, View child) {
1535             if (mGetDisplayList != null && mRecreateDisplayList != null) {
1536                 try {
1537                     mRecreateDisplayList.setBoolean(child, true);
1538                     mGetDisplayList.invoke(child, (Object[]) null);
1539                 } catch (Exception e) {
1540                     Log.e(TAG, "Error refreshing display list state", e);
1541                 }
1542             } else {
1543                 // Slow path. REALLY slow path. Let's hope we don't get here.
1544                 child.invalidate();
1545                 return;
1546             }
1547             super.invalidateChildRegion(parent, child);
1548         }
1549     }
1550 
1551     static class SlidingPanelLayoutImplJBMR1 extends SlidingPanelLayoutImplBase {
1552         @Override
invalidateChildRegion(SlidingPaneLayout parent, View child)1553         public void invalidateChildRegion(SlidingPaneLayout parent, View child) {
1554             ViewCompat.setLayerPaint(child, ((LayoutParams) child.getLayoutParams()).dimPaint);
1555         }
1556     }
1557 
1558     class AccessibilityDelegate extends AccessibilityDelegateCompat {
1559         private final Rect mTmpRect = new Rect();
1560 
1561         @Override
onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info)1562         public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
1563             final AccessibilityNodeInfoCompat superNode = AccessibilityNodeInfoCompat.obtain(info);
1564             super.onInitializeAccessibilityNodeInfo(host, superNode);
1565             copyNodeInfoNoChildren(info, superNode);
1566             superNode.recycle();
1567 
1568             info.setClassName(SlidingPaneLayout.class.getName());
1569             info.setSource(host);
1570 
1571             final ViewParent parent = ViewCompat.getParentForAccessibility(host);
1572             if (parent instanceof View) {
1573                 info.setParent((View) parent);
1574             }
1575 
1576             // This is a best-approximation of addChildrenForAccessibility()
1577             // that accounts for filtering.
1578             final int childCount = getChildCount();
1579             for (int i = 0; i < childCount; i++) {
1580                 final View child = getChildAt(i);
1581                 if (!filter(child) && (child.getVisibility() == View.VISIBLE)) {
1582                     // Force importance to "yes" since we can't read the value.
1583                     ViewCompat.setImportantForAccessibility(
1584                             child, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
1585                     info.addChild(child);
1586                 }
1587             }
1588         }
1589 
1590         @Override
onInitializeAccessibilityEvent(View host, AccessibilityEvent event)1591         public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
1592             super.onInitializeAccessibilityEvent(host, event);
1593 
1594             event.setClassName(SlidingPaneLayout.class.getName());
1595         }
1596 
1597         @Override
onRequestSendAccessibilityEvent(ViewGroup host, View child, AccessibilityEvent event)1598         public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child,
1599                 AccessibilityEvent event) {
1600             if (!filter(child)) {
1601                 return super.onRequestSendAccessibilityEvent(host, child, event);
1602             }
1603             return false;
1604         }
1605 
filter(View child)1606         public boolean filter(View child) {
1607             return isDimmed(child);
1608         }
1609 
1610         /**
1611          * This should really be in AccessibilityNodeInfoCompat, but there unfortunately
1612          * seem to be a few elements that are not easily cloneable using the underlying API.
1613          * Leave it private here as it's not general-purpose useful.
1614          */
copyNodeInfoNoChildren(AccessibilityNodeInfoCompat dest, AccessibilityNodeInfoCompat src)1615         private void copyNodeInfoNoChildren(AccessibilityNodeInfoCompat dest,
1616                 AccessibilityNodeInfoCompat src) {
1617             final Rect rect = mTmpRect;
1618 
1619             src.getBoundsInParent(rect);
1620             dest.setBoundsInParent(rect);
1621 
1622             src.getBoundsInScreen(rect);
1623             dest.setBoundsInScreen(rect);
1624 
1625             dest.setVisibleToUser(src.isVisibleToUser());
1626             dest.setPackageName(src.getPackageName());
1627             dest.setClassName(src.getClassName());
1628             dest.setContentDescription(src.getContentDescription());
1629 
1630             dest.setEnabled(src.isEnabled());
1631             dest.setClickable(src.isClickable());
1632             dest.setFocusable(src.isFocusable());
1633             dest.setFocused(src.isFocused());
1634             dest.setAccessibilityFocused(src.isAccessibilityFocused());
1635             dest.setSelected(src.isSelected());
1636             dest.setLongClickable(src.isLongClickable());
1637 
1638             dest.addAction(src.getActions());
1639 
1640             dest.setMovementGranularities(src.getMovementGranularities());
1641         }
1642     }
1643 
1644     private class DisableLayerRunnable implements Runnable {
1645         final View mChildView;
1646 
DisableLayerRunnable(View childView)1647         DisableLayerRunnable(View childView) {
1648             mChildView = childView;
1649         }
1650 
1651         @Override
run()1652         public void run() {
1653             if (mChildView.getParent() == SlidingPaneLayout.this) {
1654                 ViewCompat.setLayerType(mChildView, ViewCompat.LAYER_TYPE_NONE, null);
1655                 invalidateChildRegion(mChildView);
1656             }
1657             mPostedRunnables.remove(this);
1658         }
1659     }
1660 
isLayoutRtlSupport()1661     private boolean isLayoutRtlSupport() {
1662         return ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL;
1663     }
1664 }
1665