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