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