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