FileDocCategorySizeDatePackage
ResolverDrawerLayout.javaAPI DocAndroid 5.1 API29381Thu Mar 12 22:22:10 GMT 2015com.android.internal.widget

ResolverDrawerLayout

public class ResolverDrawerLayout extends android.view.ViewGroup

Fields Summary
private static final String
TAG
private int
mMaxWidth
Max width of the whole drawer layout
private int
mMaxCollapsedHeight
Max total visible height of views not marked always-show when in the closed/initial state
private int
mMaxCollapsedHeightSmall
Max total visible height of views not marked always-show when in the closed/initial state when a default option is present
private boolean
mSmallCollapsed
private float
mCollapseOffset
Move views down from the top by this much in px
private int
mCollapsibleHeight
private int
mUncollapsibleHeight
private int
mTopOffset
private boolean
mIsDragging
private boolean
mOpenOnClick
private boolean
mOpenOnLayout
private boolean
mDismissOnScrollerFinished
private final int
mTouchSlop
private final float
mMinFlingVelocity
private final android.widget.OverScroller
mScroller
private final android.view.VelocityTracker
mVelocityTracker
private OnDismissedListener
mOnDismissedListener
private RunOnDismissedListener
mRunOnDismissedListener
private float
mInitialTouchX
private float
mInitialTouchY
private float
mLastTouchY
private int
mActivePointerId
private final android.graphics.Rect
mTempRect
private final ViewTreeObserver.OnTouchModeChangeListener
mTouchModeChangeListener
Constructors Summary
public ResolverDrawerLayout(android.content.Context context)


       
        this(context, null);
    
public ResolverDrawerLayout(android.content.Context context, android.util.AttributeSet attrs)

        this(context, attrs, 0);
    
public ResolverDrawerLayout(android.content.Context context, android.util.AttributeSet attrs, int defStyleAttr)

        super(context, attrs, defStyleAttr);

        final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ResolverDrawerLayout,
                defStyleAttr, 0);
        mMaxWidth = a.getDimensionPixelSize(R.styleable.ResolverDrawerLayout_maxWidth, -1);
        mMaxCollapsedHeight = a.getDimensionPixelSize(
                R.styleable.ResolverDrawerLayout_maxCollapsedHeight, 0);
        mMaxCollapsedHeightSmall = a.getDimensionPixelSize(
                R.styleable.ResolverDrawerLayout_maxCollapsedHeightSmall,
                mMaxCollapsedHeight);
        a.recycle();

        mScroller = new OverScroller(context, AnimationUtils.loadInterpolator(context,
                android.R.interpolator.decelerate_quint));
        mVelocityTracker = VelocityTracker.obtain();

        final ViewConfiguration vc = ViewConfiguration.get(context);
        mTouchSlop = vc.getScaledTouchSlop();
        mMinFlingVelocity = vc.getScaledMinimumFlingVelocity();
    
Methods Summary
private voidabortAnimation()

        mScroller.abortAnimation();
        mRunOnDismissedListener = null;
        mDismissOnScrollerFinished = false;
    
public voidcomputeScroll()

        super.computeScroll();
        if (mScroller.computeScrollOffset()) {
            final boolean keepGoing = !mScroller.isFinished();
            performDrag(mScroller.getCurrY() - mCollapseOffset);
            if (keepGoing) {
                postInvalidateOnAnimation();
            } else if (mDismissOnScrollerFinished && mOnDismissedListener != null) {
                mRunOnDismissedListener = new RunOnDismissedListener();
                post(mRunOnDismissedListener);
            }
        }
    
voiddispatchOnDismissed()

        if (mOnDismissedListener != null) {
            mOnDismissedListener.onDismissed();
        }
        if (mRunOnDismissedListener != null) {
            removeCallbacks(mRunOnDismissedListener);
            mRunOnDismissedListener = null;
        }
    
private floatdistanceInfluenceForSnapDuration(float f)

        f -= 0.5f; // center the values about 0.
        f *= 0.3f * Math.PI / 2.0f;
        return (float) Math.sin(f);
    
private android.view.ViewfindChildUnder(float x, float y)
Note: this method doesn't take Z into account for overlapping views since it is only used in contexts where this doesn't affect the outcome.

        return findChildUnder(this, x, y);
    
private static android.view.ViewfindChildUnder(android.view.ViewGroup parent, float x, float y)

        final int childCount = parent.getChildCount();
        for (int i = childCount - 1; i >= 0; i--) {
            final View child = parent.getChildAt(i);
            if (isChildUnder(child, x, y)) {
                return child;
            }
        }
        return null;
    
