ResolverDrawerLayout.java revision fd1e93d128da9dbb2f97630c01239bb56199686e
1/*
2 * Copyright (C) 2014 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 com.android.internal.widget;
19
20import android.content.Context;
21import android.content.res.TypedArray;
22import android.graphics.Rect;
23import android.os.Parcel;
24import android.os.Parcelable;
25import android.util.AttributeSet;
26import android.util.Log;
27import android.view.MotionEvent;
28import android.view.VelocityTracker;
29import android.view.View;
30import android.view.ViewConfiguration;
31import android.view.ViewGroup;
32import android.view.ViewParent;
33import android.view.ViewTreeObserver;
34import android.view.animation.AnimationUtils;
35import android.widget.AbsListView;
36import android.widget.OverScroller;
37import com.android.internal.R;
38
39public class ResolverDrawerLayout extends ViewGroup {
40    private static final String TAG = "ResolverDrawerLayout";
41
42    /**
43     * Max width of the whole drawer layout
44     */
45    private int mMaxWidth;
46
47    /**
48     * Max total visible height of views not marked always-show when in the closed/initial state
49     */
50    private int mMaxCollapsedHeight;
51
52    /**
53     * Max total visible height of views not marked always-show when in the closed/initial state
54     * when a default option is present
55     */
56    private int mMaxCollapsedHeightSmall;
57
58    private boolean mSmallCollapsed;
59
60    /**
61     * Move views down from the top by this much in px
62     */
63    private float mCollapseOffset;
64
65    private int mCollapsibleHeight;
66
67    private int mTopOffset;
68
69    private boolean mIsDragging;
70    private boolean mOpenOnClick;
71    private boolean mOpenOnLayout;
72    private final int mTouchSlop;
73    private final float mMinFlingVelocity;
74    private final OverScroller mScroller;
75    private final VelocityTracker mVelocityTracker;
76
77    private OnClickListener mClickOutsideListener;
78    private float mInitialTouchX;
79    private float mInitialTouchY;
80    private float mLastTouchY;
81    private int mActivePointerId = MotionEvent.INVALID_POINTER_ID;
82
83    private final Rect mTempRect = new Rect();
84
85    private final ViewTreeObserver.OnTouchModeChangeListener mTouchModeChangeListener =
86            new ViewTreeObserver.OnTouchModeChangeListener() {
87                @Override
88                public void onTouchModeChanged(boolean isInTouchMode) {
89                    if (!isInTouchMode && hasFocus() && isDescendantClipped(getFocusedChild())) {
90                        smoothScrollTo(0, 0);
91                    }
92                }
93            };
94
95    public ResolverDrawerLayout(Context context) {
96        this(context, null);
97    }
98
99    public ResolverDrawerLayout(Context context, AttributeSet attrs) {
100        this(context, attrs, 0);
101    }
102
103    public ResolverDrawerLayout(Context context, AttributeSet attrs, int defStyleAttr) {
104        super(context, attrs, defStyleAttr);
105
106        final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ResolverDrawerLayout,
107                defStyleAttr, 0);
108        mMaxWidth = a.getDimensionPixelSize(R.styleable.ResolverDrawerLayout_maxWidth, -1);
109        mMaxCollapsedHeight = a.getDimensionPixelSize(
110                R.styleable.ResolverDrawerLayout_maxCollapsedHeight, 0);
111        mMaxCollapsedHeightSmall = a.getDimensionPixelSize(
112                R.styleable.ResolverDrawerLayout_maxCollapsedHeightSmall,
113                mMaxCollapsedHeight);
114        a.recycle();
115
116        mScroller = new OverScroller(context, AnimationUtils.loadInterpolator(context,
117                android.R.interpolator.decelerate_quint));
118        mVelocityTracker = VelocityTracker.obtain();
119
120        final ViewConfiguration vc = ViewConfiguration.get(context);
121        mTouchSlop = vc.getScaledTouchSlop();
122        mMinFlingVelocity = vc.getScaledMinimumFlingVelocity();
123    }
124
125    public void setSmallCollapsed(boolean smallCollapsed) {
126        mSmallCollapsed = smallCollapsed;
127        requestLayout();
128    }
129
130    public boolean isSmallCollapsed() {
131        return mSmallCollapsed;
132    }
133
134    public boolean isCollapsed() {
135        return mCollapseOffset > 0;
136    }
137
138    private boolean isMoving() {
139        return mIsDragging || !mScroller.isFinished();
140    }
141
142    private int getMaxCollapsedHeight() {
143        return isSmallCollapsed() ? mMaxCollapsedHeightSmall : mMaxCollapsedHeight;
144    }
145
146    public void setOnClickOutsideListener(OnClickListener listener) {
147        mClickOutsideListener = listener;
148    }
149
150    @Override
151    public boolean onInterceptTouchEvent(MotionEvent ev) {
152        final int action = ev.getActionMasked();
153
154        if (action == MotionEvent.ACTION_DOWN) {
155            mVelocityTracker.clear();
156        }
157
158        mVelocityTracker.addMovement(ev);
159
160        switch (action) {
161            case MotionEvent.ACTION_DOWN: {
162                final float x = ev.getX();
163                final float y = ev.getY();
164                mInitialTouchX = x;
165                mInitialTouchY = mLastTouchY = y;
166                mOpenOnClick = isListChildUnderClipped(x, y) && mCollapsibleHeight > 0;
167            }
168            break;
169
170            case MotionEvent.ACTION_MOVE: {
171                final float x = ev.getX();
172                final float y = ev.getY();
173                final float dy = y - mInitialTouchY;
174                if (Math.abs(dy) > mTouchSlop && findChildUnder(x, y) != null &&
175                        (getNestedScrollAxes() & SCROLL_AXIS_VERTICAL) == 0) {
176                    mActivePointerId = ev.getPointerId(0);
177                    mIsDragging = true;
178                    mLastTouchY = Math.max(mLastTouchY - mTouchSlop,
179                            Math.min(mLastTouchY + dy, mLastTouchY + mTouchSlop));
180                }
181            }
182            break;
183
184            case MotionEvent.ACTION_POINTER_UP: {
185                onSecondaryPointerUp(ev);
186            }
187            break;
188
189            case MotionEvent.ACTION_CANCEL:
190            case MotionEvent.ACTION_UP: {
191                resetTouch();
192            }
193            break;
194        }
195
196        if (mIsDragging) {
197            mScroller.abortAnimation();
198        }
199        return mIsDragging || mOpenOnClick;
200    }
201
202    @Override
203    public boolean onTouchEvent(MotionEvent ev) {
204        final int action = ev.getActionMasked();
205
206        mVelocityTracker.addMovement(ev);
207
208        boolean handled = false;
209        switch (action) {
210            case MotionEvent.ACTION_DOWN: {
211                final float x = ev.getX();
212                final float y = ev.getY();
213                mInitialTouchX = x;
214                mInitialTouchY = mLastTouchY = y;
215                mActivePointerId = ev.getPointerId(0);
216                if (findChildUnder(mInitialTouchX, mInitialTouchY) == null &&
217                        mClickOutsideListener != null) {
218                    mIsDragging = handled = true;
219                }
220                handled |= mCollapsibleHeight > 0;
221                mScroller.abortAnimation();
222            }
223            break;
224
225            case MotionEvent.ACTION_MOVE: {
226                int index = ev.findPointerIndex(mActivePointerId);
227                if (index < 0) {
228                    Log.e(TAG, "Bad pointer id " + mActivePointerId + ", resetting");
229                    index = 0;
230                    mActivePointerId = ev.getPointerId(0);
231                    mInitialTouchX = ev.getX();
232                    mInitialTouchY = mLastTouchY = ev.getY();
233                }
234                final float x = ev.getX(index);
235                final float y = ev.getY(index);
236                if (!mIsDragging) {
237                    final float dy = y - mInitialTouchY;
238                    if (Math.abs(dy) > mTouchSlop && findChildUnder(x, y) != null) {
239                        handled = mIsDragging = true;
240                        mLastTouchY = Math.max(mLastTouchY - mTouchSlop,
241                                Math.min(mLastTouchY + dy, mLastTouchY + mTouchSlop));
242                    }
243                }
244                if (mIsDragging) {
245                    final float dy = y - mLastTouchY;
246                    performDrag(dy);
247                }
248                mLastTouchY = y;
249            }
250            break;
251
252            case MotionEvent.ACTION_POINTER_DOWN: {
253                final int pointerIndex = ev.getActionIndex();
254                final int pointerId = ev.getPointerId(pointerIndex);
255                mActivePointerId = pointerId;
256                mInitialTouchX = ev.getX(pointerIndex);
257                mInitialTouchY = mLastTouchY = ev.getY(pointerIndex);
258            }
259            break;
260
261            case MotionEvent.ACTION_POINTER_UP: {
262                onSecondaryPointerUp(ev);
263            }
264            break;
265
266            case MotionEvent.ACTION_UP: {
267                mIsDragging = false;
268                if (!mIsDragging && findChildUnder(mInitialTouchX, mInitialTouchY) == null &&
269                        findChildUnder(ev.getX(), ev.getY()) == null) {
270                    if (mClickOutsideListener != null) {
271                        mClickOutsideListener.onClick(this);
272                        resetTouch();
273                        return true;
274                    }
275                }
276                if (mOpenOnClick && Math.abs(ev.getX() - mInitialTouchX) < mTouchSlop &&
277                        Math.abs(ev.getY() - mInitialTouchY) < mTouchSlop) {
278                    smoothScrollTo(0, 0);
279                    return true;
280                }
281                mVelocityTracker.computeCurrentVelocity(1000);
282                final float yvel = mVelocityTracker.getYVelocity(mActivePointerId);
283                if (Math.abs(yvel) > mMinFlingVelocity) {
284                    smoothScrollTo(yvel < 0 ? 0 : mCollapsibleHeight, yvel);
285                } else {
286                    smoothScrollTo(
287                            mCollapseOffset < mCollapsibleHeight / 2 ? 0 : mCollapsibleHeight, 0);
288                }
289                resetTouch();
290            }
291            break;
292
293            case MotionEvent.ACTION_CANCEL: {
294                if (mIsDragging) {
295                    smoothScrollTo(
296                            mCollapseOffset < mCollapsibleHeight / 2 ? 0 : mCollapsibleHeight, 0);
297                }
298                resetTouch();
299                return true;
300            }
301        }
302
303        return handled;
304    }
305
306    private void onSecondaryPointerUp(MotionEvent ev) {
307        final int pointerIndex = ev.getActionIndex();
308        final int pointerId = ev.getPointerId(pointerIndex);
309        if (pointerId == mActivePointerId) {
310            // This was our active pointer going up. Choose a new
311            // active pointer and adjust accordingly.
312            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
313            mInitialTouchX = ev.getX(newPointerIndex);
314            mInitialTouchY = mLastTouchY = ev.getY(newPointerIndex);
315            mActivePointerId = ev.getPointerId(newPointerIndex);
316        }
317    }
318
319    private void resetTouch() {
320        mActivePointerId = MotionEvent.INVALID_POINTER_ID;
321        mIsDragging = false;
322        mOpenOnClick = false;
323        mInitialTouchX = mInitialTouchY = mLastTouchY = 0;
324        mVelocityTracker.clear();
325    }
326
327    @Override
328    public void computeScroll() {
329        super.computeScroll();
330        if (!mScroller.isFinished()) {
331            final boolean keepGoing = mScroller.computeScrollOffset();
332            performDrag(mScroller.getCurrY() - mCollapseOffset);
333            if (keepGoing) {
334                postInvalidateOnAnimation();
335            }
336        }
337    }
338
339    private float performDrag(float dy) {
340        final float newPos = Math.max(0, Math.min(mCollapseOffset + dy, mCollapsibleHeight));
341        if (newPos != mCollapseOffset) {
342            dy = newPos - mCollapseOffset;
343            final int childCount = getChildCount();
344            for (int i = 0; i < childCount; i++) {
345                final View child = getChildAt(i);
346                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
347                if (!lp.ignoreOffset) {
348                    child.offsetTopAndBottom((int) dy);
349                }
350            }
351            mCollapseOffset = newPos;
352            mTopOffset += dy;
353            postInvalidateOnAnimation();
354            return dy;
355        }
356        return 0;
357    }
358
359    private void smoothScrollTo(int yOffset, float velocity) {
360        if (getMaxCollapsedHeight() == 0) {
361            return;
362        }
363        mScroller.abortAnimation();
364        final int sy = (int) mCollapseOffset;
365        int dy = yOffset - sy;
366        if (dy == 0) {
367            return;
368        }
369
370        final int height = getHeight();
371        final int halfHeight = height / 2;
372        final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dy) / height);
373        final float distance = halfHeight + halfHeight *
374                distanceInfluenceForSnapDuration(distanceRatio);
375
376        int duration = 0;
377        velocity = Math.abs(velocity);
378        if (velocity > 0) {
379            duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
380        } else {
381            final float pageDelta = (float) Math.abs(dy) / height;
382            duration = (int) ((pageDelta + 1) * 100);
383        }
384        duration = Math.min(duration, 300);
385
386        mScroller.startScroll(0, sy, 0, dy, duration);
387        postInvalidateOnAnimation();
388    }
389
390    private float distanceInfluenceForSnapDuration(float f) {
391        f -= 0.5f; // center the values about 0.
392        f *= 0.3f * Math.PI / 2.0f;
393        return (float) Math.sin(f);
394    }
395
396    /**
397     * Note: this method doesn't take Z into account for overlapping views
398     * since it is only used in contexts where this doesn't affect the outcome.
399     */
400    private View findChildUnder(float x, float y) {
401        return findChildUnder(this, x, y);
402    }
403
404    private static View findChildUnder(ViewGroup parent, float x, float y) {
405        final int childCount = parent.getChildCount();
406        for (int i = childCount - 1; i >= 0; i--) {
407            final View child = parent.getChildAt(i);
408            if (isChildUnder(child, x, y)) {
409                return child;
410            }
411        }
412        return null;
413    }
414
415    private View findListChildUnder(float x, float y) {
416        View v = findChildUnder(x, y);
417        while (v != null) {
418            x -= v.getX();
419            y -= v.getY();
420            if (v instanceof AbsListView) {
421                // One more after this.
422                return findChildUnder((ViewGroup) v, x, y);
423            }
424            v = v instanceof ViewGroup ? findChildUnder((ViewGroup) v, x, y) : null;
425        }
426        return v;
427    }
428
429    /**
430     * This only checks clipping along the bottom edge.
431     */
432    private boolean isListChildUnderClipped(float x, float y) {
433        final View listChild = findListChildUnder(x, y);
434        return listChild != null && isDescendantClipped(listChild);
435    }
436
437    private boolean isDescendantClipped(View child) {
438        mTempRect.set(0, 0, child.getWidth(), child.getHeight());
439        offsetDescendantRectToMyCoords(child, mTempRect);
440        View directChild;
441        if (child.getParent() == this) {
442            directChild = child;
443        } else {
444            View v = child;
445            ViewParent p = child.getParent();
446            while (p != this) {
447                v = (View) p;
448                p = v.getParent();
449            }
450            directChild = v;
451        }
452
453        // ResolverDrawerLayout lays out vertically in child order;
454        // the next view and forward is what to check against.
455        int clipEdge = getHeight() - getPaddingBottom();
456        final int childCount = getChildCount();
457        for (int i = indexOfChild(directChild) + 1; i < childCount; i++) {
458            final View nextChild = getChildAt(i);
459            if (nextChild.getVisibility() == GONE) {
460                continue;
461            }
462            clipEdge = Math.min(clipEdge, nextChild.getTop());
463        }
464        return mTempRect.bottom > clipEdge;
465    }
466
467    private static boolean isChildUnder(View child, float x, float y) {
468        final float left = child.getX();
469        final float top = child.getY();
470        final float right = left + child.getWidth();
471        final float bottom = top + child.getHeight();
472        return x >= left && y >= top && x < right && y < bottom;
473    }
474
475    @Override
476    public void requestChildFocus(View child, View focused) {
477        super.requestChildFocus(child, focused);
478        if (!isInTouchMode() && isDescendantClipped(focused)) {
479            smoothScrollTo(0, 0);
480        }
481    }
482
483    @Override
484    protected void onAttachedToWindow() {
485        super.onAttachedToWindow();
486        getViewTreeObserver().addOnTouchModeChangeListener(mTouchModeChangeListener);
487    }
488
489    @Override
490    protected void onDetachedFromWindow() {
491        super.onDetachedFromWindow();
492        getViewTreeObserver().removeOnTouchModeChangeListener(mTouchModeChangeListener);
493    }
494
495    @Override
496    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
497        return (nestedScrollAxes & View.SCROLL_AXIS_VERTICAL) != 0;
498    }
499
500    @Override
501    public void onNestedScrollAccepted(View child, View target, int axes) {
502        super.onNestedScrollAccepted(child, target, axes);
503    }
504
505    @Override
506    public void onStopNestedScroll(View child) {
507        super.onStopNestedScroll(child);
508        if (mScroller.isFinished()) {
509            smoothScrollTo(mCollapseOffset < mCollapsibleHeight / 2 ? 0 : mCollapsibleHeight, 0);
510        }
511    }
512
513    @Override
514    public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
515            int dxUnconsumed, int dyUnconsumed) {
516        if (dyUnconsumed < 0) {
517            performDrag(-dyUnconsumed);
518        }
519    }
520
521    @Override
522    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
523        if (dy > 0) {
524            consumed[1] = (int) -performDrag(-dy);
525        }
526    }
527
528    @Override
529    public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
530        if (velocityY > mMinFlingVelocity && mCollapseOffset != 0) {
531            smoothScrollTo(0, velocityY);
532            return true;
533        }
534        return false;
535    }
536
537    @Override
538    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
539        if (!consumed && Math.abs(velocityY) > mMinFlingVelocity) {
540            smoothScrollTo(velocityY > 0 ? 0 : mCollapsibleHeight, velocityY);
541            return true;
542        }
543        return false;
544    }
545
546    @Override
547    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
548        final int sourceWidth = MeasureSpec.getSize(widthMeasureSpec);
549        int widthSize = sourceWidth;
550        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
551
552        // Single-use layout; just ignore the mode and use available space.
553        // Clamp to maxWidth.
554        if (mMaxWidth >= 0) {
555            widthSize = Math.min(widthSize, mMaxWidth);
556        }
557
558        final int widthSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY);
559        final int heightSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY);
560        final int widthPadding = getPaddingLeft() + getPaddingRight();
561        int heightUsed = getPaddingTop() + getPaddingBottom();
562
563        // Measure always-show children first.
564        final int childCount = getChildCount();
565        for (int i = 0; i < childCount; i++) {
566            final View child = getChildAt(i);
567            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
568            if (lp.alwaysShow && child.getVisibility() != GONE) {
569                measureChildWithMargins(child, widthSpec, widthPadding, heightSpec, heightUsed);
570                heightUsed += lp.topMargin + child.getMeasuredHeight() + lp.bottomMargin;
571            }
572        }
573
574        final int alwaysShowHeight = heightUsed;
575
576        // And now the rest.
577        for (int i = 0; i < childCount; i++) {
578            final View child = getChildAt(i);
579            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
580            if (!lp.alwaysShow && child.getVisibility() != GONE) {
581                measureChildWithMargins(child, widthSpec, widthPadding, heightSpec, heightUsed);
582                heightUsed += lp.topMargin + child.getMeasuredHeight() + lp.bottomMargin;
583            }
584        }
585
586        mCollapsibleHeight = Math.max(0,
587                heightUsed - alwaysShowHeight - getMaxCollapsedHeight());
588
589        if (isLaidOut()) {
590            mCollapseOffset = Math.min(mCollapseOffset, mCollapsibleHeight);
591        } else {
592            // Start out collapsed at first unless we restored state for otherwise
593            mCollapseOffset = mOpenOnLayout ? 0 : mCollapsibleHeight;
594        }
595
596        mTopOffset = Math.max(0, heightSize - heightUsed) + (int) mCollapseOffset;
597
598        setMeasuredDimension(sourceWidth, heightSize);
599    }
600
601    @Override
602    protected void onLayout(boolean changed, int l, int t, int r, int b) {
603        final int width = getWidth();
604
605        int ypos = mTopOffset;
606        int leftEdge = getPaddingLeft();
607        int rightEdge = width - getPaddingRight();
608
609        final int childCount = getChildCount();
610        for (int i = 0; i < childCount; i++) {
611            final View child = getChildAt(i);
612            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
613
614            if (child.getVisibility() == GONE) {
615                continue;
616            }
617
618            int top = ypos + lp.topMargin;
619            if (lp.ignoreOffset) {
620                top -= mCollapseOffset;
621            }
622            final int bottom = top + child.getMeasuredHeight();
623
624            final int childWidth = child.getMeasuredWidth();
625            final int widthAvailable = rightEdge - leftEdge;
626            final int left = leftEdge + (widthAvailable - childWidth) / 2;
627            final int right = left + childWidth;
628
629            child.layout(left, top, right, bottom);
630
631            ypos = bottom + lp.bottomMargin;
632        }
633    }
634
635    @Override
636    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
637        return new LayoutParams(getContext(), attrs);
638    }
639
640    @Override
641    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
642        if (p instanceof LayoutParams) {
643            return new LayoutParams((LayoutParams) p);
644        } else if (p instanceof MarginLayoutParams) {
645            return new LayoutParams((MarginLayoutParams) p);
646        }
647        return new LayoutParams(p);
648    }
649
650    @Override
651    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
652        return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
653    }
654
655    @Override
656    protected Parcelable onSaveInstanceState() {
657        final SavedState ss = new SavedState(super.onSaveInstanceState());
658        ss.open = mCollapsibleHeight > 0 && mCollapseOffset == 0;
659        return ss;
660    }
661
662    @Override
663    protected void onRestoreInstanceState(Parcelable state) {
664        final SavedState ss = (SavedState) state;
665        super.onRestoreInstanceState(ss.getSuperState());
666        mOpenOnLayout = ss.open;
667    }
668
669    public static class LayoutParams extends MarginLayoutParams {
670        public boolean alwaysShow;
671        public boolean ignoreOffset;
672
673        public LayoutParams(Context c, AttributeSet attrs) {
674            super(c, attrs);
675
676            final TypedArray a = c.obtainStyledAttributes(attrs,
677                    R.styleable.ResolverDrawerLayout_LayoutParams);
678            alwaysShow = a.getBoolean(
679                    R.styleable.ResolverDrawerLayout_LayoutParams_layout_alwaysShow,
680                    false);
681            ignoreOffset = a.getBoolean(
682                    R.styleable.ResolverDrawerLayout_LayoutParams_layout_ignoreOffset,
683                    false);
684            a.recycle();
685        }
686
687        public LayoutParams(int width, int height) {
688            super(width, height);
689        }
690
691        public LayoutParams(LayoutParams source) {
692            super(source);
693            this.alwaysShow = source.alwaysShow;
694            this.ignoreOffset = source.ignoreOffset;
695        }
696
697        public LayoutParams(MarginLayoutParams source) {
698            super(source);
699        }
700
701        public LayoutParams(ViewGroup.LayoutParams source) {
702            super(source);
703        }
704    }
705
706    static class SavedState extends BaseSavedState {
707        boolean open;
708
709        SavedState(Parcelable superState) {
710            super(superState);
711        }
712
713        private SavedState(Parcel in) {
714            super(in);
715            open = in.readInt() != 0;
716        }
717
718        @Override
719        public void writeToParcel(Parcel out, int flags) {
720            super.writeToParcel(out, flags);
721            out.writeInt(open ? 1 : 0);
722        }
723
724        public static final Parcelable.Creator<SavedState> CREATOR =
725                new Parcelable.Creator<SavedState>() {
726            @Override
727            public SavedState createFromParcel(Parcel in) {
728                return new SavedState(in);
729            }
730
731            @Override
732            public SavedState[] newArray(int size) {
733                return new SavedState[size];
734            }
735        };
736    }
737}
738