DrawerLayout.java revision 1d26501f0c8e9f3577f651938a03f6b3a1a672c7
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.support.v4.view.GravityCompat;
25import android.support.v4.view.MotionEventCompat;
26import android.support.v4.view.ViewCompat;
27import android.util.AttributeSet;
28import android.util.Log;
29import android.view.Gravity;
30import android.view.MotionEvent;
31import android.view.View;
32import android.view.ViewGroup;
33
34/**
35 * DrawerLayout acts as a top-level container for window content that allows for
36 * interactive "drawer" views to be pulled out from the edge of the window.
37 *
38 * <p>Drawer positioning and layout is controlled using the <code>android:layout_gravity</code>
39 * attribute on child views corresponding to </p>
40 *
41 * <p>As per the Android Design guide, any drawers positioned to the left/start should
42 * always contain content for navigating around the application, whereas any drawers
43 * positioned to the right/end should always contain actions to take on the current content.
44 * This preserves the same navigation left, actions right structure present in the Action Bar
45 * and elsewhere.</p>
46 */
47public class DrawerLayout extends ViewGroup {
48    private static final String TAG = "DrawerLayout";
49
50    private static final int INVALID_POINTER = -1;
51
52    /**
53     * Indicates that any drawers are in an idle, settled state. No animation is in progress.
54     */
55    public static final int STATE_IDLE = ViewDragHelper.STATE_IDLE;
56
57    /**
58     * Indicates that a drawer is currently being dragged by the user.
59     */
60    public static final int STATE_DRAGGING = ViewDragHelper.STATE_DRAGGING;
61
62    /**
63     * Indicates that a drawer is in the process of settling to a final position.
64     */
65    public static final int STATE_SETTLING = ViewDragHelper.STATE_SETTLING;
66
67    private static final int MIN_DRAWER_MARGIN = 64; // dp
68
69    private static final int DRAWER_PEEK_DISTANCE = 16; // dp
70
71    private static final int DEFAULT_SCRIM_COLOR = 0x99000000;
72
73    private static final int[] LAYOUT_ATTRS = new int[] {
74            android.R.attr.layout_gravity
75    };
76
77    private int mMinDrawerMargin;
78    private int mDrawerPeekDistance;
79
80    private int mScrimColor = DEFAULT_SCRIM_COLOR;
81    private float mScrimOpacity;
82    private Paint mScrimPaint = new Paint();
83
84    private final ViewDragHelper mLeftDragger;
85    private final ViewDragHelper mRightDragger;
86    private int mDrawerState;
87
88    private DrawerListener mListener;
89
90    private float mInitialMotionX;
91    private float mInitialMotionY;
92
93    /**
94     * Listener for monitoring events about drawers.
95     */
96    public interface DrawerListener {
97        /**
98         * Called when a drawer's position changes.
99         * @param drawerView The child view that was moved
100         * @param slideOffset The new offset of this drawer within its range, from 0-1
101         */
102        public void onDrawerSlide(View drawerView, float slideOffset);
103
104        /**
105         * Called when a drawer has settled in a completely open state.
106         * The drawer is interactive at this point.
107         *
108         * @param drawerView Drawer view that is now open
109         */
110        public void onDrawerOpened(View drawerView);
111
112        /**
113         * Called when a drawer has settled in a completely closed state.
114         *
115         * @param drawerView Drawer view that is now closed
116         */
117        public void onDrawerClosed(View drawerView);
118
119        /**
120         * Called when the drawer motion state changes. The new state will
121         * be one of {@link #STATE_IDLE}, {@link #STATE_DRAGGING} or {@link #STATE_SETTLING}.
122         *
123         * @param newState The new drawer motion state
124         */
125        public void onDrawerStateChanged(int newState);
126    }
127
128    public DrawerLayout(Context context) {
129        this(context, null);
130    }
131
132    public DrawerLayout(Context context, AttributeSet attrs) {
133        this(context, attrs, 0);
134    }
135
136    public DrawerLayout(Context context, AttributeSet attrs, int defStyle) {
137        super(context, attrs, defStyle);
138
139        final float density = getResources().getDisplayMetrics().density;
140        mMinDrawerMargin = (int) (MIN_DRAWER_MARGIN * density + 0.5f);
141        mDrawerPeekDistance = (int) (DRAWER_PEEK_DISTANCE * density + 0.5f);
142
143        final ViewDragCallback leftCallback = new ViewDragCallback(Gravity.LEFT);
144        final ViewDragCallback rightCallback = new ViewDragCallback(Gravity.RIGHT);
145
146        mLeftDragger = ViewDragHelper.create(this, 0.5f, leftCallback);
147        mLeftDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
148        leftCallback.setDragger(mLeftDragger);
149
150        mRightDragger = ViewDragHelper.create(this, 0.5f, rightCallback);
151        mRightDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_RIGHT);
152        rightCallback.setDragger(mRightDragger);
153    }
154
155    /**
156     * Set a listener to be notified of drawer events.
157     *
158     * @param listener Listener to notify when drawer events occur
159     * @see DrawerListener
160     */
161    public void setDrawerListener(DrawerListener listener) {
162        mListener = listener;
163    }
164
165    /**
166     * Resolve the shared state of all drawers from the component ViewDragHelpers.
167     * Should be called whenever a ViewDragHelper's state changes.
168     */
169    void updateDrawerState(int forGravity) {
170        final int leftState = mLeftDragger.getViewDragState();
171        final int rightState = mRightDragger.getViewDragState();
172
173        final int state;
174        if (leftState == STATE_DRAGGING || rightState == STATE_DRAGGING) {
175            state = STATE_DRAGGING;
176        } else if (leftState == STATE_SETTLING || rightState == STATE_SETTLING) {
177            state = STATE_SETTLING;
178        } else {
179            state = STATE_IDLE;
180        }
181
182        if (state != mDrawerState) {
183            mDrawerState = state;
184            final View activeDrawer = findDrawerWithGravity(forGravity);
185            if (state == STATE_IDLE && activeDrawer != null) {
186                final LayoutParams lp = (LayoutParams) activeDrawer.getLayoutParams();
187                if (lp.onscreen == 0) {
188                    dispatchOnDrawerClosed(activeDrawer);
189                } else if (lp.onscreen == 1) {
190                    dispatchOnDrawerOpened(activeDrawer);
191                }
192            }
193            if (mListener != null) {
194                mListener.onDrawerStateChanged(state);
195            }
196        }
197    }
198
199    void dispatchOnDrawerClosed(View drawerView) {
200        final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
201        if (lp.knownOpen) {
202            lp.knownOpen = false;
203            if (mListener != null) {
204                mListener.onDrawerClosed(drawerView);
205            }
206        }
207    }
208
209    void dispatchOnDrawerOpened(View drawerView) {
210        final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
211        if (!lp.knownOpen) {
212            lp.knownOpen = true;
213            if (mListener != null) {
214                mListener.onDrawerOpened(drawerView);
215            }
216        }
217    }
218
219    void dispatchOnDrawerSlide(View drawerView, float slideOffset) {
220        if (mListener != null) {
221            mListener.onDrawerSlide(drawerView, slideOffset);
222        }
223    }
224
225    void setDrawerViewOffset(View drawerView, float slideOffset) {
226        final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
227        if (slideOffset == lp.onscreen) {
228            return;
229        }
230
231        lp.onscreen = slideOffset;
232        dispatchOnDrawerSlide(drawerView, slideOffset);
233    }
234
235    float getDrawerViewOffset(View drawerView) {
236        return ((LayoutParams) drawerView.getLayoutParams()).onscreen;
237    }
238
239    int getDrawerViewGravity(View drawerView) {
240        final int gravity = ((LayoutParams) drawerView.getLayoutParams()).gravity;
241        return GravityCompat.getAbsoluteGravity(gravity, ViewCompat.getLayoutDirection(drawerView));
242    }
243
244    boolean checkDrawerViewGravity(View drawerView, int checkFor) {
245        final int absGrav = getDrawerViewGravity(drawerView);
246        return (absGrav & checkFor) == checkFor;
247    }
248
249    void moveDrawerToOffset(View drawerView, float slideOffset) {
250        final float oldOffset = getDrawerViewOffset(drawerView);
251        final int width = drawerView.getWidth();
252        final int oldPos = (int) (width * oldOffset);
253        final int newPos = (int) (width * slideOffset);
254        final int dx = newPos - oldPos;
255
256        drawerView.offsetLeftAndRight(checkDrawerViewGravity(drawerView, Gravity.LEFT) ? dx : -dx);
257        setDrawerViewOffset(drawerView, slideOffset);
258    }
259
260    View findDrawerWithGravity(int gravity) {
261        final int childCount = getChildCount();
262        for (int i = 0; i < childCount; i++) {
263            final View child = getChildAt(i);
264            final int childGravity = getDrawerViewGravity(child);
265            if ((childGravity & Gravity.HORIZONTAL_GRAVITY_MASK) ==
266                    (gravity & Gravity.HORIZONTAL_GRAVITY_MASK)) {
267                return child;
268            }
269        }
270        return null;
271    }
272
273    /**
274     * Simple gravity to string - only supports LEFT and RIGHT for debugging output.
275     *
276     * @param gravity Absolute gravity value
277     * @return LEFT or RIGHT as appropriate, or a hex string
278     */
279    static String gravityToString(int gravity) {
280        if ((gravity & Gravity.LEFT) == Gravity.LEFT) {
281            return "LEFT";
282        }
283        if ((gravity & Gravity.RIGHT) == Gravity.RIGHT) {
284            return "RIGHT";
285        }
286        return Integer.toHexString(gravity);
287    }
288
289    @Override
290    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
291        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
292        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
293        final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
294        final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
295
296        if (widthMode != MeasureSpec.EXACTLY || heightMode != MeasureSpec.EXACTLY) {
297            throw new IllegalArgumentException(
298                    "DrawerLayout must be measured with MeasureSpec.EXACTLY.");
299        }
300
301        setMeasuredDimension(widthSize, heightSize);
302
303        // Gravity value for each drawer we've seen. Only one of each permitted.
304        int foundDrawers = 0;
305        final int childCount = getChildCount();
306        for (int i = 0; i < childCount; i++) {
307            final View child = getChildAt(i);
308
309            if (child.getVisibility() == GONE) {
310                continue;
311            }
312
313            if (isContentView(child)) {
314                // Content views get measured at exactly the layout's size.
315                child.measure(widthMeasureSpec, heightMeasureSpec);
316            } else if (isDrawerView(child)) {
317                final int childGravity =
318                        getDrawerViewGravity(child) & Gravity.HORIZONTAL_GRAVITY_MASK;
319                if ((foundDrawers & childGravity) != 0) {
320                    throw new IllegalStateException("Child drawer has absolute gravity " +
321                            gravityToString(childGravity) + " but this " + TAG + " already has a " +
322                            "drawer view along that edge");
323                }
324                final int drawerWidthSpec = getChildMeasureSpec(widthMeasureSpec, mMinDrawerMargin,
325                        child.getLayoutParams().width);
326                child.measure(drawerWidthSpec, heightMeasureSpec);
327            } else {
328                throw new IllegalStateException("Child " + child + " at index " + i +
329                        " does not have a valid layout_gravity - must be Gravity.LEFT, " +
330                        "Gravity.RIGHT or Gravity.NO_GRAVITY");
331            }
332        }
333    }
334
335    @Override
336    protected void onLayout(boolean changed, int l, int t, int r, int b) {
337        final int childCount = getChildCount();
338        for (int i = 0; i < childCount; i++) {
339            final View child = getChildAt(i);
340
341            if (child.getVisibility() == GONE) {
342                continue;
343            }
344
345            if (isContentView(child)) {
346                child.layout(0, 0, child.getMeasuredWidth(), child.getMeasuredHeight());
347            } else { // Drawer, if it wasn't onMeasure would have thrown an exception.
348                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
349
350                final int childWidth = child.getMeasuredWidth();
351                int childLeft;
352
353                if (checkDrawerViewGravity(child, Gravity.LEFT)) {
354                    childLeft = -childWidth + (int) (childWidth * lp.onscreen);
355                } else { // Right; onMeasure checked for us.
356                    childLeft = r - l - (int) (childWidth * lp.onscreen);
357                }
358
359                child.layout(childLeft, 0, childLeft + childWidth, child.getMeasuredHeight());
360            }
361        }
362    }
363
364    @Override
365    public void computeScroll() {
366        final int childCount = getChildCount();
367        float scrimOpacity = 0;
368        for (int i = 0; i < childCount; i++) {
369            final float onscreen = ((LayoutParams) getChildAt(i).getLayoutParams()).onscreen;
370            scrimOpacity = Math.max(scrimOpacity, onscreen);
371        }
372        mScrimOpacity = scrimOpacity;
373
374        // "|" used on purpose; both need to run.
375        if (mLeftDragger.continueSettling(true) | mRightDragger.continueSettling(true)) {
376            ViewCompat.postInvalidateOnAnimation(this);
377        }
378    }
379
380    @Override
381    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
382        final int restoreCount = canvas.save();
383        final boolean result = super.drawChild(canvas, child, drawingTime);
384        canvas.restoreToCount(restoreCount);
385        if (mScrimOpacity > 0 && isContentView(child)) {
386            final int baseAlpha = (mScrimColor & 0xff000000) >>> 24;
387            final int imag = (int) (baseAlpha * mScrimOpacity);
388            final int color = imag << 24 | (mScrimColor & 0xffffff);
389            mScrimPaint.setColor(color);
390
391            canvas.drawRect(0, 0, getWidth(), getHeight(), mScrimPaint);
392        }
393        return result;
394    }
395
396    boolean isContentView(View child) {
397        return ((LayoutParams) child.getLayoutParams()).gravity == Gravity.NO_GRAVITY;
398    }
399
400    boolean isDrawerView(View child) {
401        final int gravity = ((LayoutParams) child.getLayoutParams()).gravity;
402        final int absGravity = GravityCompat.getAbsoluteGravity(gravity,
403                ViewCompat.getLayoutDirection(child));
404        return (absGravity & (Gravity.LEFT | Gravity.RIGHT)) != 0;
405    }
406
407    @Override
408    public boolean onInterceptTouchEvent(MotionEvent ev) {
409        final int action = MotionEventCompat.getActionMasked(ev);
410
411        // "|" used deliberately here; both methods should be invoked.
412        final boolean interceptForDrag = mLeftDragger.shouldInterceptTouchEvent(ev) |
413                mRightDragger.shouldInterceptTouchEvent(ev);
414
415        boolean interceptForTap = false;
416
417        switch (action) {
418            case MotionEvent.ACTION_DOWN: {
419                final float x = ev.getX();
420                final float y = ev.getY();
421                mInitialMotionX = x;
422                mInitialMotionY = y;
423                if (mScrimOpacity > 0 &&
424                        isContentView(mLeftDragger.findTopChildUnder((int) x, (int) y))) {
425                    interceptForTap = true;
426                }
427                break;
428            }
429
430            case MotionEvent.ACTION_CANCEL:
431            case MotionEvent.ACTION_UP: {
432                closeDrawers(true);
433            }
434        }
435        return interceptForDrag || interceptForTap;
436    }
437
438    @Override
439    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
440        super.requestDisallowInterceptTouchEvent(disallowIntercept);
441        if (disallowIntercept) {
442            closeDrawers(true);
443        }
444    }
445
446    @Override
447    public boolean onTouchEvent(MotionEvent ev) {
448        mLeftDragger.processTouchEvent(ev);
449        mRightDragger.processTouchEvent(ev);
450
451        final int action = ev.getAction();
452        boolean wantTouchEvents = true;
453
454        switch (action & MotionEventCompat.ACTION_MASK) {
455            case MotionEvent.ACTION_DOWN: {
456                final float x = ev.getX();
457                final float y = ev.getY();
458                mInitialMotionX = x;
459                mInitialMotionY = y;
460                break;
461            }
462
463            case MotionEvent.ACTION_UP: {
464                final float x = ev.getX();
465                final float y = ev.getY();
466                boolean peekingOnly = true;
467                if (isContentView(mLeftDragger.findTopChildUnder((int) x, (int) y))) {
468                    final float dx = x - mInitialMotionX;
469                    final float dy = y - mInitialMotionY;
470                    final int slop = mLeftDragger.getTouchSlop();
471                    if (dx * dx + dy * dy < slop * slop) {
472                        // Taps close a dimmed open pane.
473                        peekingOnly = false;
474                    }
475                }
476                closeDrawers(peekingOnly);
477                break;
478            }
479
480            case MotionEvent.ACTION_CANCEL: {
481                closeDrawers(true);
482                break;
483            }
484        }
485
486        return wantTouchEvents;
487    }
488
489    /**
490     * Close all currently open drawer views by animating them out of view.
491     */
492    public void closeDrawers() {
493        closeDrawers(false);
494    }
495
496    void closeDrawers(boolean peekingOnly) {
497        boolean needsInvalidate = false;
498        final int childCount = getChildCount();
499        for (int i = 0; i < childCount; i++) {
500            final View child = getChildAt(i);
501            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
502
503            if (!isDrawerView(child) || (peekingOnly && !lp.isPeeking)) {
504                continue;
505            }
506
507            final int childWidth = child.getWidth();
508
509            if (checkDrawerViewGravity(child, Gravity.LEFT)) {
510                needsInvalidate |= mLeftDragger.smoothSlideViewTo(child,
511                        -childWidth, child.getTop());
512            } else {
513                needsInvalidate |= mRightDragger.smoothSlideViewTo(child,
514                        getWidth(), child.getTop());
515            }
516
517            lp.isPeeking = false;
518        }
519
520        if (needsInvalidate) {
521            invalidate();
522        }
523    }
524
525    /**
526     * Open the specified drawer view by animating it into view.
527     *
528     * @param drawerView Drawer view to open
529     */
530    public void openDrawer(View drawerView) {
531        if (!isDrawerView(drawerView)) {
532            throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer");
533        }
534
535        if (checkDrawerViewGravity(drawerView, Gravity.LEFT)) {
536            mLeftDragger.smoothSlideViewTo(drawerView, 0, drawerView.getTop());
537        } else {
538            mRightDragger.smoothSlideViewTo(drawerView, getWidth() - drawerView.getWidth(),
539                    drawerView.getTop());
540        }
541        invalidate();
542    }
543
544    /**
545     * Open the specified drawer by animating it out of view.
546     *
547     * @param gravity Gravity.LEFT to move the left drawer or Gravity.RIGHT for the right.
548     *                GravityCompat.START or GravityCompat.END may also be used.
549     */
550    public void openDrawer(int gravity) {
551        final int absGravity = GravityCompat.getAbsoluteGravity(gravity,
552                ViewCompat.getLayoutDirection(this));
553        final View drawerView = findDrawerWithGravity(absGravity);
554
555        if (drawerView == null) {
556            throw new IllegalArgumentException("No drawer view found with absolute gravity " +
557                    gravityToString(absGravity));
558        }
559        openDrawer(drawerView);
560    }
561
562    /**
563     * Close the specified drawer view by animating it into view.
564     *
565     * @param drawerView Drawer view to close
566     */
567    public void closeDrawer(View drawerView) {
568        if (!isDrawerView(drawerView)) {
569            throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer");
570        }
571
572        if (checkDrawerViewGravity(drawerView, Gravity.LEFT)) {
573            mLeftDragger.smoothSlideViewTo(drawerView, -drawerView.getWidth(), drawerView.getTop());
574        } else {
575            mRightDragger.smoothSlideViewTo(drawerView, getWidth(), drawerView.getTop());
576        }
577        invalidate();
578    }
579
580    /**
581     * Close the specified drawer by animating it out of view.
582     *
583     * @param gravity Gravity.LEFT to move the left drawer or Gravity.RIGHT for the right.
584     *                GravityCompat.START or GravityCompat.END may also be used.
585     */
586    public void closeDrawer(int gravity) {
587        final int absGravity = GravityCompat.getAbsoluteGravity(gravity,
588                ViewCompat.getLayoutDirection(this));
589        final View drawerView = findDrawerWithGravity(absGravity);
590
591        if (drawerView == null) {
592            throw new IllegalArgumentException("No drawer view found with absolute gravity " +
593                    gravityToString(absGravity));
594        }
595        closeDrawer(drawerView);
596    }
597
598    @Override
599    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
600        return new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
601    }
602
603    @Override
604    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
605        return p instanceof LayoutParams
606                ? new LayoutParams((LayoutParams) p)
607                : new LayoutParams(p);
608    }
609
610    @Override
611    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
612        return p instanceof LayoutParams && super.checkLayoutParams(p);
613    }
614
615    @Override
616    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
617        return new LayoutParams(getContext(), attrs);
618    }
619
620    private class ViewDragCallback extends ViewDragHelper.Callback {
621
622        private final int mGravity;
623        private ViewDragHelper mDragger;
624
625        public ViewDragCallback(int gravity) {
626            mGravity = gravity;
627        }
628
629        public void setDragger(ViewDragHelper dragger) {
630            mDragger = dragger;
631        }
632
633        @Override
634        public boolean tryCaptureView(View child, int pointerId) {
635            // Only capture views where the gravity matches what we're looking for.
636            // This lets us use two ViewDragHelpers, one for each side drawer.
637            return isDrawerView(child) && checkDrawerViewGravity(child, mGravity);
638        }
639
640        @Override
641        public void onViewDragStateChanged(int state) {
642            updateDrawerState(mGravity);
643        }
644
645        @Override
646        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
647            float offset;
648            final int childWidth = changedView.getWidth();
649
650            // This reverses the positioning shown in onLayout.
651            if (checkDrawerViewGravity(changedView, Gravity.LEFT)) {
652                offset = (float) (childWidth + left) / childWidth;
653            } else {
654                final int width = getWidth();
655                offset = (float) (width - left) / childWidth;
656            }
657            setDrawerViewOffset(changedView, offset);
658            invalidate();
659        }
660
661        @Override
662        public void onViewCaptured(View capturedChild, int activePointerId) {
663            final LayoutParams lp = (LayoutParams) capturedChild.getLayoutParams();
664            lp.isPeeking = false;
665
666            closeOtherDrawer();
667        }
668
669        private void closeOtherDrawer() {
670            final int otherGrav = mGravity == Gravity.LEFT ? Gravity.RIGHT : Gravity.LEFT;
671            final View toClose = findDrawerWithGravity(otherGrav);
672            if (toClose != null) {
673                closeDrawer(toClose);
674            }
675        }
676
677        @Override
678        public void onViewReleased(View releasedChild, float xvel, float yvel) {
679            // Offset is how open the drawer is, therefore left/right values
680            // are reversed from one another.
681            final float offset = getDrawerViewOffset(releasedChild);
682            final int childWidth = releasedChild.getWidth();
683
684            int left;
685            if (checkDrawerViewGravity(releasedChild, Gravity.LEFT)) {
686                left = xvel > 0 || xvel == 0 && offset > 0.5f ? 0 : -childWidth;
687            } else {
688                final int width = getWidth();
689                left = xvel < 0 || xvel == 0 && offset < 0.5f ? width - childWidth : width;
690            }
691
692            mDragger.settleCapturedViewAt(left, releasedChild.getTop());
693            invalidate();
694        }
695
696        @Override
697        public void onEdgeTouched(int edgeFlags, int pointerId) {
698            final View toCapture;
699            final int childLeft;
700            if ((edgeFlags & ViewDragHelper.EDGE_LEFT) == ViewDragHelper.EDGE_LEFT) {
701                toCapture = findDrawerWithGravity(Gravity.LEFT);
702                childLeft = -toCapture.getWidth() + mDrawerPeekDistance;
703            } else {
704                toCapture = findDrawerWithGravity(Gravity.RIGHT);
705                childLeft = getWidth() - mDrawerPeekDistance;
706            }
707
708            if (toCapture != null) {
709                mDragger.smoothSlideViewTo(toCapture, childLeft, toCapture.getTop());
710                ((LayoutParams) toCapture.getLayoutParams()).isPeeking = true;
711                invalidate();
712
713                closeOtherDrawer();
714            }
715        }
716
717        @Override
718        public void onEdgeDragStarted(int edgeFlags, int pointerId) {
719            final View toCapture;
720            if ((edgeFlags & ViewDragHelper.EDGE_LEFT) == ViewDragHelper.EDGE_LEFT) {
721                toCapture = findDrawerWithGravity(Gravity.LEFT);
722            } else {
723                toCapture = findDrawerWithGravity(Gravity.RIGHT);
724            }
725
726            if (toCapture != null) {
727                mDragger.captureChildView(toCapture, pointerId);
728            }
729        }
730
731        @Override
732        public int getViewHorizontalDragRange(View child) {
733            return child.getWidth();
734        }
735
736        @Override
737        public int clampViewPositionHorizontal(View child, int left, int dx) {
738            if (checkDrawerViewGravity(child, Gravity.LEFT)) {
739                return Math.max(-child.getWidth(), Math.min(left, 0));
740            } else {
741                final int width = getWidth();
742                return Math.max(width - child.getWidth(), Math.min(left, width));
743            }
744        }
745    }
746
747    public static class LayoutParams extends ViewGroup.LayoutParams {
748
749        public int gravity = Gravity.NO_GRAVITY;
750        float onscreen;
751        boolean isPeeking;
752        boolean knownOpen;
753
754        public LayoutParams(Context c, AttributeSet attrs) {
755            super(c, attrs);
756
757            final TypedArray a = c.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
758            this.gravity = a.getInt(0, Gravity.NO_GRAVITY);
759            a.recycle();
760        }
761
762        public LayoutParams(int width, int height) {
763            super(width, height);
764        }
765
766        public LayoutParams(int width, int height, int gravity) {
767            this(width, height);
768            this.gravity = gravity;
769        }
770
771        public LayoutParams(LayoutParams source) {
772            super(source);
773            this.gravity = source.gravity;
774        }
775
776        public LayoutParams(ViewGroup.LayoutParams source) {
777            super(source);
778        }
779    }
780}
781