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