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