private android.view.ViewfindListChildUnder(float x, float y)

        View v = findChildUnder(x, y);
        while (v != null) {
            x -= v.getX();
            y -= v.getY();
            if (v instanceof AbsListView) {
                // One more after this.
                return findChildUnder((ViewGroup) v, x, y);
            }
            v = v instanceof ViewGroup ? findChildUnder((ViewGroup) v, x, y) : null;
        }
        return v;
    
protected ViewGroup.LayoutParamsgenerateDefaultLayoutParams()

        return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
    
public ViewGroup.LayoutParamsgenerateLayoutParams(android.util.AttributeSet attrs)

        return new LayoutParams(getContext(), attrs);
    
protected ViewGroup.LayoutParamsgenerateLayoutParams(ViewGroup.LayoutParams p)

        if (p instanceof LayoutParams) {
            return new LayoutParams((LayoutParams) p);
        } else if (p instanceof MarginLayoutParams) {
            return new LayoutParams((MarginLayoutParams) p);
        }
        return new LayoutParams(p);
    
private intgetMaxCollapsedHeight()

        return isSmallCollapsed() ? mMaxCollapsedHeightSmall : mMaxCollapsedHeight;
    
private static booleanisChildUnder(android.view.View child, float x, float y)

        final float left = child.getX();
        final float top = child.getY();
        final float right = left + child.getWidth();
        final float bottom = top + child.getHeight();
        return x >= left && y >= top && x < right && y < bottom;
    
public booleanisCollapsed()

        return mCollapseOffset > 0;
    
private booleanisDescendantClipped(android.view.View child)

        mTempRect.set(0, 0, child.getWidth(), child.getHeight());
        offsetDescendantRectToMyCoords(child, mTempRect);
        View directChild;
        if (child.getParent() == this) {
            directChild = child;
        } else {
            View v = child;
            ViewParent p = child.getParent();
            while (p != this) {
                v = (View) p;
                p = v.getParent();
            }
            directChild = v;
        }

        // ResolverDrawerLayout lays out vertically in child order;
        // the next view and forward is what to check against.
        int clipEdge = getHeight() - getPaddingBottom();
        final int childCount = getChildCount();
        for (int i = indexOfChild(directChild) + 1; i < childCount; i++) {
            final View nextChild = getChildAt(i);
            if (nextChild.getVisibility() == GONE) {
                continue;
            }
            clipEdge = Math.min(clipEdge, nextChild.getTop());
        }
        return mTempRect.bottom > clipEdge;
    
private booleanisListChildUnderClipped(float x, float y)
This only checks clipping along the bottom edge.

        final View listChild = findListChildUnder(x, y);
        return listChild != null && isDescendantClipped(listChild);
    
private booleanisMoving()

        return mIsDragging || !mScroller.isFinished();
    
public booleanisSmallCollapsed()

        return mSmallCollapsed;
    
protected voidonAttachedToWindow()

        super.onAttachedToWindow();
        getViewTreeObserver().addOnTouchModeChangeListener(mTouchModeChangeListener);
    
protected voidonDetachedFromWindow()

        super.onDetachedFromWindow();
        getViewTreeObserver().removeOnTouchModeChangeListener(mTouchModeChangeListener);
        abortAnimation();
    
public voidonInitializeAccessibilityEvent(android.view.accessibility.AccessibilityEvent event)

        super.onInitializeAccessibilityEvent(event);
        event.setClassName(ResolverDrawerLayout.class.getName());
    
public voidonInitializeAccessibilityNodeInfo(android.view.accessibility.AccessibilityNodeInfo info)

        super.onInitializeAccessibilityNodeInfo(info);
        info.setClassName(ResolverDrawerLayout.class.getName());
        if (isEnabled()) {
            if (mCollapseOffset != 0) {
                info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
                info.setScrollable(true);
            }
        }
    
public booleanonInterceptTouchEvent(android.view.MotionEvent ev)

        final int action = ev.getActionMasked();

        if (action == MotionEvent.ACTION_DOWN) {
            mVelocityTracker.clear();
        }

        mVelocityTracker.addMovement(ev);

        switch (action) {
            case MotionEvent.ACTION_DOWN: {
                final float x = ev.getX();
                final float y = ev.getY();
                mInitialTouchX = x;
                mInitialTouchY = mLastTouchY = y;
                mOpenOnClick = isListChildUnderClipped(x, y) && mCollapsibleHeight > 0;
            }
            break;

            case MotionEvent.ACTION_MOVE: {
                final float x = ev.getX();
                final float y = ev.getY();
                final float dy = y - mInitialTouchY;
                if (Math.abs(dy) > mTouchSlop && findChildUnder(x, y) != null &&
                        (getNestedScrollAxes() & SCROLL_AXIS_VERTICAL) == 0) {
                    mActivePointerId = ev.getPointerId(0);
                    mIsDragging = true;
                    mLastTouchY = Math.max(mLastTouchY - mTouchSlop,
                            Math.min(mLastTouchY + dy, mLastTouchY + mTouchSlop));
                }
            }
            break;

            case MotionEvent.ACTION_POINTER_UP: {
                onSecondaryPointerUp(ev);
            }
            break;

            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP: {
                resetTouch();
            }
            break;
        }

        if (mIsDragging) {
            abortAnimation();
        }
        return mIsDragging || mOpenOnClick;
    
