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