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