protected voidonLayout(boolean changed, int l, int t, int r, int b)

        final int width = getWidth();

        int ypos = mTopOffset;
        int leftEdge = getPaddingLeft();
        int rightEdge = width - getPaddingRight();

        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = getChildAt(i);
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();

            if (child.getVisibility() == GONE) {
                continue;
            }

            int top = ypos + lp.topMargin;
            if (lp.ignoreOffset) {
                top -= mCollapseOffset;
            }
            final int bottom = top + child.getMeasuredHeight();

            final int childWidth = child.getMeasuredWidth();
            final int widthAvailable = rightEdge - leftEdge;
            final int left = leftEdge + (widthAvailable - childWidth) / 2;
            final int right = left + childWidth;

            child.layout(left, top, right, bottom);

            ypos = bottom + lp.bottomMargin;
        }
    
protected voidonMeasure(int widthMeasureSpec, int heightMeasureSpec)

        final int sourceWidth = MeasureSpec.getSize(widthMeasureSpec);
        int widthSize = sourceWidth;
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        // Single-use layout; just ignore the mode and use available space.
        // Clamp to maxWidth.
        if (mMaxWidth >= 0) {
            widthSize = Math.min(widthSize, mMaxWidth);
        }

        final int widthSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY);
        final int heightSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY);
        final int widthPadding = getPaddingLeft() + getPaddingRight();
        int heightUsed = getPaddingTop() + getPaddingBottom();

        // Measure always-show children first.
        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = getChildAt(i);
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            if (lp.alwaysShow && child.getVisibility() != GONE) {
                measureChildWithMargins(child, widthSpec, widthPadding, heightSpec, heightUsed);
                heightUsed += lp.topMargin + child.getMeasuredHeight() + lp.bottomMargin;
            }
        }

        final int alwaysShowHeight = heightUsed;

        // And now the rest.
        for (int i = 0; i < childCount; i++) {
            final View child = getChildAt(i);
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            if (!lp.alwaysShow && child.getVisibility() != GONE) {
                measureChildWithMargins(child, widthSpec, widthPadding, heightSpec, heightUsed);
                heightUsed += lp.topMargin + child.getMeasuredHeight() + lp.bottomMargin;
            }
        }

        mCollapsibleHeight = Math.max(0,
                heightUsed - alwaysShowHeight - getMaxCollapsedHeight());
        mUncollapsibleHeight = heightUsed - mCollapsibleHeight;

        if (isLaidOut()) {
            final boolean isCollapsedOld = mCollapseOffset != 0;
            mCollapseOffset = Math.min(mCollapseOffset, mCollapsibleHeight);
            final boolean isCollapsedNew = mCollapseOffset != 0;
            if (isCollapsedOld != isCollapsedNew) {
                notifyViewAccessibilityStateChangedIfNeeded(
                        AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
            }
        } else {
            // Start out collapsed at first unless we restored state for otherwise
            mCollapseOffset = mOpenOnLayout ? 0 : mCollapsibleHeight;
        }

        mTopOffset = Math.max(0, heightSize - heightUsed) + (int) mCollapseOffset;

        setMeasuredDimension(sourceWidth, heightSize);
    
public booleanonNestedFling(android.view.View target, float velocityX, float velocityY, boolean consumed)

        if (!consumed && Math.abs(velocityY) > mMinFlingVelocity) {
            smoothScrollTo(velocityY > 0 ? 0 : mCollapsibleHeight, velocityY);
            return true;
        }
        return false;
    
public booleanonNestedPreFling(android.view.View target, float velocityX, float velocityY)

        if (velocityY > mMinFlingVelocity && mCollapseOffset != 0) {
            smoothScrollTo(0, velocityY);
            return true;
        }
        return false;
    
public booleanonNestedPrePerformAccessibilityAction(android.view.View target, int action, android.os.Bundle args)

        if (super.onNestedPrePerformAccessibilityAction(target, action, args)) {
            return true;
        }

        if (action == AccessibilityNodeInfo.ACTION_SCROLL_FORWARD && mCollapseOffset != 0) {
            smoothScrollTo(0, 0);
            return true;
        }
        return false;
    
public voidonNestedPreScroll(android.view.View target, int dx, int dy, int[] consumed)

        if (dy > 0) {
            consumed[1] = (int) -performDrag(-dy);
        }
    
public voidonNestedScroll(android.view.View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed)

        if (dyUnconsumed < 0) {
            performDrag(-dyUnconsumed);
        }
    
public voidonNestedScrollAccepted(android.view.View child, android.view.View target, int axes)

        super.onNestedScrollAccepted(child, target, axes);
    
protected voidonRestoreInstanceState(android.os.Parcelable state)

        final SavedState ss = (SavedState) state;
        super.onRestoreInstanceState(ss.getSuperState());
        mOpenOnLayout = ss.open;
    
protected android.os.ParcelableonSaveInstanceState()

        final SavedState ss = new SavedState(super.onSaveInstanceState());
        ss.open = mCollapsibleHeight > 0 && mCollapseOffset == 0;
        return ss;
    
private voidonSecondaryPointerUp(android.view.MotionEvent ev)

        final int pointerIndex = ev.getActionIndex();
        final int pointerId = ev.getPointerId(pointerIndex);
        if (pointerId == mActivePointerId) {
            // This was our active pointer going up. Choose a new
            // active pointer and adjust accordingly.
            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
            mInitialTouchX = ev.getX(newPointerIndex);
            mInitialTouchY = mLastTouchY = ev.getY(newPointerIndex);
            mActivePointerId = ev.getPointerId(newPointerIndex);
        }
    
public booleanonStartNestedScroll(android.view.View child, android.view.View target, int nestedScrollAxes)

        return (nestedScrollAxes & View.SCROLL_AXIS_VERTICAL) != 0;
    
public voidonStopNestedScroll(android.view.View child)

        super.onStopNestedScroll(child);
        if (mScroller.isFinished()) {
            smoothScrollTo(mCollapseOffset < mCollapsibleHeight / 2 ? 0 : mCollapsibleHeight, 0);
        }
    
public booleanonTouchEvent(android.view.MotionEvent ev)

        final int action = ev.getActionMasked();

        mVelocityTracker.addMovement(ev);

        boolean handled = false;
        switch (action) {
            case MotionEvent.ACTION_DOWN: {
                final float x = ev.getX();
                final float y = ev.getY();
                mInitialTouchX = x;
                mInitialTouchY = mLastTouchY = y;
                mActivePointerId = ev.getPointerId(0);
                final boolean hitView = findChildUnder(mInitialTouchX, mInitialTouchY) != null;
                handled = (!hitView && mOnDismissedListener != null) || mCollapsibleHeight > 0;
                mIsDragging = hitView && handled;
                abortAnimation();
            }
            break;

            case MotionEvent.ACTION_MOVE: {
                int index = ev.findPointerIndex(mActivePointerId);
                if (index < 0) {
                    Log.e(TAG, "Bad pointer id " + mActivePointerId + ", resetting");
                    index = 0;
                    mActivePointerId = ev.getPointerId(0);
                    mInitialTouchX = ev.getX();
                    mInitialTouchY = mLastTouchY = ev.getY();
                }
                final float x = ev.getX(index);
                final float y = ev.getY(index);
                if (!mIsDragging) {
                    final float dy = y - mInitialTouchY;
                    if (Math.abs(dy) > mTouchSlop && findChildUnder(x, y) != null) {
                        handled = mIsDragging = true;
                        mLastTouchY = Math.max(mLastTouchY - mTouchSlop,
                                Math.min(mLastTouchY + dy, mLastTouchY + mTouchSlop));
                    }
                }
                if (mIsDragging) {
                    final float dy = y - mLastTouchY;
                    performDrag(dy);
                }
                mLastTouchY = y;
            }
            break;

            case MotionEvent.ACTION_POINTER_DOWN: {
                final int pointerIndex = ev.getActionIndex();
                final int pointerId = ev.getPointerId(pointerIndex);
                mActivePointerId = pointerId;
                mInitialTouchX = ev.getX(pointerIndex);
                mInitialTouchY = mLastTouchY = ev.getY(pointerIndex);
            }
            break;

            case MotionEvent.ACTION_POINTER_UP: {
                onSecondaryPointerUp(ev);
            }
            break;

            case MotionEvent.ACTION_UP: {
                final boolean wasDragging = mIsDragging;
                mIsDragging = false;
                if (!wasDragging && findChildUnder(mInitialTouchX, mInitialTouchY) == null &&
                        findChildUnder(ev.getX(), ev.getY()) == null) {
                    if (mOnDismissedListener != null) {
                        dispatchOnDismissed();
                        resetTouch();
                        return true;
                    }
                }
                if (mOpenOnClick && Math.abs(ev.getX() - mInitialTouchX) < mTouchSlop &&
                        Math.abs(ev.getY() - mInitialTouchY) < mTouchSlop) {
                    smoothScrollTo(0, 0);
                    return true;
                }
                mVelocityTracker.computeCurrentVelocity(1000);
                final float yvel = mVelocityTracker.getYVelocity(mActivePointerId);
                if (Math.abs(yvel) > mMinFlingVelocity) {
                    if (mOnDismissedListener != null
                            && yvel > 0 && mCollapseOffset > mCollapsibleHeight) {
                        smoothScrollTo(mCollapsibleHeight + mUncollapsibleHeight, yvel);
                        mDismissOnScrollerFinished = true;
                    } else {
                        smoothScrollTo(yvel < 0 ? 0 : mCollapsibleHeight, yvel);
                    }
                } else {
                    smoothScrollTo(
                            mCollapseOffset < mCollapsibleHeight / 2 ? 0 : mCollapsibleHeight, 0);
                }
                resetTouch();
            }
            break;

            case MotionEvent.ACTION_CANCEL: {
                if (mIsDragging) {
                    smoothScrollTo(
                            mCollapseOffset < mCollapsibleHeight / 2 ? 0 : mCollapsibleHeight, 0);
                }
                resetTouch();
                return true;
            }
        }

        return handled;
    
public booleanperformAccessibilityAction(int action, android.os.Bundle arguments)

        if (super.performAccessibilityAction(action, arguments)) {
            return true;
        }

        if (action == AccessibilityNodeInfo.ACTION_SCROLL_FORWARD && mCollapseOffset != 0) {
            smoothScrollTo(0, 0);
            return true;
        }
        return false;
    
private floatperformDrag(float dy)

        final float newPos = Math.max(0, Math.min(mCollapseOffset + dy,
                mCollapsibleHeight + mUncollapsibleHeight));
        if (newPos != mCollapseOffset) {
            dy = newPos - mCollapseOffset;
            final int childCount = getChildCount();
            for (int i = 0; i < childCount; i++) {
                final View child = getChildAt(i);
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                if (!lp.ignoreOffset) {
                    child.offsetTopAndBottom((int) dy);
                }
            }
            final boolean isCollapsedOld = mCollapseOffset != 0;
            mCollapseOffset = newPos;
            mTopOffset += dy;
            final boolean isCollapsedNew = newPos != 0;
            if (isCollapsedOld != isCollapsedNew) {
                notifyViewAccessibilityStateChangedIfNeeded(
                        AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
            }
            postInvalidateOnAnimation();
            return dy;
        }
        return 0;
    
public voidrequestChildFocus(android.view.View child, android.view.View focused)

        super.requestChildFocus(child, focused);
        if (!isInTouchMode() && isDescendantClipped(focused)) {
            smoothScrollTo(0, 0);
        }
    
private voidresetTouch()

        mActivePointerId = MotionEvent.INVALID_POINTER_ID;
        mIsDragging = false;
        mOpenOnClick = false;
        mInitialTouchX = mInitialTouchY = mLastTouchY = 0;
        mVelocityTracker.clear();
    
public voidsetOnDismissedListener(com.android.internal.widget.ResolverDrawerLayout$OnDismissedListener listener)

        mOnDismissedListener = listener;
    
public voidsetSmallCollapsed(boolean smallCollapsed)

        mSmallCollapsed = smallCollapsed;
        requestLayout();
    
private voidsmoothScrollTo(int yOffset, float velocity)

        abortAnimation();
        final int sy = (int) mCollapseOffset;
        int dy = yOffset - sy;
        if (dy == 0) {
            return;
        }

        final int height = getHeight();
        final int halfHeight = height / 2;
        final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dy) / height);
        final float distance = halfHeight + halfHeight *
                distanceInfluenceForSnapDuration(distanceRatio);

        int duration = 0;
        velocity = Math.abs(velocity);
        if (velocity > 0) {
            duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
        } else {
            final float pageDelta = (float) Math.abs(dy) / height;
            duration = (int) ((pageDelta + 1) * 100);
        }
        duration = Math.min(duration, 300);

        mScroller.startScroll(0, sy, 0, dy, duration);
        postInvalidateOnAnimation();