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