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