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