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