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