Home | History | Annotate | Download | only in widget
      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 
     18 package com.android.internal.widget;
     19 
     20 import android.content.Context;
     21 import android.content.res.TypedArray;
     22 import android.graphics.Rect;
     23 import android.os.Parcel;
     24 import android.os.Parcelable;
     25 import android.util.AttributeSet;
     26 import android.util.Log;
     27 import android.view.MotionEvent;
     28 import android.view.VelocityTracker;
     29 import android.view.View;
     30 import android.view.ViewConfiguration;
     31 import android.view.ViewGroup;
     32 import android.view.ViewParent;
     33 import android.view.ViewTreeObserver;
     34 import android.view.animation.AnimationUtils;
     35 import android.widget.AbsListView;
     36 import android.widget.OverScroller;
     37 import com.android.internal.R;
     38 
     39 public 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