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 
     18 package androidx.drawerlayout.widget;
     19 
     20 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
     21 
     22 import android.annotation.SuppressLint;
     23 import android.annotation.TargetApi;
     24 import android.content.Context;
     25 import android.content.res.TypedArray;
     26 import android.graphics.Canvas;
     27 import android.graphics.Matrix;
     28 import android.graphics.Paint;
     29 import android.graphics.PixelFormat;
     30 import android.graphics.Rect;
     31 import android.graphics.drawable.ColorDrawable;
     32 import android.graphics.drawable.Drawable;
     33 import android.os.Build;
     34 import android.os.Parcel;
     35 import android.os.Parcelable;
     36 import android.os.SystemClock;
     37 import android.util.AttributeSet;
     38 import android.view.Gravity;
     39 import android.view.InputDevice;
     40 import android.view.KeyEvent;
     41 import android.view.MotionEvent;
     42 import android.view.View;
     43 import android.view.ViewGroup;
     44 import android.view.ViewParent;
     45 import android.view.WindowInsets;
     46 import android.view.accessibility.AccessibilityEvent;
     47 
     48 import androidx.annotation.ColorInt;
     49 import androidx.annotation.DrawableRes;
     50 import androidx.annotation.IntDef;
     51 import androidx.annotation.NonNull;
     52 import androidx.annotation.Nullable;
     53 import androidx.annotation.RestrictTo;
     54 import androidx.core.content.ContextCompat;
     55 import androidx.core.graphics.drawable.DrawableCompat;
     56 import androidx.core.view.AccessibilityDelegateCompat;
     57 import androidx.core.view.GravityCompat;
     58 import androidx.core.view.ViewCompat;
     59 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
     60 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat;
     61 import androidx.customview.view.AbsSavedState;
     62 import androidx.customview.widget.ViewDragHelper;
     63 
     64 import java.lang.annotation.Retention;
     65 import java.lang.annotation.RetentionPolicy;
     66 import java.util.ArrayList;
     67 import java.util.List;
     68 
     69 /**
     70  * DrawerLayout acts as a top-level container for window content that allows for
     71  * interactive "drawer" views to be pulled out from one or both vertical edges of the window.
     72  *
     73  * <p>Drawer positioning and layout is controlled using the <code>android:layout_gravity</code>
     74  * attribute on child views corresponding to which side of the view you want the drawer
     75  * to emerge from: left or right (or start/end on platform versions that support layout direction.)
     76  * Note that you can only have one drawer view for each vertical edge of the window. If your
     77  * layout configures more than one drawer view per vertical edge of the window, an exception will
     78  * be thrown at runtime.
     79  * </p>
     80  *
     81  * <p>To use a DrawerLayout, position your primary content view as the first child with
     82  * width and height of <code>match_parent</code> and no <code>layout_gravity></code>.
     83  * Add drawers as child views after the main content view and set the <code>layout_gravity</code>
     84  * appropriately. Drawers commonly use <code>match_parent</code> for height with a fixed width.</p>
     85  *
     86  * <p>{@link DrawerListener} can be used to monitor the state and motion of drawer views.
     87  * Avoid performing expensive operations such as layout during animation as it can cause
     88  * stuttering; try to perform expensive operations during the {@link #STATE_IDLE} state.
     89  * {@link SimpleDrawerListener} offers default/no-op implementations of each callback method.</p>
     90  *
     91  * <p>As per the <a href="{@docRoot}design/patterns/navigation-drawer.html">Android Design
     92  * guide</a>, any drawers positioned to the left/start should
     93  * always contain content for navigating around the application, whereas any drawers
     94  * positioned to the right/end should always contain actions to take on the current content.
     95  * This preserves the same navigation left, actions right structure present in the Action Bar
     96  * and elsewhere.</p>
     97  *
     98  * <p>For more information about how to use DrawerLayout, read <a
     99  * href="{@docRoot}training/implementing-navigation/nav-drawer.html">Creating a Navigation
    100  * Drawer</a>.</p>
    101  */
    102 public class DrawerLayout extends ViewGroup {
    103     private static final String TAG = "DrawerLayout";
    104 
    105     private static final int[] THEME_ATTRS = {
    106             android.R.attr.colorPrimaryDark
    107     };
    108 
    109     @IntDef({STATE_IDLE, STATE_DRAGGING, STATE_SETTLING})
    110     @Retention(RetentionPolicy.SOURCE)
    111     private @interface State {}
    112 
    113     /**
    114      * Indicates that any drawers are in an idle, settled state. No animation is in progress.
    115      */
    116     public static final int STATE_IDLE = ViewDragHelper.STATE_IDLE;
    117 
    118     /**
    119      * Indicates that a drawer is currently being dragged by the user.
    120      */
    121     public static final int STATE_DRAGGING = ViewDragHelper.STATE_DRAGGING;
    122 
    123     /**
    124      * Indicates that a drawer is in the process of settling to a final position.
    125      */
    126     public static final int STATE_SETTLING = ViewDragHelper.STATE_SETTLING;
    127 
    128     @IntDef({LOCK_MODE_UNLOCKED, LOCK_MODE_LOCKED_CLOSED, LOCK_MODE_LOCKED_OPEN,
    129             LOCK_MODE_UNDEFINED})
    130     @Retention(RetentionPolicy.SOURCE)
    131     private @interface LockMode {}
    132 
    133     /**
    134      * The drawer is unlocked.
    135      */
    136     public static final int LOCK_MODE_UNLOCKED = 0;
    137 
    138     /**
    139      * The drawer is locked closed. The user may not open it, though
    140      * the app may open it programmatically.
    141      */
    142     public static final int LOCK_MODE_LOCKED_CLOSED = 1;
    143 
    144     /**
    145      * The drawer is locked open. The user may not close it, though the app
    146      * may close it programmatically.
    147      */
    148     public static final int LOCK_MODE_LOCKED_OPEN = 2;
    149 
    150     /**
    151      * The drawer's lock state is reset to default.
    152      */
    153     public static final int LOCK_MODE_UNDEFINED = 3;
    154 
    155     @IntDef(value = {Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END},
    156             flag = true)
    157     @Retention(RetentionPolicy.SOURCE)
    158     private @interface EdgeGravity {}
    159 
    160 
    161     private static final int MIN_DRAWER_MARGIN = 64; // dp
    162     private static final int DRAWER_ELEVATION = 10; //dp
    163 
    164     private static final int DEFAULT_SCRIM_COLOR = 0x99000000;
    165 
    166     /**
    167      * Length of time to delay before peeking the drawer.
    168      */
    169     private static final int PEEK_DELAY = 160; // ms
    170 
    171     /**
    172      * Minimum velocity that will be detected as a fling
    173      */
    174     private static final int MIN_FLING_VELOCITY = 400; // dips per second
    175 
    176     /**
    177      * Experimental feature.
    178      */
    179     private static final boolean ALLOW_EDGE_LOCK = false;
    180 
    181     private static final boolean CHILDREN_DISALLOW_INTERCEPT = true;
    182 
    183     private static final float TOUCH_SLOP_SENSITIVITY = 1.f;
    184 
    185     static final int[] LAYOUT_ATTRS = new int[] {
    186             android.R.attr.layout_gravity
    187     };
    188 
    189     /** Whether we can use NO_HIDE_DESCENDANTS accessibility importance. */
    190     static final boolean CAN_HIDE_DESCENDANTS = Build.VERSION.SDK_INT >= 19;
    191 
    192     /** Whether the drawer shadow comes from setting elevation on the drawer. */
    193     private static final boolean SET_DRAWER_SHADOW_FROM_ELEVATION =
    194             Build.VERSION.SDK_INT >= 21;
    195 
    196     private final ChildAccessibilityDelegate mChildAccessibilityDelegate =
    197             new ChildAccessibilityDelegate();
    198     private float mDrawerElevation;
    199 
    200     private int mMinDrawerMargin;
    201 
    202     private int mScrimColor = DEFAULT_SCRIM_COLOR;
    203     private float mScrimOpacity;
    204     private Paint mScrimPaint = new Paint();
    205 
    206     private final ViewDragHelper mLeftDragger;
    207     private final ViewDragHelper mRightDragger;
    208     private final ViewDragCallback mLeftCallback;
    209     private final ViewDragCallback mRightCallback;
    210     private int mDrawerState;
    211     private boolean mInLayout;
    212     private boolean mFirstLayout = true;
    213 
    214     private @LockMode int mLockModeLeft = LOCK_MODE_UNDEFINED;
    215     private @LockMode int mLockModeRight = LOCK_MODE_UNDEFINED;
    216     private @LockMode int mLockModeStart = LOCK_MODE_UNDEFINED;
    217     private @LockMode int mLockModeEnd = LOCK_MODE_UNDEFINED;
    218 
    219     private boolean mDisallowInterceptRequested;
    220     private boolean mChildrenCanceledTouch;
    221 
    222     private @Nullable DrawerListener mListener;
    223     private List<DrawerListener> mListeners;
    224 
    225     private float mInitialMotionX;
    226     private float mInitialMotionY;
    227 
    228     private Drawable mStatusBarBackground;
    229     private Drawable mShadowLeftResolved;
    230     private Drawable mShadowRightResolved;
    231 
    232     private CharSequence mTitleLeft;
    233     private CharSequence mTitleRight;
    234 
    235     private Object mLastInsets;
    236     private boolean mDrawStatusBarBackground;
    237 
    238     /** Shadow drawables for different gravity */
    239     private Drawable mShadowStart = null;
    240     private Drawable mShadowEnd = null;
    241     private Drawable mShadowLeft = null;
    242     private Drawable mShadowRight = null;
    243 
    244     private final ArrayList<View> mNonDrawerViews;
    245 
    246     private Rect mChildHitRect;
    247     private Matrix mChildInvertedMatrix;
    248 
    249     /**
    250      * Listener for monitoring events about drawers.
    251      */
    252     public interface DrawerListener {
    253         /**
    254          * Called when a drawer's position changes.
    255          * @param drawerView The child view that was moved
    256          * @param slideOffset The new offset of this drawer within its range, from 0-1
    257          */
    258         void onDrawerSlide(@NonNull View drawerView, float slideOffset);
    259 
    260         /**
    261          * Called when a drawer has settled in a completely open state.
    262          * The drawer is interactive at this point.
    263          *
    264          * @param drawerView Drawer view that is now open
    265          */
    266         void onDrawerOpened(@NonNull View drawerView);
    267 
    268         /**
    269          * Called when a drawer has settled in a completely closed state.
    270          *
    271          * @param drawerView Drawer view that is now closed
    272          */
    273         void onDrawerClosed(@NonNull View drawerView);
    274 
    275         /**
    276          * Called when the drawer motion state changes. The new state will
    277          * be one of {@link #STATE_IDLE}, {@link #STATE_DRAGGING} or {@link #STATE_SETTLING}.
    278          *
    279          * @param newState The new drawer motion state
    280          */
    281         void onDrawerStateChanged(@State int newState);
    282     }
    283 
    284     /**
    285      * Stub/no-op implementations of all methods of {@link DrawerListener}.
    286      * Override this if you only care about a few of the available callback methods.
    287      */
    288     public abstract static class SimpleDrawerListener implements DrawerListener {
    289         @Override
    290         public void onDrawerSlide(View drawerView, float slideOffset) {
    291         }
    292 
    293         @Override
    294         public void onDrawerOpened(View drawerView) {
    295         }
    296 
    297         @Override
    298         public void onDrawerClosed(View drawerView) {
    299         }
    300 
    301         @Override
    302         public void onDrawerStateChanged(int newState) {
    303         }
    304     }
    305 
    306     public DrawerLayout(@NonNull Context context) {
    307         this(context, null);
    308     }
    309 
    310     public DrawerLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
    311         this(context, attrs, 0);
    312     }
    313 
    314     public DrawerLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
    315         super(context, attrs, defStyle);
    316         setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
    317         final float density = getResources().getDisplayMetrics().density;
    318         mMinDrawerMargin = (int) (MIN_DRAWER_MARGIN * density + 0.5f);
    319         final float minVel = MIN_FLING_VELOCITY * density;
    320 
    321         mLeftCallback = new ViewDragCallback(Gravity.LEFT);
    322         mRightCallback = new ViewDragCallback(Gravity.RIGHT);
    323 
    324         mLeftDragger = ViewDragHelper.create(this, TOUCH_SLOP_SENSITIVITY, mLeftCallback);
    325         mLeftDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
    326         mLeftDragger.setMinVelocity(minVel);
    327         mLeftCallback.setDragger(mLeftDragger);
    328 
    329         mRightDragger = ViewDragHelper.create(this, TOUCH_SLOP_SENSITIVITY, mRightCallback);
    330         mRightDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_RIGHT);
    331         mRightDragger.setMinVelocity(minVel);
    332         mRightCallback.setDragger(mRightDragger);
    333 
    334         // So that we can catch the back button
    335         setFocusableInTouchMode(true);
    336 
    337         ViewCompat.setImportantForAccessibility(this,
    338                 ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
    339 
    340         ViewCompat.setAccessibilityDelegate(this, new AccessibilityDelegate());
    341         setMotionEventSplittingEnabled(false);
    342         if (ViewCompat.getFitsSystemWindows(this)) {
    343             if (Build.VERSION.SDK_INT >= 21) {
    344                 setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
    345                     @TargetApi(21)
    346                     @Override
    347                     public WindowInsets onApplyWindowInsets(View view, WindowInsets insets) {
    348                         final DrawerLayout drawerLayout = (DrawerLayout) view;
    349                         drawerLayout.setChildInsets(insets, insets.getSystemWindowInsetTop() > 0);
    350                         return insets.consumeSystemWindowInsets();
    351                     }
    352                 });
    353                 setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
    354                         | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
    355                 final TypedArray a = context.obtainStyledAttributes(THEME_ATTRS);
    356                 try {
    357                     mStatusBarBackground = a.getDrawable(0);
    358                 } finally {
    359                     a.recycle();
    360                 }
    361             } else {
    362                 mStatusBarBackground = null;
    363             }
    364         }
    365 
    366         mDrawerElevation = DRAWER_ELEVATION * density;
    367 
    368         mNonDrawerViews = new ArrayList<View>();
    369     }
    370 
    371     /**
    372      * Sets the base elevation of the drawer(s) relative to the parent, in pixels. Note that the
    373      * elevation change is only supported in API 21 and above.
    374      *
    375      * @param elevation The base depth position of the view, in pixels.
    376      */
    377     public void setDrawerElevation(float elevation) {
    378         mDrawerElevation = elevation;
    379         for (int i = 0; i < getChildCount(); i++) {
    380             View child = getChildAt(i);
    381             if (isDrawerView(child)) {
    382                 ViewCompat.setElevation(child, mDrawerElevation);
    383             }
    384         }
    385     }
    386 
    387     /**
    388      * The base elevation of the drawer(s) relative to the parent, in pixels. Note that the
    389      * elevation change is only supported in API 21 and above. For unsupported API levels, 0 will
    390      * be returned as the elevation.
    391      *
    392      * @return The base depth position of the view, in pixels.
    393      */
    394     public float getDrawerElevation() {
    395         if (SET_DRAWER_SHADOW_FROM_ELEVATION) {
    396             return mDrawerElevation;
    397         }
    398         return 0f;
    399     }
    400 
    401     /**
    402      * @hide Internal use only; called to apply window insets when configured
    403      * with fitsSystemWindows="true"
    404      */
    405     @RestrictTo(LIBRARY_GROUP)
    406     public void setChildInsets(Object insets, boolean draw) {
    407         mLastInsets = insets;
    408         mDrawStatusBarBackground = draw;
    409         setWillNotDraw(!draw && getBackground() == null);
    410         requestLayout();
    411     }
    412 
    413     /**
    414      * Set a simple drawable used for the left or right shadow. The drawable provided must have a
    415      * nonzero intrinsic width. For API 21 and above, an elevation will be set on the drawer
    416      * instead of using the provided shadow drawable.
    417      *
    418      * <p>Note that for better support for both left-to-right and right-to-left layout
    419      * directions, a drawable for RTL layout (in additional to the one in LTR layout) can be
    420      * defined with a resource qualifier "ldrtl" for API 17 and above with the gravity
    421      * {@link GravityCompat#START}. Alternatively, for API 23 and above, the drawable can
    422      * auto-mirrored such that the drawable will be mirrored in RTL layout.</p>
    423      *
    424      * @param shadowDrawable Shadow drawable to use at the edge of a drawer
    425      * @param gravity Which drawer the shadow should apply to
    426      */
    427     public void setDrawerShadow(Drawable shadowDrawable, @EdgeGravity int gravity) {
    428         /*
    429          * TODO Someone someday might want to set more complex drawables here.
    430          * They're probably nuts, but we might want to consider registering callbacks,
    431          * setting states, etc. properly.
    432          */
    433         if (SET_DRAWER_SHADOW_FROM_ELEVATION) {
    434             // No op. Drawer shadow will come from setting an elevation on the drawer.
    435             return;
    436         }
    437         if ((gravity & GravityCompat.START) == GravityCompat.START) {
    438             mShadowStart = shadowDrawable;
    439         } else if ((gravity & GravityCompat.END) == GravityCompat.END) {
    440             mShadowEnd = shadowDrawable;
    441         } else if ((gravity & Gravity.LEFT) == Gravity.LEFT) {
    442             mShadowLeft = shadowDrawable;
    443         } else if ((gravity & Gravity.RIGHT) == Gravity.RIGHT) {
    444             mShadowRight = shadowDrawable;
    445         } else {
    446             return;
    447         }
    448         resolveShadowDrawables();
    449         invalidate();
    450     }
    451 
    452     /**
    453      * Set a simple drawable used for the left or right shadow. The drawable provided must have a
    454      * nonzero intrinsic width. For API 21 and above, an elevation will be set on the drawer
    455      * instead of using the provided shadow drawable.
    456      *
    457      * <p>Note that for better support for both left-to-right and right-to-left layout
    458      * directions, a drawable for RTL layout (in additional to the one in LTR layout) can be
    459      * defined with a resource qualifier "ldrtl" for API 17 and above with the gravity
    460      * {@link GravityCompat#START}. Alternatively, for API 23 and above, the drawable can
    461      * auto-mirrored such that the drawable will be mirrored in RTL layout.</p>
    462      *
    463      * @param resId Resource id of a shadow drawable to use at the edge of a drawer
    464      * @param gravity Which drawer the shadow should apply to
    465      */
    466     public void setDrawerShadow(@DrawableRes int resId, @EdgeGravity int gravity) {
    467         setDrawerShadow(ContextCompat.getDrawable(getContext(), resId), gravity);
    468     }
    469 
    470     /**
    471      * Set a color to use for the scrim that obscures primary content while a drawer is open.
    472      *
    473      * @param color Color to use in 0xAARRGGBB format.
    474      */
    475     public void setScrimColor(@ColorInt int color) {
    476         mScrimColor = color;
    477         invalidate();
    478     }
    479 
    480     /**
    481      * Set a listener to be notified of drawer events. Note that this method is deprecated
    482      * and you should use {@link #addDrawerListener(DrawerListener)} to add a listener and
    483      * {@link #removeDrawerListener(DrawerListener)} to remove a registered listener.
    484      *
    485      * @param listener Listener to notify when drawer events occur
    486      * @deprecated Use {@link #addDrawerListener(DrawerListener)}
    487      * @see DrawerListener
    488      * @see #addDrawerListener(DrawerListener)
    489      * @see #removeDrawerListener(DrawerListener)
    490      */
    491     @Deprecated
    492     public void setDrawerListener(DrawerListener listener) {
    493         // The logic in this method emulates what we had before support for multiple
    494         // registered listeners.
    495         if (mListener != null) {
    496             removeDrawerListener(mListener);
    497         }
    498         if (listener != null) {
    499             addDrawerListener(listener);
    500         }
    501         // Update the deprecated field so that we can remove the passed listener the next
    502         // time we're called
    503         mListener = listener;
    504     }
    505 
    506     /**
    507      * Adds the specified listener to the list of listeners that will be notified of drawer events.
    508      *
    509      * @param listener Listener to notify when drawer events occur.
    510      * @see #removeDrawerListener(DrawerListener)
    511      */
    512     public void addDrawerListener(@NonNull DrawerListener listener) {
    513         if (listener == null) {
    514             return;
    515         }
    516         if (mListeners == null) {
    517             mListeners = new ArrayList<DrawerListener>();
    518         }
    519         mListeners.add(listener);
    520     }
    521 
    522     /**
    523      * Removes the specified listener from the list of listeners that will be notified of drawer
    524      * events.
    525      *
    526      * @param listener Listener to remove from being notified of drawer events
    527      * @see #addDrawerListener(DrawerListener)
    528      */
    529     public void removeDrawerListener(@NonNull DrawerListener listener) {
    530         if (listener == null) {
    531             return;
    532         }
    533         if (mListeners == null) {
    534             // This can happen if this method is called before the first call to addDrawerListener
    535             return;
    536         }
    537         mListeners.remove(listener);
    538     }
    539 
    540     /**
    541      * Enable or disable interaction with all drawers.
    542      *
    543      * <p>This allows the application to restrict the user's ability to open or close
    544      * any drawer within this layout. DrawerLayout will still respond to calls to
    545      * {@link #openDrawer(int)}, {@link #closeDrawer(int)} and friends if a drawer is locked.</p>
    546      *
    547      * <p>Locking drawers open or closed will implicitly open or close
    548      * any drawers as appropriate.</p>
    549      *
    550      * @param lockMode The new lock mode for the given drawer. One of {@link #LOCK_MODE_UNLOCKED},
    551      *                 {@link #LOCK_MODE_LOCKED_CLOSED} or {@link #LOCK_MODE_LOCKED_OPEN}.
    552      */
    553     public void setDrawerLockMode(@LockMode int lockMode) {
    554         setDrawerLockMode(lockMode, Gravity.LEFT);
    555         setDrawerLockMode(lockMode, Gravity.RIGHT);
    556     }
    557 
    558     /**
    559      * Enable or disable interaction with the given drawer.
    560      *
    561      * <p>This allows the application to restrict the user's ability to open or close
    562      * the given drawer. DrawerLayout will still respond to calls to {@link #openDrawer(int)},
    563      * {@link #closeDrawer(int)} and friends if a drawer is locked.</p>
    564      *
    565      * <p>Locking a drawer open or closed will implicitly open or close
    566      * that drawer as appropriate.</p>
    567      *
    568      * @param lockMode The new lock mode for the given drawer. One of {@link #LOCK_MODE_UNLOCKED},
    569      *                 {@link #LOCK_MODE_LOCKED_CLOSED} or {@link #LOCK_MODE_LOCKED_OPEN}.
    570      * @param edgeGravity Gravity.LEFT, RIGHT, START or END.
    571      *                    Expresses which drawer to change the mode for.
    572      *
    573      * @see #LOCK_MODE_UNLOCKED
    574      * @see #LOCK_MODE_LOCKED_CLOSED
    575      * @see #LOCK_MODE_LOCKED_OPEN
    576      */
    577     public void setDrawerLockMode(@LockMode int lockMode, @EdgeGravity int edgeGravity) {
    578         final int absGravity = GravityCompat.getAbsoluteGravity(edgeGravity,
    579                 ViewCompat.getLayoutDirection(this));
    580 
    581         switch (edgeGravity) {
    582             case Gravity.LEFT:
    583                 mLockModeLeft = lockMode;
    584                 break;
    585             case Gravity.RIGHT:
    586                 mLockModeRight = lockMode;
    587                 break;
    588             case GravityCompat.START:
    589                 mLockModeStart = lockMode;
    590                 break;
    591             case GravityCompat.END:
    592                 mLockModeEnd = lockMode;
    593                 break;
    594         }
    595 
    596         if (lockMode != LOCK_MODE_UNLOCKED) {
    597             // Cancel interaction in progress
    598             final ViewDragHelper helper = absGravity == Gravity.LEFT ? mLeftDragger : mRightDragger;
    599             helper.cancel();
    600         }
    601         switch (lockMode) {
    602             case LOCK_MODE_LOCKED_OPEN:
    603                 final View toOpen = findDrawerWithGravity(absGravity);
    604                 if (toOpen != null) {
    605                     openDrawer(toOpen);
    606                 }
    607                 break;
    608             case LOCK_MODE_LOCKED_CLOSED:
    609                 final View toClose = findDrawerWithGravity(absGravity);
    610                 if (toClose != null) {
    611                     closeDrawer(toClose);
    612                 }
    613                 break;
    614             // default: do nothing
    615         }
    616     }
    617 
    618     /**
    619      * Enable or disable interaction with the given drawer.
    620      *
    621      * <p>This allows the application to restrict the user's ability to open or close
    622      * the given drawer. DrawerLayout will still respond to calls to {@link #openDrawer(int)},
    623      * {@link #closeDrawer(int)} and friends if a drawer is locked.</p>
    624      *
    625      * <p>Locking a drawer open or closed will implicitly open or close
    626      * that drawer as appropriate.</p>
    627      *
    628      * @param lockMode The new lock mode for the given drawer. One of {@link #LOCK_MODE_UNLOCKED},
    629      *                 {@link #LOCK_MODE_LOCKED_CLOSED} or {@link #LOCK_MODE_LOCKED_OPEN}.
    630      * @param drawerView The drawer view to change the lock mode for
    631      *
    632      * @see #LOCK_MODE_UNLOCKED
    633      * @see #LOCK_MODE_LOCKED_CLOSED
    634      * @see #LOCK_MODE_LOCKED_OPEN
    635      */
    636     public void setDrawerLockMode(@LockMode int lockMode, @NonNull View drawerView) {
    637         if (!isDrawerView(drawerView)) {
    638             throw new IllegalArgumentException("View " + drawerView + " is not a "
    639                     + "drawer with appropriate layout_gravity");
    640         }
    641         final int gravity = ((LayoutParams) drawerView.getLayoutParams()).gravity;
    642         setDrawerLockMode(lockMode, gravity);
    643     }
    644 
    645     /**
    646      * Check the lock mode of the drawer with the given gravity.
    647      *
    648      * @param edgeGravity Gravity of the drawer to check
    649      * @return one of {@link #LOCK_MODE_UNLOCKED}, {@link #LOCK_MODE_LOCKED_CLOSED} or
    650      *         {@link #LOCK_MODE_LOCKED_OPEN}.
    651      */
    652     @LockMode
    653     public int getDrawerLockMode(@EdgeGravity int edgeGravity) {
    654         int layoutDirection = ViewCompat.getLayoutDirection(this);
    655 
    656         switch (edgeGravity) {
    657             case Gravity.LEFT:
    658                 if (mLockModeLeft != LOCK_MODE_UNDEFINED) {
    659                     return mLockModeLeft;
    660                 }
    661                 int leftLockMode = (layoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR)
    662                         ? mLockModeStart : mLockModeEnd;
    663                 if (leftLockMode != LOCK_MODE_UNDEFINED) {
    664                     return leftLockMode;
    665                 }
    666                 break;
    667             case Gravity.RIGHT:
    668                 if (mLockModeRight != LOCK_MODE_UNDEFINED) {
    669                     return mLockModeRight;
    670                 }
    671                 int rightLockMode = (layoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR)
    672                         ? mLockModeEnd : mLockModeStart;
    673                 if (rightLockMode != LOCK_MODE_UNDEFINED) {
    674                     return rightLockMode;
    675                 }
    676                 break;
    677             case GravityCompat.START:
    678                 if (mLockModeStart != LOCK_MODE_UNDEFINED) {
    679                     return mLockModeStart;
    680                 }
    681                 int startLockMode = (layoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR)
    682                         ? mLockModeLeft : mLockModeRight;
    683                 if (startLockMode != LOCK_MODE_UNDEFINED) {
    684                     return startLockMode;
    685                 }
    686                 break;
    687             case GravityCompat.END:
    688                 if (mLockModeEnd != LOCK_MODE_UNDEFINED) {
    689                     return mLockModeEnd;
    690                 }
    691                 int endLockMode = (layoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR)
    692                         ? mLockModeRight : mLockModeLeft;
    693                 if (endLockMode != LOCK_MODE_UNDEFINED) {
    694                     return endLockMode;
    695                 }
    696                 break;
    697         }
    698 
    699         return LOCK_MODE_UNLOCKED;
    700     }
    701 
    702     /**
    703      * Check the lock mode of the given drawer view.
    704      *
    705      * @param drawerView Drawer view to check lock mode
    706      * @return one of {@link #LOCK_MODE_UNLOCKED}, {@link #LOCK_MODE_LOCKED_CLOSED} or
    707      *         {@link #LOCK_MODE_LOCKED_OPEN}.
    708      */
    709     @LockMode
    710     public int getDrawerLockMode(@NonNull View drawerView) {
    711         if (!isDrawerView(drawerView)) {
    712             throw new IllegalArgumentException("View " + drawerView + " is not a drawer");
    713         }
    714         final int drawerGravity = ((LayoutParams) drawerView.getLayoutParams()).gravity;
    715         return getDrawerLockMode(drawerGravity);
    716     }
    717 
    718     /**
    719      * Sets the title of the drawer with the given gravity.
    720      * <p>
    721      * When accessibility is turned on, this is the title that will be used to
    722      * identify the drawer to the active accessibility service.
    723      *
    724      * @param edgeGravity Gravity.LEFT, RIGHT, START or END. Expresses which
    725      *            drawer to set the title for.
    726      * @param title The title for the drawer.
    727      */
    728     public void setDrawerTitle(@EdgeGravity int edgeGravity, @Nullable CharSequence title) {
    729         final int absGravity = GravityCompat.getAbsoluteGravity(
    730                 edgeGravity, ViewCompat.getLayoutDirection(this));
    731         if (absGravity == Gravity.LEFT) {
    732             mTitleLeft = title;
    733         } else if (absGravity == Gravity.RIGHT) {
    734             mTitleRight = title;
    735         }
    736     }
    737 
    738     /**
    739      * Returns the title of the drawer with the given gravity.
    740      *
    741      * @param edgeGravity Gravity.LEFT, RIGHT, START or END. Expresses which
    742      *            drawer to return the title for.
    743      * @return The title of the drawer, or null if none set.
    744      * @see #setDrawerTitle(int, CharSequence)
    745      */
    746     @Nullable
    747     public CharSequence getDrawerTitle(@EdgeGravity int edgeGravity) {
    748         final int absGravity = GravityCompat.getAbsoluteGravity(
    749                 edgeGravity, ViewCompat.getLayoutDirection(this));
    750         if (absGravity == Gravity.LEFT) {
    751             return mTitleLeft;
    752         } else if (absGravity == Gravity.RIGHT) {
    753             return mTitleRight;
    754         }
    755         return null;
    756     }
    757 
    758     /**
    759      * Returns true if x and y coord in DrawerLayout's coordinate space are inside the bounds of the
    760      * child's coordinate space.
    761      */
    762     private boolean isInBoundsOfChild(float x, float y, View child) {
    763         if (mChildHitRect == null) {
    764             mChildHitRect = new Rect();
    765         }
    766         child.getHitRect(mChildHitRect);
    767         return mChildHitRect.contains((int) x, (int) y);
    768     }
    769 
    770     /**
    771      * Copied from ViewGroup#dispatchTransformedGenericPointerEvent(MotionEvent, View) then modified
    772      * in order to make calls that are otherwise too visibility restricted to make.
    773      */
    774     private boolean dispatchTransformedGenericPointerEvent(MotionEvent event, View child) {
    775         boolean handled;
    776         final Matrix childMatrix = child.getMatrix();
    777         if (!childMatrix.isIdentity()) {
    778             MotionEvent transformedEvent = getTransformedMotionEvent(event, child);
    779             handled = child.dispatchGenericMotionEvent(transformedEvent);
    780             transformedEvent.recycle();
    781         } else {
    782             final float offsetX = getScrollX() - child.getLeft();
    783             final float offsetY = getScrollY() - child.getTop();
    784             event.offsetLocation(offsetX, offsetY);
    785             handled = child.dispatchGenericMotionEvent(event);
    786             event.offsetLocation(-offsetX, -offsetY);
    787         }
    788         return handled;
    789     }
    790 
    791     /**
    792      * Copied from ViewGroup#getTransformedMotionEvent(MotionEvent, View) then  modified in order to
    793      * make calls that are otherwise too visibility restricted to make.
    794      */
    795     private MotionEvent getTransformedMotionEvent(MotionEvent event, View child) {
    796         final float offsetX = getScrollX() - child.getLeft();
    797         final float offsetY = getScrollY() - child.getTop();
    798         final MotionEvent transformedEvent = MotionEvent.obtain(event);
    799         transformedEvent.offsetLocation(offsetX, offsetY);
    800         final Matrix childMatrix = child.getMatrix();
    801         if (!childMatrix.isIdentity()) {
    802             if (mChildInvertedMatrix == null) {
    803                 mChildInvertedMatrix = new Matrix();
    804             }
    805             childMatrix.invert(mChildInvertedMatrix);
    806             transformedEvent.transform(mChildInvertedMatrix);
    807         }
    808         return transformedEvent;
    809     }
    810 
    811     /**
    812      * Resolve the shared state of all drawers from the component ViewDragHelpers.
    813      * Should be called whenever a ViewDragHelper's state changes.
    814      */
    815     void updateDrawerState(int forGravity, @State int activeState, View activeDrawer) {
    816         final int leftState = mLeftDragger.getViewDragState();
    817         final int rightState = mRightDragger.getViewDragState();
    818 
    819         final int state;
    820         if (leftState == STATE_DRAGGING || rightState == STATE_DRAGGING) {
    821             state = STATE_DRAGGING;
    822         } else if (leftState == STATE_SETTLING || rightState == STATE_SETTLING) {
    823             state = STATE_SETTLING;
    824         } else {
    825             state = STATE_IDLE;
    826         }
    827 
    828         if (activeDrawer != null && activeState == STATE_IDLE) {
    829             final LayoutParams lp = (LayoutParams) activeDrawer.getLayoutParams();
    830             if (lp.onScreen == 0) {
    831                 dispatchOnDrawerClosed(activeDrawer);
    832             } else if (lp.onScreen == 1) {
    833                 dispatchOnDrawerOpened(activeDrawer);
    834             }
    835         }
    836 
    837         if (state != mDrawerState) {
    838             mDrawerState = state;
    839 
    840             if (mListeners != null) {
    841                 // Notify the listeners. Do that from the end of the list so that if a listener
    842                 // removes itself as the result of being called, it won't mess up with our iteration
    843                 int listenerCount = mListeners.size();
    844                 for (int i = listenerCount - 1; i >= 0; i--) {
    845                     mListeners.get(i).onDrawerStateChanged(state);
    846                 }
    847             }
    848         }
    849     }
    850 
    851     void dispatchOnDrawerClosed(View drawerView) {
    852         final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
    853         if ((lp.openState & LayoutParams.FLAG_IS_OPENED) == 1) {
    854             lp.openState = 0;
    855 
    856             if (mListeners != null) {
    857                 // Notify the listeners. Do that from the end of the list so that if a listener
    858                 // removes itself as the result of being called, it won't mess up with our iteration
    859                 int listenerCount = mListeners.size();
    860                 for (int i = listenerCount - 1; i >= 0; i--) {
    861                     mListeners.get(i).onDrawerClosed(drawerView);
    862                 }
    863             }
    864 
    865             updateChildrenImportantForAccessibility(drawerView, false);
    866 
    867             // Only send WINDOW_STATE_CHANGE if the host has window focus. This
    868             // may change if support for multiple foreground windows (e.g. IME)
    869             // improves.
    870             if (hasWindowFocus()) {
    871                 final View rootView = getRootView();
    872                 if (rootView != null) {
    873                     rootView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
    874                 }
    875             }
    876         }
    877     }
    878 
    879     void dispatchOnDrawerOpened(View drawerView) {
    880         final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
    881         if ((lp.openState & LayoutParams.FLAG_IS_OPENED) == 0) {
    882             lp.openState = LayoutParams.FLAG_IS_OPENED;
    883             if (mListeners != null) {
    884                 // Notify the listeners. Do that from the end of the list so that if a listener
    885                 // removes itself as the result of being called, it won't mess up with our iteration
    886                 int listenerCount = mListeners.size();
    887                 for (int i = listenerCount - 1; i >= 0; i--) {
    888                     mListeners.get(i).onDrawerOpened(drawerView);
    889                 }
    890             }
    891 
    892             updateChildrenImportantForAccessibility(drawerView, true);
    893 
    894             // Only send WINDOW_STATE_CHANGE if the host has window focus.
    895             if (hasWindowFocus()) {
    896                 sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
    897             }
    898         }
    899     }
    900 
    901     private void updateChildrenImportantForAccessibility(View drawerView, boolean isDrawerOpen) {
    902         final int childCount = getChildCount();
    903         for (int i = 0; i < childCount; i++) {
    904             final View child = getChildAt(i);
    905             if ((!isDrawerOpen && !isDrawerView(child)) || (isDrawerOpen && child == drawerView)) {
    906                 // Drawer is closed and this is a content view or this is an
    907                 // open drawer view, so it should be visible.
    908                 ViewCompat.setImportantForAccessibility(child,
    909                         ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
    910             } else {
    911                 ViewCompat.setImportantForAccessibility(child,
    912                         ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
    913             }
    914         }
    915     }
    916 
    917     void dispatchOnDrawerSlide(View drawerView, float slideOffset) {
    918         if (mListeners != null) {
    919             // Notify the listeners. Do that from the end of the list so that if a listener
    920             // removes itself as the result of being called, it won't mess up with our iteration
    921             int listenerCount = mListeners.size();
    922             for (int i = listenerCount - 1; i >= 0; i--) {
    923                 mListeners.get(i).onDrawerSlide(drawerView, slideOffset);
    924             }
    925         }
    926     }
    927 
    928     void setDrawerViewOffset(View drawerView, float slideOffset) {
    929         final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
    930         if (slideOffset == lp.onScreen) {
    931             return;
    932         }
    933 
    934         lp.onScreen = slideOffset;
    935         dispatchOnDrawerSlide(drawerView, slideOffset);
    936     }
    937 
    938     float getDrawerViewOffset(View drawerView) {
    939         return ((LayoutParams) drawerView.getLayoutParams()).onScreen;
    940     }
    941 
    942     /**
    943      * @return the absolute gravity of the child drawerView, resolved according
    944      *         to the current layout direction
    945      */
    946     int getDrawerViewAbsoluteGravity(View drawerView) {
    947         final int gravity = ((LayoutParams) drawerView.getLayoutParams()).gravity;
    948         return GravityCompat.getAbsoluteGravity(gravity, ViewCompat.getLayoutDirection(this));
    949     }
    950 
    951     boolean checkDrawerViewAbsoluteGravity(View drawerView, int checkFor) {
    952         final int absGravity = getDrawerViewAbsoluteGravity(drawerView);
    953         return (absGravity & checkFor) == checkFor;
    954     }
    955 
    956     View findOpenDrawer() {
    957         final int childCount = getChildCount();
    958         for (int i = 0; i < childCount; i++) {
    959             final View child = getChildAt(i);
    960             final LayoutParams childLp = (LayoutParams) child.getLayoutParams();
    961             if ((childLp.openState & LayoutParams.FLAG_IS_OPENED) == 1) {
    962                 return child;
    963             }
    964         }
    965         return null;
    966     }
    967 
    968     void moveDrawerToOffset(View drawerView, float slideOffset) {
    969         final float oldOffset = getDrawerViewOffset(drawerView);
    970         final int width = drawerView.getWidth();
    971         final int oldPos = (int) (width * oldOffset);
    972         final int newPos = (int) (width * slideOffset);
    973         final int dx = newPos - oldPos;
    974 
    975         drawerView.offsetLeftAndRight(
    976                 checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT) ? dx : -dx);
    977         setDrawerViewOffset(drawerView, slideOffset);
    978     }
    979 
    980     /**
    981      * @param gravity the gravity of the child to return. If specified as a
    982      *            relative value, it will be resolved according to the current
    983      *            layout direction.
    984      * @return the drawer with the specified gravity
    985      */
    986     View findDrawerWithGravity(int gravity) {
    987         final int absHorizGravity = GravityCompat.getAbsoluteGravity(
    988                 gravity, ViewCompat.getLayoutDirection(this)) & Gravity.HORIZONTAL_GRAVITY_MASK;
    989         final int childCount = getChildCount();
    990         for (int i = 0; i < childCount; i++) {
    991             final View child = getChildAt(i);
    992             final int childAbsGravity = getDrawerViewAbsoluteGravity(child);
    993             if ((childAbsGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == absHorizGravity) {
    994                 return child;
    995             }
    996         }
    997         return null;
    998     }
    999 
   1000     /**
   1001      * Simple gravity to string - only supports LEFT and RIGHT for debugging output.
   1002      *
   1003      * @param gravity Absolute gravity value
   1004      * @return LEFT or RIGHT as appropriate, or a hex string
   1005      */
   1006     static String gravityToString(@EdgeGravity int gravity) {
   1007         if ((gravity & Gravity.LEFT) == Gravity.LEFT) {
   1008             return "LEFT";
   1009         }
   1010         if ((gravity & Gravity.RIGHT) == Gravity.RIGHT) {
   1011             return "RIGHT";
   1012         }
   1013         return Integer.toHexString(gravity);
   1014     }
   1015 
   1016     @Override
   1017     protected void onDetachedFromWindow() {
   1018         super.onDetachedFromWindow();
   1019         mFirstLayout = true;
   1020     }
   1021 
   1022     @Override
   1023     protected void onAttachedToWindow() {
   1024         super.onAttachedToWindow();
   1025         mFirstLayout = true;
   1026     }
   1027 
   1028     @SuppressLint("WrongConstant")
   1029     @Override
   1030     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
   1031         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
   1032         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
   1033         int widthSize = MeasureSpec.getSize(widthMeasureSpec);
   1034         int heightSize = MeasureSpec.getSize(heightMeasureSpec);
   1035 
   1036         if (widthMode != MeasureSpec.EXACTLY || heightMode != MeasureSpec.EXACTLY) {
   1037             if (isInEditMode()) {
   1038                 // Don't crash the layout editor. Consume all of the space if specified
   1039                 // or pick a magic number from thin air otherwise.
   1040                 // TODO Better communication with tools of this bogus state.
   1041                 // It will crash on a real device.
   1042                 if (widthMode == MeasureSpec.AT_MOST) {
   1043                     widthMode = MeasureSpec.EXACTLY;
   1044                 } else if (widthMode == MeasureSpec.UNSPECIFIED) {
   1045                     widthMode = MeasureSpec.EXACTLY;
   1046                     widthSize = 300;
   1047                 }
   1048                 if (heightMode == MeasureSpec.AT_MOST) {
   1049                     heightMode = MeasureSpec.EXACTLY;
   1050                 } else if (heightMode == MeasureSpec.UNSPECIFIED) {
   1051                     heightMode = MeasureSpec.EXACTLY;
   1052                     heightSize = 300;
   1053                 }
   1054             } else {
   1055                 throw new IllegalArgumentException(
   1056                         "DrawerLayout must be measured with MeasureSpec.EXACTLY.");
   1057             }
   1058         }
   1059 
   1060         setMeasuredDimension(widthSize, heightSize);
   1061 
   1062         final boolean applyInsets = mLastInsets != null && ViewCompat.getFitsSystemWindows(this);
   1063         final int layoutDirection = ViewCompat.getLayoutDirection(this);
   1064 
   1065         // Only one drawer is permitted along each vertical edge (left / right). These two booleans
   1066         // are tracking the presence of the edge drawers.
   1067         boolean hasDrawerOnLeftEdge = false;
   1068         boolean hasDrawerOnRightEdge = false;
   1069         final int childCount = getChildCount();
   1070         for (int i = 0; i < childCount; i++) {
   1071             final View child = getChildAt(i);
   1072 
   1073             if (child.getVisibility() == GONE) {
   1074                 continue;
   1075             }
   1076 
   1077             final LayoutParams lp = (LayoutParams) child.getLayoutParams();
   1078 
   1079             if (applyInsets) {
   1080                 final int cgrav = GravityCompat.getAbsoluteGravity(lp.gravity, layoutDirection);
   1081                 if (ViewCompat.getFitsSystemWindows(child)) {
   1082                     if (Build.VERSION.SDK_INT >= 21) {
   1083                         WindowInsets wi = (WindowInsets) mLastInsets;
   1084                         if (cgrav == Gravity.LEFT) {
   1085                             wi = wi.replaceSystemWindowInsets(wi.getSystemWindowInsetLeft(),
   1086                                     wi.getSystemWindowInsetTop(), 0,
   1087                                     wi.getSystemWindowInsetBottom());
   1088                         } else if (cgrav == Gravity.RIGHT) {
   1089                             wi = wi.replaceSystemWindowInsets(0, wi.getSystemWindowInsetTop(),
   1090                                     wi.getSystemWindowInsetRight(),
   1091                                     wi.getSystemWindowInsetBottom());
   1092                         }
   1093                         child.dispatchApplyWindowInsets(wi);
   1094                     }
   1095                 } else {
   1096                     if (Build.VERSION.SDK_INT >= 21) {
   1097                         WindowInsets wi = (WindowInsets) mLastInsets;
   1098                         if (cgrav == Gravity.LEFT) {
   1099                             wi = wi.replaceSystemWindowInsets(wi.getSystemWindowInsetLeft(),
   1100                                     wi.getSystemWindowInsetTop(), 0,
   1101                                     wi.getSystemWindowInsetBottom());
   1102                         } else if (cgrav == Gravity.RIGHT) {
   1103                             wi = wi.replaceSystemWindowInsets(0, wi.getSystemWindowInsetTop(),
   1104                                     wi.getSystemWindowInsetRight(),
   1105                                     wi.getSystemWindowInsetBottom());
   1106                         }
   1107                         lp.leftMargin = wi.getSystemWindowInsetLeft();
   1108                         lp.topMargin = wi.getSystemWindowInsetTop();
   1109                         lp.rightMargin = wi.getSystemWindowInsetRight();
   1110                         lp.bottomMargin = wi.getSystemWindowInsetBottom();
   1111                     }
   1112                 }
   1113             }
   1114 
   1115             if (isContentView(child)) {
   1116                 // Content views get measured at exactly the layout's size.
   1117                 final int contentWidthSpec = MeasureSpec.makeMeasureSpec(
   1118                         widthSize - lp.leftMargin - lp.rightMargin, MeasureSpec.EXACTLY);
   1119                 final int contentHeightSpec = MeasureSpec.makeMeasureSpec(
   1120                         heightSize - lp.topMargin - lp.bottomMargin, MeasureSpec.EXACTLY);
   1121                 child.measure(contentWidthSpec, contentHeightSpec);
   1122             } else if (isDrawerView(child)) {
   1123                 if (SET_DRAWER_SHADOW_FROM_ELEVATION) {
   1124                     if (ViewCompat.getElevation(child) != mDrawerElevation) {
   1125                         ViewCompat.setElevation(child, mDrawerElevation);
   1126                     }
   1127                 }
   1128                 final @EdgeGravity int childGravity =
   1129                         getDrawerViewAbsoluteGravity(child) & Gravity.HORIZONTAL_GRAVITY_MASK;
   1130                 // Note that the isDrawerView check guarantees that childGravity here is either
   1131                 // LEFT or RIGHT
   1132                 boolean isLeftEdgeDrawer = (childGravity == Gravity.LEFT);
   1133                 if ((isLeftEdgeDrawer && hasDrawerOnLeftEdge)
   1134                         || (!isLeftEdgeDrawer && hasDrawerOnRightEdge)) {
   1135                     throw new IllegalStateException("Child drawer has absolute gravity "
   1136                             + gravityToString(childGravity) + " but this " + TAG + " already has a "
   1137                             + "drawer view along that edge");
   1138                 }
   1139                 if (isLeftEdgeDrawer) {
   1140                     hasDrawerOnLeftEdge = true;
   1141                 } else {
   1142                     hasDrawerOnRightEdge = true;
   1143                 }
   1144                 final int drawerWidthSpec = getChildMeasureSpec(widthMeasureSpec,
   1145                         mMinDrawerMargin + lp.leftMargin + lp.rightMargin,
   1146                         lp.width);
   1147                 final int drawerHeightSpec = getChildMeasureSpec(heightMeasureSpec,
   1148                         lp.topMargin + lp.bottomMargin,
   1149                         lp.height);
   1150                 child.measure(drawerWidthSpec, drawerHeightSpec);
   1151             } else {
   1152                 throw new IllegalStateException("Child " + child + " at index " + i
   1153                         + " does not have a valid layout_gravity - must be Gravity.LEFT, "
   1154                         + "Gravity.RIGHT or Gravity.NO_GRAVITY");
   1155             }
   1156         }
   1157     }
   1158 
   1159     private void resolveShadowDrawables() {
   1160         if (SET_DRAWER_SHADOW_FROM_ELEVATION) {
   1161             return;
   1162         }
   1163         mShadowLeftResolved = resolveLeftShadow();
   1164         mShadowRightResolved = resolveRightShadow();
   1165     }
   1166 
   1167     private Drawable resolveLeftShadow() {
   1168         int layoutDirection = ViewCompat.getLayoutDirection(this);
   1169         // Prefer shadows defined with start/end gravity over left and right.
   1170         if (layoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR) {
   1171             if (mShadowStart != null) {
   1172                 // Correct drawable layout direction, if needed.
   1173                 mirror(mShadowStart, layoutDirection);
   1174                 return mShadowStart;
   1175             }
   1176         } else {
   1177             if (mShadowEnd != null) {
   1178                 // Correct drawable layout direction, if needed.
   1179                 mirror(mShadowEnd, layoutDirection);
   1180                 return mShadowEnd;
   1181             }
   1182         }
   1183         return mShadowLeft;
   1184     }
   1185 
   1186     private Drawable resolveRightShadow() {
   1187         int layoutDirection = ViewCompat.getLayoutDirection(this);
   1188         if (layoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR) {
   1189             if (mShadowEnd != null) {
   1190                 // Correct drawable layout direction, if needed.
   1191                 mirror(mShadowEnd, layoutDirection);
   1192                 return mShadowEnd;
   1193             }
   1194         } else {
   1195             if (mShadowStart != null) {
   1196                 // Correct drawable layout direction, if needed.
   1197                 mirror(mShadowStart, layoutDirection);
   1198                 return mShadowStart;
   1199             }
   1200         }
   1201         return mShadowRight;
   1202     }
   1203 
   1204     /**
   1205      * Change the layout direction of the given drawable.
   1206      * Return true if auto-mirror is supported and drawable's layout direction can be changed.
   1207      * Otherwise, return false.
   1208      */
   1209     private boolean mirror(Drawable drawable, int layoutDirection) {
   1210         if (drawable == null || !DrawableCompat.isAutoMirrored(drawable)) {
   1211             return false;
   1212         }
   1213 
   1214         DrawableCompat.setLayoutDirection(drawable, layoutDirection);
   1215         return true;
   1216     }
   1217 
   1218     @Override
   1219     protected void onLayout(boolean changed, int l, int t, int r, int b) {
   1220         mInLayout = true;
   1221         final int width = r - l;
   1222         final int childCount = getChildCount();
   1223         for (int i = 0; i < childCount; i++) {
   1224             final View child = getChildAt(i);
   1225 
   1226             if (child.getVisibility() == GONE) {
   1227                 continue;
   1228             }
   1229 
   1230             final LayoutParams lp = (LayoutParams) child.getLayoutParams();
   1231 
   1232             if (isContentView(child)) {
   1233                 child.layout(lp.leftMargin, lp.topMargin,
   1234                         lp.leftMargin + child.getMeasuredWidth(),
   1235                         lp.topMargin + child.getMeasuredHeight());
   1236             } else { // Drawer, if it wasn't onMeasure would have thrown an exception.
   1237                 final int childWidth = child.getMeasuredWidth();
   1238                 final int childHeight = child.getMeasuredHeight();
   1239                 int childLeft;
   1240 
   1241                 final float newOffset;
   1242                 if (checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) {
   1243                     childLeft = -childWidth + (int) (childWidth * lp.onScreen);
   1244                     newOffset = (float) (childWidth + childLeft) / childWidth;
   1245                 } else { // Right; onMeasure checked for us.
   1246                     childLeft = width - (int) (childWidth * lp.onScreen);
   1247                     newOffset = (float) (width - childLeft) / childWidth;
   1248                 }
   1249 
   1250                 final boolean changeOffset = newOffset != lp.onScreen;
   1251 
   1252                 final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
   1253 
   1254                 switch (vgrav) {
   1255                     default:
   1256                     case Gravity.TOP: {
   1257                         child.layout(childLeft, lp.topMargin, childLeft + childWidth,
   1258                                 lp.topMargin + childHeight);
   1259                         break;
   1260                     }
   1261 
   1262                     case Gravity.BOTTOM: {
   1263                         final int height = b - t;
   1264                         child.layout(childLeft,
   1265                                 height - lp.bottomMargin - child.getMeasuredHeight(),
   1266                                 childLeft + childWidth,
   1267                                 height - lp.bottomMargin);
   1268                         break;
   1269                     }
   1270 
   1271                     case Gravity.CENTER_VERTICAL: {
   1272                         final int height = b - t;
   1273                         int childTop = (height - childHeight) / 2;
   1274 
   1275                         // Offset for margins. If things don't fit right because of
   1276                         // bad measurement before, oh well.
   1277                         if (childTop < lp.topMargin) {
   1278                             childTop = lp.topMargin;
   1279                         } else if (childTop + childHeight > height - lp.bottomMargin) {
   1280                             childTop = height - lp.bottomMargin - childHeight;
   1281                         }
   1282                         child.layout(childLeft, childTop, childLeft + childWidth,
   1283                                 childTop + childHeight);
   1284                         break;
   1285                     }
   1286                 }
   1287 
   1288                 if (changeOffset) {
   1289                     setDrawerViewOffset(child, newOffset);
   1290                 }
   1291 
   1292                 final int newVisibility = lp.onScreen > 0 ? VISIBLE : INVISIBLE;
   1293                 if (child.getVisibility() != newVisibility) {
   1294                     child.setVisibility(newVisibility);
   1295                 }
   1296             }
   1297         }
   1298         mInLayout = false;
   1299         mFirstLayout = false;
   1300     }
   1301 
   1302     @Override
   1303     public void requestLayout() {
   1304         if (!mInLayout) {
   1305             super.requestLayout();
   1306         }
   1307     }
   1308 
   1309     @Override
   1310     public void computeScroll() {
   1311         final int childCount = getChildCount();
   1312         float scrimOpacity = 0;
   1313         for (int i = 0; i < childCount; i++) {
   1314             final float onscreen = ((LayoutParams) getChildAt(i).getLayoutParams()).onScreen;
   1315             scrimOpacity = Math.max(scrimOpacity, onscreen);
   1316         }
   1317         mScrimOpacity = scrimOpacity;
   1318 
   1319         boolean leftDraggerSettling = mLeftDragger.continueSettling(true);
   1320         boolean rightDraggerSettling = mRightDragger.continueSettling(true);
   1321         if (leftDraggerSettling || rightDraggerSettling) {
   1322             ViewCompat.postInvalidateOnAnimation(this);
   1323         }
   1324     }
   1325 
   1326     private static boolean hasOpaqueBackground(View v) {
   1327         final Drawable bg = v.getBackground();
   1328         if (bg != null) {
   1329             return bg.getOpacity() == PixelFormat.OPAQUE;
   1330         }
   1331         return false;
   1332     }
   1333 
   1334     /**
   1335      * Set a drawable to draw in the insets area for the status bar.
   1336      * Note that this will only be activated if this DrawerLayout fitsSystemWindows.
   1337      *
   1338      * @param bg Background drawable to draw behind the status bar
   1339      */
   1340     public void setStatusBarBackground(@Nullable Drawable bg) {
   1341         mStatusBarBackground = bg;
   1342         invalidate();
   1343     }
   1344 
   1345     /**
   1346      * Gets the drawable used to draw in the insets area for the status bar.
   1347      *
   1348      * @return The status bar background drawable, or null if none set
   1349      */
   1350     @Nullable
   1351     public Drawable getStatusBarBackgroundDrawable() {
   1352         return mStatusBarBackground;
   1353     }
   1354 
   1355     /**
   1356      * Set a drawable to draw in the insets area for the status bar.
   1357      * Note that this will only be activated if this DrawerLayout fitsSystemWindows.
   1358      *
   1359      * @param resId Resource id of a background drawable to draw behind the status bar
   1360      */
   1361     public void setStatusBarBackground(int resId) {
   1362         mStatusBarBackground = resId != 0 ? ContextCompat.getDrawable(getContext(), resId) : null;
   1363         invalidate();
   1364     }
   1365 
   1366     /**
   1367      * Set a drawable to draw in the insets area for the status bar.
   1368      * Note that this will only be activated if this DrawerLayout fitsSystemWindows.
   1369      *
   1370      * @param color Color to use as a background drawable to draw behind the status bar
   1371      *              in 0xAARRGGBB format.
   1372      */
   1373     public void setStatusBarBackgroundColor(@ColorInt int color) {
   1374         mStatusBarBackground = new ColorDrawable(color);
   1375         invalidate();
   1376     }
   1377 
   1378     @Override
   1379     public void onRtlPropertiesChanged(int layoutDirection) {
   1380         resolveShadowDrawables();
   1381     }
   1382 
   1383     @Override
   1384     public void onDraw(Canvas c) {
   1385         super.onDraw(c);
   1386         if (mDrawStatusBarBackground && mStatusBarBackground != null) {
   1387             final int inset;
   1388             if (Build.VERSION.SDK_INT >= 21) {
   1389                 inset = mLastInsets != null
   1390                         ? ((WindowInsets) mLastInsets).getSystemWindowInsetTop() : 0;
   1391             } else {
   1392                 inset = 0;
   1393             }
   1394             if (inset > 0) {
   1395                 mStatusBarBackground.setBounds(0, 0, getWidth(), inset);
   1396                 mStatusBarBackground.draw(c);
   1397             }
   1398         }
   1399     }
   1400 
   1401     @Override
   1402     protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
   1403         final int height = getHeight();
   1404         final boolean drawingContent = isContentView(child);
   1405         int clipLeft = 0, clipRight = getWidth();
   1406 
   1407         final int restoreCount = canvas.save();
   1408         if (drawingContent) {
   1409             final int childCount = getChildCount();
   1410             for (int i = 0; i < childCount; i++) {
   1411                 final View v = getChildAt(i);
   1412                 if (v == child || v.getVisibility() != VISIBLE
   1413                         || !hasOpaqueBackground(v) || !isDrawerView(v)
   1414                         || v.getHeight() < height) {
   1415                     continue;
   1416                 }
   1417 
   1418                 if (checkDrawerViewAbsoluteGravity(v, Gravity.LEFT)) {
   1419                     final int vright = v.getRight();
   1420                     if (vright > clipLeft) clipLeft = vright;
   1421                 } else {
   1422                     final int vleft = v.getLeft();
   1423                     if (vleft < clipRight) clipRight = vleft;
   1424                 }
   1425             }
   1426             canvas.clipRect(clipLeft, 0, clipRight, getHeight());
   1427         }
   1428         final boolean result = super.drawChild(canvas, child, drawingTime);
   1429         canvas.restoreToCount(restoreCount);
   1430 
   1431         if (mScrimOpacity > 0 && drawingContent) {
   1432             final int baseAlpha = (mScrimColor & 0xff000000) >>> 24;
   1433             final int imag = (int) (baseAlpha * mScrimOpacity);
   1434             final int color = imag << 24 | (mScrimColor & 0xffffff);
   1435             mScrimPaint.setColor(color);
   1436 
   1437             canvas.drawRect(clipLeft, 0, clipRight, getHeight(), mScrimPaint);
   1438         } else if (mShadowLeftResolved != null
   1439                 &&  checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) {
   1440             final int shadowWidth = mShadowLeftResolved.getIntrinsicWidth();
   1441             final int childRight = child.getRight();
   1442             final int drawerPeekDistance = mLeftDragger.getEdgeSize();
   1443             final float alpha =
   1444                     Math.max(0, Math.min((float) childRight / drawerPeekDistance, 1.f));
   1445             mShadowLeftResolved.setBounds(childRight, child.getTop(),
   1446                     childRight + shadowWidth, child.getBottom());
   1447             mShadowLeftResolved.setAlpha((int) (0xff * alpha));
   1448             mShadowLeftResolved.draw(canvas);
   1449         } else if (mShadowRightResolved != null
   1450                 &&  checkDrawerViewAbsoluteGravity(child, Gravity.RIGHT)) {
   1451             final int shadowWidth = mShadowRightResolved.getIntrinsicWidth();
   1452             final int childLeft = child.getLeft();
   1453             final int showing = getWidth() - childLeft;
   1454             final int drawerPeekDistance = mRightDragger.getEdgeSize();
   1455             final float alpha =
   1456                     Math.max(0, Math.min((float) showing / drawerPeekDistance, 1.f));
   1457             mShadowRightResolved.setBounds(childLeft - shadowWidth, child.getTop(),
   1458                     childLeft, child.getBottom());
   1459             mShadowRightResolved.setAlpha((int) (0xff * alpha));
   1460             mShadowRightResolved.draw(canvas);
   1461         }
   1462         return result;
   1463     }
   1464 
   1465     boolean isContentView(View child) {
   1466         return ((LayoutParams) child.getLayoutParams()).gravity == Gravity.NO_GRAVITY;
   1467     }
   1468 
   1469     boolean isDrawerView(View child) {
   1470         final int gravity = ((LayoutParams) child.getLayoutParams()).gravity;
   1471         final int absGravity = GravityCompat.getAbsoluteGravity(gravity,
   1472                 ViewCompat.getLayoutDirection(child));
   1473         if ((absGravity & Gravity.LEFT) != 0) {
   1474             // This child is a left-edge drawer
   1475             return true;
   1476         }
   1477         if ((absGravity & Gravity.RIGHT) != 0) {
   1478             // This child is a right-edge drawer
   1479             return true;
   1480         }
   1481         return false;
   1482     }
   1483 
   1484     @SuppressWarnings("ShortCircuitBoolean")
   1485     @Override
   1486     public boolean onInterceptTouchEvent(MotionEvent ev) {
   1487         final int action = ev.getActionMasked();
   1488 
   1489         // "|" used deliberately here; both methods should be invoked.
   1490         final boolean interceptForDrag = mLeftDragger.shouldInterceptTouchEvent(ev)
   1491                 | mRightDragger.shouldInterceptTouchEvent(ev);
   1492 
   1493         boolean interceptForTap = false;
   1494 
   1495         switch (action) {
   1496             case MotionEvent.ACTION_DOWN: {
   1497                 final float x = ev.getX();
   1498                 final float y = ev.getY();
   1499                 mInitialMotionX = x;
   1500                 mInitialMotionY = y;
   1501                 if (mScrimOpacity > 0) {
   1502                     final View child = mLeftDragger.findTopChildUnder((int) x, (int) y);
   1503                     if (child != null && isContentView(child)) {
   1504                         interceptForTap = true;
   1505                     }
   1506                 }
   1507                 mDisallowInterceptRequested = false;
   1508                 mChildrenCanceledTouch = false;
   1509                 break;
   1510             }
   1511 
   1512             case MotionEvent.ACTION_MOVE: {
   1513                 // If we cross the touch slop, don't perform the delayed peek for an edge touch.
   1514                 if (mLeftDragger.checkTouchSlop(ViewDragHelper.DIRECTION_ALL)) {
   1515                     mLeftCallback.removeCallbacks();
   1516                     mRightCallback.removeCallbacks();
   1517                 }
   1518                 break;
   1519             }
   1520 
   1521             case MotionEvent.ACTION_CANCEL:
   1522             case MotionEvent.ACTION_UP: {
   1523                 closeDrawers(true);
   1524                 mDisallowInterceptRequested = false;
   1525                 mChildrenCanceledTouch = false;
   1526             }
   1527         }
   1528 
   1529         return interceptForDrag || interceptForTap || hasPeekingDrawer() || mChildrenCanceledTouch;
   1530     }
   1531 
   1532     @Override
   1533     public boolean dispatchGenericMotionEvent(MotionEvent event) {
   1534 
   1535         // If this is not a pointer event, or if this is an hover exit, or we are not displaying
   1536         // that the content view can't be interacted with, then don't override and do anything
   1537         // special.
   1538         if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) == 0
   1539                 || event.getAction() == MotionEvent.ACTION_HOVER_EXIT
   1540                 || mScrimOpacity <= 0) {
   1541             return super.dispatchGenericMotionEvent(event);
   1542         }
   1543 
   1544         final int childrenCount = getChildCount();
   1545         if (childrenCount != 0) {
   1546             final float x = event.getX();
   1547             final float y = event.getY();
   1548 
   1549             // Walk through children from top to bottom.
   1550             for (int i = childrenCount - 1; i >= 0; i--) {
   1551                 final View child = getChildAt(i);
   1552 
   1553                 // If the event is out of bounds or the child is the content view, don't dispatch
   1554                 // to it.
   1555                 if (!isInBoundsOfChild(x, y, child) || isContentView(child)) {
   1556                     continue;
   1557                 }
   1558 
   1559                 // If a child handles it, return true.
   1560                 if (dispatchTransformedGenericPointerEvent(event, child)) {
   1561                     return true;
   1562                 }
   1563             }
   1564         }
   1565 
   1566         return false;
   1567     }
   1568 
   1569     @Override
   1570     public boolean onTouchEvent(MotionEvent ev) {
   1571         mLeftDragger.processTouchEvent(ev);
   1572         mRightDragger.processTouchEvent(ev);
   1573 
   1574         final int action = ev.getAction();
   1575         boolean wantTouchEvents = true;
   1576 
   1577         switch (action & MotionEvent.ACTION_MASK) {
   1578             case MotionEvent.ACTION_DOWN: {
   1579                 final float x = ev.getX();
   1580                 final float y = ev.getY();
   1581                 mInitialMotionX = x;
   1582                 mInitialMotionY = y;
   1583                 mDisallowInterceptRequested = false;
   1584                 mChildrenCanceledTouch = false;
   1585                 break;
   1586             }
   1587 
   1588             case MotionEvent.ACTION_UP: {
   1589                 final float x = ev.getX();
   1590                 final float y = ev.getY();
   1591                 boolean peekingOnly = true;
   1592                 final View touchedView = mLeftDragger.findTopChildUnder((int) x, (int) y);
   1593                 if (touchedView != null && isContentView(touchedView)) {
   1594                     final float dx = x - mInitialMotionX;
   1595                     final float dy = y - mInitialMotionY;
   1596                     final int slop = mLeftDragger.getTouchSlop();
   1597                     if (dx * dx + dy * dy < slop * slop) {
   1598                         // Taps close a dimmed open drawer but only if it isn't locked open.
   1599                         final View openDrawer = findOpenDrawer();
   1600                         if (openDrawer != null) {
   1601                             peekingOnly = getDrawerLockMode(openDrawer) == LOCK_MODE_LOCKED_OPEN;
   1602                         }
   1603                     }
   1604                 }
   1605                 closeDrawers(peekingOnly);
   1606                 mDisallowInterceptRequested = false;
   1607                 break;
   1608             }
   1609 
   1610             case MotionEvent.ACTION_CANCEL: {
   1611                 closeDrawers(true);
   1612                 mDisallowInterceptRequested = false;
   1613                 mChildrenCanceledTouch = false;
   1614                 break;
   1615             }
   1616         }
   1617 
   1618         return wantTouchEvents;
   1619     }
   1620 
   1621     @Override
   1622     public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
   1623         if (CHILDREN_DISALLOW_INTERCEPT
   1624                 || (!mLeftDragger.isEdgeTouched(ViewDragHelper.EDGE_LEFT)
   1625                         && !mRightDragger.isEdgeTouched(ViewDragHelper.EDGE_RIGHT))) {
   1626             // If we have an edge touch we want to skip this and track it for later instead.
   1627             super.requestDisallowInterceptTouchEvent(disallowIntercept);
   1628         }
   1629         mDisallowInterceptRequested = disallowIntercept;
   1630         if (disallowIntercept) {
   1631             closeDrawers(true);
   1632         }
   1633     }
   1634 
   1635     /**
   1636      * Close all currently open drawer views by animating them out of view.
   1637      */
   1638     public void closeDrawers() {
   1639         closeDrawers(false);
   1640     }
   1641 
   1642     void closeDrawers(boolean peekingOnly) {
   1643         boolean needsInvalidate = false;
   1644         final int childCount = getChildCount();
   1645         for (int i = 0; i < childCount; i++) {
   1646             final View child = getChildAt(i);
   1647             final LayoutParams lp = (LayoutParams) child.getLayoutParams();
   1648 
   1649             if (!isDrawerView(child) || (peekingOnly && !lp.isPeeking)) {
   1650                 continue;
   1651             }
   1652 
   1653             final int childWidth = child.getWidth();
   1654 
   1655             if (checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) {
   1656                 needsInvalidate |= mLeftDragger.smoothSlideViewTo(child,
   1657                         -childWidth, child.getTop());
   1658             } else {
   1659                 needsInvalidate |= mRightDragger.smoothSlideViewTo(child,
   1660                         getWidth(), child.getTop());
   1661             }
   1662 
   1663             lp.isPeeking = false;
   1664         }
   1665 
   1666         mLeftCallback.removeCallbacks();
   1667         mRightCallback.removeCallbacks();
   1668 
   1669         if (needsInvalidate) {
   1670             invalidate();
   1671         }
   1672     }
   1673 
   1674     /**
   1675      * Open the specified drawer view by animating it into view.
   1676      *
   1677      * @param drawerView Drawer view to open
   1678      */
   1679     public void openDrawer(@NonNull View drawerView) {
   1680         openDrawer(drawerView, true);
   1681     }
   1682 
   1683     /**
   1684      * Open the specified drawer view.
   1685      *
   1686      * @param drawerView Drawer view to open
   1687      * @param animate Whether opening of the drawer should be animated.
   1688      */
   1689     public void openDrawer(@NonNull View drawerView, boolean animate) {
   1690         if (!isDrawerView(drawerView)) {
   1691             throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer");
   1692         }
   1693 
   1694         final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
   1695         if (mFirstLayout) {
   1696             lp.onScreen = 1.f;
   1697             lp.openState = LayoutParams.FLAG_IS_OPENED;
   1698 
   1699             updateChildrenImportantForAccessibility(drawerView, true);
   1700         } else if (animate) {
   1701             lp.openState |= LayoutParams.FLAG_IS_OPENING;
   1702 
   1703             if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) {
   1704                 mLeftDragger.smoothSlideViewTo(drawerView, 0, drawerView.getTop());
   1705             } else {
   1706                 mRightDragger.smoothSlideViewTo(drawerView, getWidth() - drawerView.getWidth(),
   1707                         drawerView.getTop());
   1708             }
   1709         } else {
   1710             moveDrawerToOffset(drawerView, 1.f);
   1711             updateDrawerState(lp.gravity, STATE_IDLE, drawerView);
   1712             drawerView.setVisibility(VISIBLE);
   1713         }
   1714         invalidate();
   1715     }
   1716 
   1717     /**
   1718      * Open the specified drawer by animating it out of view.
   1719      *
   1720      * @param gravity Gravity.LEFT to move the left drawer or Gravity.RIGHT for the right.
   1721      *                GravityCompat.START or GravityCompat.END may also be used.
   1722      */
   1723     public void openDrawer(@EdgeGravity int gravity) {
   1724         openDrawer(gravity, true);
   1725     }
   1726 
   1727     /**
   1728      * Open the specified drawer.
   1729      *
   1730      * @param gravity Gravity.LEFT to move the left drawer or Gravity.RIGHT for the right.
   1731      *                GravityCompat.START or GravityCompat.END may also be used.
   1732      * @param animate Whether opening of the drawer should be animated.
   1733      */
   1734     public void openDrawer(@EdgeGravity int gravity, boolean animate) {
   1735         final View drawerView = findDrawerWithGravity(gravity);
   1736         if (drawerView == null) {
   1737             throw new IllegalArgumentException("No drawer view found with gravity "
   1738                     + gravityToString(gravity));
   1739         }
   1740         openDrawer(drawerView, animate);
   1741     }
   1742 
   1743     /**
   1744      * Close the specified drawer view by animating it into view.
   1745      *
   1746      * @param drawerView Drawer view to close
   1747      */
   1748     public void closeDrawer(@NonNull View drawerView) {
   1749         closeDrawer(drawerView, true);
   1750     }
   1751 
   1752     /**
   1753      * Close the specified drawer view.
   1754      *
   1755      * @param drawerView Drawer view to close
   1756      * @param animate Whether closing of the drawer should be animated.
   1757      */
   1758     public void closeDrawer(@NonNull View drawerView, boolean animate) {
   1759         if (!isDrawerView(drawerView)) {
   1760             throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer");
   1761         }
   1762 
   1763         final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
   1764         if (mFirstLayout) {
   1765             lp.onScreen = 0.f;
   1766             lp.openState = 0;
   1767         } else if (animate) {
   1768             lp.openState |= LayoutParams.FLAG_IS_CLOSING;
   1769 
   1770             if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) {
   1771                 mLeftDragger.smoothSlideViewTo(drawerView, -drawerView.getWidth(),
   1772                         drawerView.getTop());
   1773             } else {
   1774                 mRightDragger.smoothSlideViewTo(drawerView, getWidth(), drawerView.getTop());
   1775             }
   1776         } else {
   1777             moveDrawerToOffset(drawerView, 0.f);
   1778             updateDrawerState(lp.gravity, STATE_IDLE, drawerView);
   1779             drawerView.setVisibility(INVISIBLE);
   1780         }
   1781         invalidate();
   1782     }
   1783 
   1784     /**
   1785      * Close the specified drawer by animating it out of view.
   1786      *
   1787      * @param gravity Gravity.LEFT to move the left drawer or Gravity.RIGHT for the right.
   1788      *                GravityCompat.START or GravityCompat.END may also be used.
   1789      */
   1790     public void closeDrawer(@EdgeGravity int gravity) {
   1791         closeDrawer(gravity, true);
   1792     }
   1793 
   1794     /**
   1795      * Close the specified drawer.
   1796      *
   1797      * @param gravity Gravity.LEFT to move the left drawer or Gravity.RIGHT for the right.
   1798      *                GravityCompat.START or GravityCompat.END may also be used.
   1799      * @param animate Whether closing of the drawer should be animated.
   1800      */
   1801     public void closeDrawer(@EdgeGravity int gravity, boolean animate) {
   1802         final View drawerView = findDrawerWithGravity(gravity);
   1803         if (drawerView == null) {
   1804             throw new IllegalArgumentException("No drawer view found with gravity "
   1805                     + gravityToString(gravity));
   1806         }
   1807         closeDrawer(drawerView, animate);
   1808     }
   1809 
   1810     /**
   1811      * Check if the given drawer view is currently in an open state.
   1812      * To be considered "open" the drawer must have settled into its fully
   1813      * visible state. To check for partial visibility use
   1814      * {@link #isDrawerVisible(android.view.View)}.
   1815      *
   1816      * @param drawer Drawer view to check
   1817      * @return true if the given drawer view is in an open state
   1818      * @see #isDrawerVisible(android.view.View)
   1819      */
   1820     public boolean isDrawerOpen(@NonNull View drawer) {
   1821         if (!isDrawerView(drawer)) {
   1822             throw new IllegalArgumentException("View " + drawer + " is not a drawer");
   1823         }
   1824         LayoutParams drawerLp = (LayoutParams) drawer.getLayoutParams();
   1825         return (drawerLp.openState & LayoutParams.FLAG_IS_OPENED) == 1;
   1826     }
   1827 
   1828     /**
   1829      * Check if the given drawer view is currently in an open state.
   1830      * To be considered "open" the drawer must have settled into its fully
   1831      * visible state. If there is no drawer with the given gravity this method
   1832      * will return false.
   1833      *
   1834      * @param drawerGravity Gravity of the drawer to check
   1835      * @return true if the given drawer view is in an open state
   1836      */
   1837     public boolean isDrawerOpen(@EdgeGravity int drawerGravity) {
   1838         final View drawerView = findDrawerWithGravity(drawerGravity);
   1839         if (drawerView != null) {
   1840             return isDrawerOpen(drawerView);
   1841         }
   1842         return false;
   1843     }
   1844 
   1845     /**
   1846      * Check if a given drawer view is currently visible on-screen. The drawer
   1847      * may be only peeking onto the screen, fully extended, or anywhere inbetween.
   1848      *
   1849      * @param drawer Drawer view to check
   1850      * @return true if the given drawer is visible on-screen
   1851      * @see #isDrawerOpen(android.view.View)
   1852      */
   1853     public boolean isDrawerVisible(@NonNull View drawer) {
   1854         if (!isDrawerView(drawer)) {
   1855             throw new IllegalArgumentException("View " + drawer + " is not a drawer");
   1856         }
   1857         return ((LayoutParams) drawer.getLayoutParams()).onScreen > 0;
   1858     }
   1859 
   1860     /**
   1861      * Check if a given drawer view is currently visible on-screen. The drawer
   1862      * may be only peeking onto the screen, fully extended, or anywhere in between.
   1863      * If there is no drawer with the given gravity this method will return false.
   1864      *
   1865      * @param drawerGravity Gravity of the drawer to check
   1866      * @return true if the given drawer is visible on-screen
   1867      */
   1868     public boolean isDrawerVisible(@EdgeGravity int drawerGravity) {
   1869         final View drawerView = findDrawerWithGravity(drawerGravity);
   1870         if (drawerView != null) {
   1871             return isDrawerVisible(drawerView);
   1872         }
   1873         return false;
   1874     }
   1875 
   1876     private boolean hasPeekingDrawer() {
   1877         final int childCount = getChildCount();
   1878         for (int i = 0; i < childCount; i++) {
   1879             final LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
   1880             if (lp.isPeeking) {
   1881                 return true;
   1882             }
   1883         }
   1884         return false;
   1885     }
   1886 
   1887     @Override
   1888     protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
   1889         return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
   1890     }
   1891 
   1892     @Override
   1893     protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
   1894         return p instanceof LayoutParams
   1895                 ? new LayoutParams((LayoutParams) p)
   1896                 : p instanceof ViewGroup.MarginLayoutParams
   1897                 ? new LayoutParams((MarginLayoutParams) p)
   1898                 : new LayoutParams(p);
   1899     }
   1900 
   1901     @Override
   1902     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
   1903         return p instanceof LayoutParams && super.checkLayoutParams(p);
   1904     }
   1905 
   1906     @Override
   1907     public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
   1908         return new LayoutParams(getContext(), attrs);
   1909     }
   1910 
   1911     @Override
   1912     public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
   1913         if (getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS) {
   1914             return;
   1915         }
   1916 
   1917         // Only the views in the open drawers are focusables. Add normal child views when
   1918         // no drawers are opened.
   1919         final int childCount = getChildCount();
   1920         boolean isDrawerOpen = false;
   1921         for (int i = 0; i < childCount; i++) {
   1922             final View child = getChildAt(i);
   1923             if (isDrawerView(child)) {
   1924                 if (isDrawerOpen(child)) {
   1925                     isDrawerOpen = true;
   1926                     child.addFocusables(views, direction, focusableMode);
   1927                 }
   1928             } else {
   1929                 mNonDrawerViews.add(child);
   1930             }
   1931         }
   1932 
   1933         if (!isDrawerOpen) {
   1934             final int nonDrawerViewsCount = mNonDrawerViews.size();
   1935             for (int i = 0; i < nonDrawerViewsCount; ++i) {
   1936                 final View child = mNonDrawerViews.get(i);
   1937                 if (child.getVisibility() == View.VISIBLE) {
   1938                     child.addFocusables(views, direction, focusableMode);
   1939                 }
   1940             }
   1941         }
   1942 
   1943         mNonDrawerViews.clear();
   1944     }
   1945 
   1946     private boolean hasVisibleDrawer() {
   1947         return findVisibleDrawer() != null;
   1948     }
   1949 
   1950     View findVisibleDrawer() {
   1951         final int childCount = getChildCount();
   1952         for (int i = 0; i < childCount; i++) {
   1953             final View child = getChildAt(i);
   1954             if (isDrawerView(child) && isDrawerVisible(child)) {
   1955                 return child;
   1956             }
   1957         }
   1958         return null;
   1959     }
   1960 
   1961     void cancelChildViewTouch() {
   1962         // Cancel child touches
   1963         if (!mChildrenCanceledTouch) {
   1964             final long now = SystemClock.uptimeMillis();
   1965             final MotionEvent cancelEvent = MotionEvent.obtain(now, now,
   1966                     MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
   1967             final int childCount = getChildCount();
   1968             for (int i = 0; i < childCount; i++) {
   1969                 getChildAt(i).dispatchTouchEvent(cancelEvent);
   1970             }
   1971             cancelEvent.recycle();
   1972             mChildrenCanceledTouch = true;
   1973         }
   1974     }
   1975 
   1976     @Override
   1977     public boolean onKeyDown(int keyCode, KeyEvent event) {
   1978         if (keyCode == KeyEvent.KEYCODE_BACK && hasVisibleDrawer()) {
   1979             event.startTracking();
   1980             return true;
   1981         }
   1982         return super.onKeyDown(keyCode, event);
   1983     }
   1984 
   1985     @Override
   1986     public boolean onKeyUp(int keyCode, KeyEvent event) {
   1987         if (keyCode == KeyEvent.KEYCODE_BACK) {
   1988             final View visibleDrawer = findVisibleDrawer();
   1989             if (visibleDrawer != null && getDrawerLockMode(visibleDrawer) == LOCK_MODE_UNLOCKED) {
   1990                 closeDrawers();
   1991             }
   1992             return visibleDrawer != null;
   1993         }
   1994         return super.onKeyUp(keyCode, event);
   1995     }
   1996 
   1997     @Override
   1998     protected void onRestoreInstanceState(Parcelable state) {
   1999         if (!(state instanceof SavedState)) {
   2000             super.onRestoreInstanceState(state);
   2001             return;
   2002         }
   2003 
   2004         final SavedState ss = (SavedState) state;
   2005         super.onRestoreInstanceState(ss.getSuperState());
   2006 
   2007         if (ss.openDrawerGravity != Gravity.NO_GRAVITY) {
   2008             final View toOpen = findDrawerWithGravity(ss.openDrawerGravity);
   2009             if (toOpen != null) {
   2010                 openDrawer(toOpen);
   2011             }
   2012         }
   2013 
   2014         if (ss.lockModeLeft != LOCK_MODE_UNDEFINED) {
   2015             setDrawerLockMode(ss.lockModeLeft, Gravity.LEFT);
   2016         }
   2017         if (ss.lockModeRight != LOCK_MODE_UNDEFINED) {
   2018             setDrawerLockMode(ss.lockModeRight, Gravity.RIGHT);
   2019         }
   2020         if (ss.lockModeStart != LOCK_MODE_UNDEFINED) {
   2021             setDrawerLockMode(ss.lockModeStart, GravityCompat.START);
   2022         }
   2023         if (ss.lockModeEnd != LOCK_MODE_UNDEFINED) {
   2024             setDrawerLockMode(ss.lockModeEnd, GravityCompat.END);
   2025         }
   2026     }
   2027 
   2028     @Override
   2029     protected Parcelable onSaveInstanceState() {
   2030         final Parcelable superState = super.onSaveInstanceState();
   2031         final SavedState ss = new SavedState(superState);
   2032 
   2033         final int childCount = getChildCount();
   2034         for (int i = 0; i < childCount; i++) {
   2035             final View child = getChildAt(i);
   2036             LayoutParams lp = (LayoutParams) child.getLayoutParams();
   2037             // Is the current child fully opened (that is, not closing)?
   2038             boolean isOpenedAndNotClosing = (lp.openState == LayoutParams.FLAG_IS_OPENED);
   2039             // Is the current child opening?
   2040             boolean isClosedAndOpening = (lp.openState == LayoutParams.FLAG_IS_OPENING);
   2041             if (isOpenedAndNotClosing || isClosedAndOpening) {
   2042                 // If one of the conditions above holds, save the child's gravity
   2043                 // so that we open that child during state restore.
   2044                 ss.openDrawerGravity = lp.gravity;
   2045                 break;
   2046             }
   2047         }
   2048 
   2049         ss.lockModeLeft = mLockModeLeft;
   2050         ss.lockModeRight = mLockModeRight;
   2051         ss.lockModeStart = mLockModeStart;
   2052         ss.lockModeEnd = mLockModeEnd;
   2053 
   2054         return ss;
   2055     }
   2056 
   2057     @Override
   2058     public void addView(View child, int index, ViewGroup.LayoutParams params) {
   2059         super.addView(child, index, params);
   2060 
   2061         final View openDrawer = findOpenDrawer();
   2062         if (openDrawer != null || isDrawerView(child)) {
   2063             // A drawer is already open or the new view is a drawer, so the
   2064             // new view should start out hidden.
   2065             ViewCompat.setImportantForAccessibility(child,
   2066                     ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
   2067         } else {
   2068             // Otherwise this is a content view and no drawer is open, so the
   2069             // new view should start out visible.
   2070             ViewCompat.setImportantForAccessibility(child,
   2071                     ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
   2072         }
   2073 
   2074         // We only need a delegate here if the framework doesn't understand
   2075         // NO_HIDE_DESCENDANTS importance.
   2076         if (!CAN_HIDE_DESCENDANTS) {
   2077             ViewCompat.setAccessibilityDelegate(child, mChildAccessibilityDelegate);
   2078         }
   2079     }
   2080 
   2081     static boolean includeChildForAccessibility(View child) {
   2082         // If the child is not important for accessibility we make
   2083         // sure this hides the entire subtree rooted at it as the
   2084         // IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDATS is not
   2085         // supported on older platforms but we want to hide the entire
   2086         // content and not opened drawers if a drawer is opened.
   2087         return ViewCompat.getImportantForAccessibility(child)
   2088                 != ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
   2089                     && ViewCompat.getImportantForAccessibility(child)
   2090                 != ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO;
   2091     }
   2092 
   2093     /**
   2094      * State persisted across instances
   2095      */
   2096     protected static class SavedState extends AbsSavedState {
   2097         int openDrawerGravity = Gravity.NO_GRAVITY;
   2098         @LockMode int lockModeLeft;
   2099         @LockMode int lockModeRight;
   2100         @LockMode int lockModeStart;
   2101         @LockMode int lockModeEnd;
   2102 
   2103         public SavedState(@NonNull Parcel in, @Nullable ClassLoader loader) {
   2104             super(in, loader);
   2105             openDrawerGravity = in.readInt();
   2106             lockModeLeft = in.readInt();
   2107             lockModeRight = in.readInt();
   2108             lockModeStart = in.readInt();
   2109             lockModeEnd = in.readInt();
   2110         }
   2111 
   2112         public SavedState(@NonNull Parcelable superState) {
   2113             super(superState);
   2114         }
   2115 
   2116         @Override
   2117         public void writeToParcel(Parcel dest, int flags) {
   2118             super.writeToParcel(dest, flags);
   2119             dest.writeInt(openDrawerGravity);
   2120             dest.writeInt(lockModeLeft);
   2121             dest.writeInt(lockModeRight);
   2122             dest.writeInt(lockModeStart);
   2123             dest.writeInt(lockModeEnd);
   2124         }
   2125 
   2126         public static final Creator<SavedState> CREATOR = new ClassLoaderCreator<SavedState>() {
   2127             @Override
   2128             public SavedState createFromParcel(Parcel in, ClassLoader loader) {
   2129                 return new SavedState(in, loader);
   2130             }
   2131 
   2132             @Override
   2133             public SavedState createFromParcel(Parcel in) {
   2134                 return new SavedState(in, null);
   2135             }
   2136 
   2137             @Override
   2138             public SavedState[] newArray(int size) {
   2139                 return new SavedState[size];
   2140             }
   2141         };
   2142     }
   2143 
   2144     private class ViewDragCallback extends ViewDragHelper.Callback {
   2145         private final int mAbsGravity;
   2146         private ViewDragHelper mDragger;
   2147 
   2148         private final Runnable mPeekRunnable = new Runnable() {
   2149             @Override public void run() {
   2150                 peekDrawer();
   2151             }
   2152         };
   2153 
   2154         ViewDragCallback(int gravity) {
   2155             mAbsGravity = gravity;
   2156         }
   2157 
   2158         public void setDragger(ViewDragHelper dragger) {
   2159             mDragger = dragger;
   2160         }
   2161 
   2162         public void removeCallbacks() {
   2163             DrawerLayout.this.removeCallbacks(mPeekRunnable);
   2164         }
   2165 
   2166         @Override
   2167         public boolean tryCaptureView(View child, int pointerId) {
   2168             // Only capture views where the gravity matches what we're looking for.
   2169             // This lets us use two ViewDragHelpers, one for each side drawer.
   2170             return isDrawerView(child) && checkDrawerViewAbsoluteGravity(child, mAbsGravity)
   2171                     && getDrawerLockMode(child) == LOCK_MODE_UNLOCKED;
   2172         }
   2173 
   2174         @Override
   2175         public void onViewDragStateChanged(int state) {
   2176             updateDrawerState(mAbsGravity, state, mDragger.getCapturedView());
   2177         }
   2178 
   2179         @Override
   2180         public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
   2181             float offset;
   2182             final int childWidth = changedView.getWidth();
   2183 
   2184             // This reverses the positioning shown in onLayout.
   2185             if (checkDrawerViewAbsoluteGravity(changedView, Gravity.LEFT)) {
   2186                 offset = (float) (childWidth + left) / childWidth;
   2187             } else {
   2188                 final int width = getWidth();
   2189                 offset = (float) (width - left) / childWidth;
   2190             }
   2191             setDrawerViewOffset(changedView, offset);
   2192             changedView.setVisibility(offset == 0 ? INVISIBLE : VISIBLE);
   2193             invalidate();
   2194         }
   2195 
   2196         @Override
   2197         public void onViewCaptured(View capturedChild, int activePointerId) {
   2198             final LayoutParams lp = (LayoutParams) capturedChild.getLayoutParams();
   2199             lp.isPeeking = false;
   2200 
   2201             closeOtherDrawer();
   2202         }
   2203 
   2204         private void closeOtherDrawer() {
   2205             final int otherGrav = mAbsGravity == Gravity.LEFT ? Gravity.RIGHT : Gravity.LEFT;
   2206             final View toClose = findDrawerWithGravity(otherGrav);
   2207             if (toClose != null) {
   2208                 closeDrawer(toClose);
   2209             }
   2210         }
   2211 
   2212         @Override
   2213         public void onViewReleased(View releasedChild, float xvel, float yvel) {
   2214             // Offset is how open the drawer is, therefore left/right values
   2215             // are reversed from one another.
   2216             final float offset = getDrawerViewOffset(releasedChild);
   2217             final int childWidth = releasedChild.getWidth();
   2218 
   2219             int left;
   2220             if (checkDrawerViewAbsoluteGravity(releasedChild, Gravity.LEFT)) {
   2221                 left = xvel > 0 || (xvel == 0 && offset > 0.5f) ? 0 : -childWidth;
   2222             } else {
   2223                 final int width = getWidth();
   2224                 left = xvel < 0 || (xvel == 0 && offset > 0.5f) ? width - childWidth : width;
   2225             }
   2226 
   2227             mDragger.settleCapturedViewAt(left, releasedChild.getTop());
   2228             invalidate();
   2229         }
   2230 
   2231         @Override
   2232         public void onEdgeTouched(int edgeFlags, int pointerId) {
   2233             postDelayed(mPeekRunnable, PEEK_DELAY);
   2234         }
   2235 
   2236         void peekDrawer() {
   2237             final View toCapture;
   2238             final int childLeft;
   2239             final int peekDistance = mDragger.getEdgeSize();
   2240             final boolean leftEdge = mAbsGravity == Gravity.LEFT;
   2241             if (leftEdge) {
   2242                 toCapture = findDrawerWithGravity(Gravity.LEFT);
   2243                 childLeft = (toCapture != null ? -toCapture.getWidth() : 0) + peekDistance;
   2244             } else {
   2245                 toCapture = findDrawerWithGravity(Gravity.RIGHT);
   2246                 childLeft = getWidth() - peekDistance;
   2247             }
   2248             // Only peek if it would mean making the drawer more visible and the drawer isn't locked
   2249             if (toCapture != null && ((leftEdge && toCapture.getLeft() < childLeft)
   2250                     || (!leftEdge && toCapture.getLeft() > childLeft))
   2251                     && getDrawerLockMode(toCapture) == LOCK_MODE_UNLOCKED) {
   2252                 final LayoutParams lp = (LayoutParams) toCapture.getLayoutParams();
   2253                 mDragger.smoothSlideViewTo(toCapture, childLeft, toCapture.getTop());
   2254                 lp.isPeeking = true;
   2255                 invalidate();
   2256 
   2257                 closeOtherDrawer();
   2258 
   2259                 cancelChildViewTouch();
   2260             }
   2261         }
   2262 
   2263         @Override
   2264         public boolean onEdgeLock(int edgeFlags) {
   2265             if (ALLOW_EDGE_LOCK) {
   2266                 final View drawer = findDrawerWithGravity(mAbsGravity);
   2267                 if (drawer != null && !isDrawerOpen(drawer)) {
   2268                     closeDrawer(drawer);
   2269                 }
   2270                 return true;
   2271             }
   2272             return false;
   2273         }
   2274 
   2275         @Override
   2276         public void onEdgeDragStarted(int edgeFlags, int pointerId) {
   2277             final View toCapture;
   2278             if ((edgeFlags & ViewDragHelper.EDGE_LEFT) == ViewDragHelper.EDGE_LEFT) {
   2279                 toCapture = findDrawerWithGravity(Gravity.LEFT);
   2280             } else {
   2281                 toCapture = findDrawerWithGravity(Gravity.RIGHT);
   2282             }
   2283 
   2284             if (toCapture != null && getDrawerLockMode(toCapture) == LOCK_MODE_UNLOCKED) {
   2285                 mDragger.captureChildView(toCapture, pointerId);
   2286             }
   2287         }
   2288 
   2289         @Override
   2290         public int getViewHorizontalDragRange(View child) {
   2291             return isDrawerView(child) ? child.getWidth() : 0;
   2292         }
   2293 
   2294         @Override
   2295         public int clampViewPositionHorizontal(View child, int left, int dx) {
   2296             if (checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) {
   2297                 return Math.max(-child.getWidth(), Math.min(left, 0));
   2298             } else {
   2299                 final int width = getWidth();
   2300                 return Math.max(width - child.getWidth(), Math.min(left, width));
   2301             }
   2302         }
   2303 
   2304         @Override
   2305         public int clampViewPositionVertical(View child, int top, int dy) {
   2306             return child.getTop();
   2307         }
   2308     }
   2309 
   2310     public static class LayoutParams extends ViewGroup.MarginLayoutParams {
   2311         private static final int FLAG_IS_OPENED = 0x1;
   2312         private static final int FLAG_IS_OPENING = 0x2;
   2313         private static final int FLAG_IS_CLOSING = 0x4;
   2314 
   2315         public int gravity = Gravity.NO_GRAVITY;
   2316         float onScreen;
   2317         boolean isPeeking;
   2318         int openState;
   2319 
   2320         public LayoutParams(@NonNull Context c, @Nullable AttributeSet attrs) {
   2321             super(c, attrs);
   2322 
   2323             final TypedArray a = c.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
   2324             this.gravity = a.getInt(0, Gravity.NO_GRAVITY);
   2325             a.recycle();
   2326         }
   2327 
   2328         public LayoutParams(int width, int height) {
   2329             super(width, height);
   2330         }
   2331 
   2332         public LayoutParams(int width, int height, int gravity) {
   2333             this(width, height);
   2334             this.gravity = gravity;
   2335         }
   2336 
   2337         public LayoutParams(@NonNull LayoutParams source) {
   2338             super(source);
   2339             this.gravity = source.gravity;
   2340         }
   2341 
   2342         public LayoutParams(@NonNull ViewGroup.LayoutParams source) {
   2343             super(source);
   2344         }
   2345 
   2346         public LayoutParams(@NonNull ViewGroup.MarginLayoutParams source) {
   2347             super(source);
   2348         }
   2349     }
   2350 
   2351     class AccessibilityDelegate extends AccessibilityDelegateCompat {
   2352         private final Rect mTmpRect = new Rect();
   2353 
   2354         @Override
   2355         public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
   2356             if (CAN_HIDE_DESCENDANTS) {
   2357                 super.onInitializeAccessibilityNodeInfo(host, info);
   2358             } else {
   2359                 // Obtain a node for the host, then manually generate the list
   2360                 // of children to only include non-obscured views.
   2361                 final AccessibilityNodeInfoCompat superNode =
   2362                         AccessibilityNodeInfoCompat.obtain(info);
   2363                 super.onInitializeAccessibilityNodeInfo(host, superNode);
   2364 
   2365                 info.setSource(host);
   2366                 final ViewParent parent = ViewCompat.getParentForAccessibility(host);
   2367                 if (parent instanceof View) {
   2368                     info.setParent((View) parent);
   2369                 }
   2370                 copyNodeInfoNoChildren(info, superNode);
   2371                 superNode.recycle();
   2372 
   2373                 addChildrenForAccessibility(info, (ViewGroup) host);
   2374             }
   2375 
   2376             info.setClassName(DrawerLayout.class.getName());
   2377 
   2378             // This view reports itself as focusable so that it can intercept
   2379             // the back button, but we should prevent this view from reporting
   2380             // itself as focusable to accessibility services.
   2381             info.setFocusable(false);
   2382             info.setFocused(false);
   2383             info.removeAction(AccessibilityActionCompat.ACTION_FOCUS);
   2384             info.removeAction(AccessibilityActionCompat.ACTION_CLEAR_FOCUS);
   2385         }
   2386 
   2387         @Override
   2388         public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
   2389             super.onInitializeAccessibilityEvent(host, event);
   2390 
   2391             event.setClassName(DrawerLayout.class.getName());
   2392         }
   2393 
   2394         @Override
   2395         public boolean dispatchPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
   2396             // Special case to handle window state change events. As far as
   2397             // accessibility services are concerned, state changes from
   2398             // DrawerLayout invalidate the entire contents of the screen (like
   2399             // an Activity or Dialog) and they should announce the title of the
   2400             // new content.
   2401             if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
   2402                 final List<CharSequence> eventText = event.getText();
   2403                 final View visibleDrawer = findVisibleDrawer();
   2404                 if (visibleDrawer != null) {
   2405                     final int edgeGravity = getDrawerViewAbsoluteGravity(visibleDrawer);
   2406                     final CharSequence title = getDrawerTitle(edgeGravity);
   2407                     if (title != null) {
   2408                         eventText.add(title);
   2409                     }
   2410                 }
   2411 
   2412                 return true;
   2413             }
   2414 
   2415             return super.dispatchPopulateAccessibilityEvent(host, event);
   2416         }
   2417 
   2418         @Override
   2419         public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child,
   2420                 AccessibilityEvent event) {
   2421             if (CAN_HIDE_DESCENDANTS || includeChildForAccessibility(child)) {
   2422                 return super.onRequestSendAccessibilityEvent(host, child, event);
   2423             }
   2424             return false;
   2425         }
   2426 
   2427         private void addChildrenForAccessibility(AccessibilityNodeInfoCompat info, ViewGroup v) {
   2428             final int childCount = v.getChildCount();
   2429             for (int i = 0; i < childCount; i++) {
   2430                 final View child = v.getChildAt(i);
   2431                 if (includeChildForAccessibility(child)) {
   2432                     info.addChild(child);
   2433                 }
   2434             }
   2435         }
   2436 
   2437         /**
   2438          * This should really be in AccessibilityNodeInfoCompat, but there unfortunately
   2439          * seem to be a few elements that are not easily cloneable using the underlying API.
   2440          * Leave it private here as it's not general-purpose useful.
   2441          */
   2442         private void copyNodeInfoNoChildren(AccessibilityNodeInfoCompat dest,
   2443                 AccessibilityNodeInfoCompat src) {
   2444             final Rect rect = mTmpRect;
   2445 
   2446             src.getBoundsInParent(rect);
   2447             dest.setBoundsInParent(rect);
   2448 
   2449             src.getBoundsInScreen(rect);
   2450             dest.setBoundsInScreen(rect);
   2451 
   2452             dest.setVisibleToUser(src.isVisibleToUser());
   2453             dest.setPackageName(src.getPackageName());
   2454             dest.setClassName(src.getClassName());
   2455             dest.setContentDescription(src.getContentDescription());
   2456 
   2457             dest.setEnabled(src.isEnabled());
   2458             dest.setClickable(src.isClickable());
   2459             dest.setFocusable(src.isFocusable());
   2460             dest.setFocused(src.isFocused());
   2461             dest.setAccessibilityFocused(src.isAccessibilityFocused());
   2462             dest.setSelected(src.isSelected());
   2463             dest.setLongClickable(src.isLongClickable());
   2464 
   2465             dest.addAction(src.getActions());
   2466         }
   2467     }
   2468 
   2469     static final class ChildAccessibilityDelegate extends AccessibilityDelegateCompat {
   2470         @Override
   2471         public void onInitializeAccessibilityNodeInfo(View child,
   2472                 AccessibilityNodeInfoCompat info) {
   2473             super.onInitializeAccessibilityNodeInfo(child, info);
   2474 
   2475             if (!includeChildForAccessibility(child)) {
   2476                 // If we are ignoring the sub-tree rooted at the child,
   2477                 // break the connection to the rest of the node tree.
   2478                 // For details refer to includeChildForAccessibility.
   2479                 info.setParent(null);
   2480             }
   2481         }
   2482     }
   2483 }
   2484