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