利用ViewDragHelper自定義ViewGroup之上拉抽屜布局效果

科普一下

  • 2013年谷歌i/o大會上介紹了兩個新的layout: SlidingPaneLayout和DrawerLayout斜纪,現(xiàn)在這倆個類被廣泛的運(yùn)用铆隘,其實(shí)研究他們的源碼你會發(fā)現(xiàn)這兩個類都運(yùn)用了ViewDragHelper來處理拖動鸠真。ViewDragHelper是Framework中不為人知卻非常有用的一個工具馍刮。ViewDragHelper解決了android中手勢處理過于復(fù)雜的問題徊件,在DrawerLayout出現(xiàn)之前梭依,側(cè)滑菜單都是由第三方開源代碼實(shí)現(xiàn)的卖擅,其中著名的當(dāng)屬M(fèi)enuDrawer鸣奔,MenuDrawer重寫onTouchEvent方法來實(shí)現(xiàn)側(cè)滑效果,代碼量很大惩阶,實(shí)現(xiàn)邏輯也需要很大的耐心才能看懂挎狸。如果每個開發(fā)人員都從這么原始的步奏開始做起,那對于安卓生態(tài)是相當(dāng)不利的断楷。所以說ViewDragHelper等的出現(xiàn)反映了安卓開發(fā)框架已經(jīng)開始向成熟的方向邁進(jìn)锨匆。引用了網(wǎng)上的片段,其實(shí)說了那么多脐嫂,ViewDragHelper就如名字說的一樣统刮,一個拖拽View的幫助類。只要我們這些普通開發(fā)者使用這個類账千,就可以輕松實(shí)現(xiàn)以前寫了一大堆代碼才實(shí)現(xiàn)的效果侥蒙,代碼更簡潔,更優(yōu)雅匀奏。

ViewDragHelper基本用法

  • 創(chuàng)建ViewDragHelper鞭衩,建議在ViewGroup內(nèi)部創(chuàng)建使用,而不是在外面使用娃善。
//第二個參數(shù)設(shè)置滑動靈敏度
mViewDragHelper = ViewDragHelper.create(this, 1.0f,mCallback);
  • 看看mCallback回調(diào)接口ViewDragHelper.Callback论衍,到底有什么重要的方法,直接上源碼。
public static abstract class Callback {
        /**
         * 當(dāng)ViewDragHelper狀態(tài)發(fā)生變化時回調(diào)(IDLE,DRAGGING,SETTING[自動滾動時])
         * @see #STATE_IDLE
         * @see #STATE_DRAGGING
         * @see #STATE_SETTLING
         */
        public void onViewDragStateChanged(int state) {}

        /**
         * 當(dāng)captureView的位置發(fā)生改變時回調(diào)
         * @param changedView View whose position changed
         * @param left New X coordinate of the left edge of the view
         * @param top New Y coordinate of the top edge of the view
         * @param dx Change in X position from the last call
         * @param dy Change in Y position from the last call
         */
        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {}

        /**
         *當(dāng)CaptureView被捕獲時回調(diào)
         * @param capturedChild Child view that was captured
         * @param activePointerId Pointer id tracking the child capture
         */
        public void onViewCaptured(View capturedChild, int activePointerId) {}

        /**
         *手指釋放的時候回調(diào)
         *
         * @param releasedChild The captured child view now being released
         * @param xvel X velocity of the pointer as it left the screen in pixels per second.
         * @param yvel Y velocity of the pointer as it left the screen in pixels per second.
         */
        public void onViewReleased(View releasedChild, float xvel, float yvel) {}

        /**
         * 當(dāng)觸摸到邊界時回調(diào)聚磺。
         *
         * @param edgeFlags A combination of edge flags describing the edge(s) currently touched
         * @param pointerId ID of the pointer touching the described edge(s)
         * @see #EDGE_LEFT
         * @see #EDGE_TOP
         * @see #EDGE_RIGHT
         * @see #EDGE_BOTTOM
         */
        public void onEdgeTouched(int edgeFlags, int pointerId) {}

        /**
         *true的時候會鎖住當(dāng)前的邊界坯台,false則unLock。
         *
         * @param edgeFlags A combination of edge flags describing the edge(s) locked
         * @return true to lock the edge, false to leave it unlocked
         */
        public boolean onEdgeLock(int edgeFlags) {
            return false;
        }

        /**
         *
         *在邊界拖動時狀態(tài)回調(diào)
         * @param edgeFlags A combination of edge flags describing the edge(s) dragged
         * @param pointerId ID of the pointer touching the described edge(s)
         * @see #EDGE_LEFT
         * @see #EDGE_TOP
         * @see #EDGE_RIGHT
         * @see #EDGE_BOTTOM
         */
        public void onEdgeDragStarted(int edgeFlags, int pointerId) {}

        /**
         * 改變同一個坐標(biāo)(x,y)去尋找captureView位置的方法
         *
         * @param index the ordered position to query for
         * @return index of the view that should be ordered at position <code>index</code>
         */
        public int getOrderedChildIndex(int index) {
            return index;
        }

        /**
         * 
         *水平方向瘫寝,子View要是消耗事件蜒蕾,就要重寫此方法返回大于1的數(shù)稠炬。
         * @param child Child view to check
         * @return range of horizontal motion in pixels
         */
        public int getViewHorizontalDragRange(View child) {
            return 0;
        }

        /**
         *垂直方向,子View要是消耗事件咪啡,就要重寫此方法返回大于1的數(shù)首启。
         *
         * @param child Child view to check
         * @return range of vertical motion in pixels
         */
        public int getViewVerticalDragRange(View child) {
            return 0;
        }

        /**
         *tryCaptureView如何返回ture則表示可以捕獲該view即是哪個View可以滑動,哪個不可以滑動撤摸,在這個方
         *法里面控制
         * @param child Child the user is attempting to capture
         * @param pointerId ID of the pointer attempting the capture
         * @return true if capture should be allowed, false otherwise
         */
        public abstract boolean tryCaptureView(View child, int pointerId);

        /**
         * 水平滑動的邊界處理
         *
         *
         * @param child Child view being dragged
         * @param left Attempted motion along the X axis
         * @param dx Proposed change in position for left
         * @return The new clamped position for left
         */
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            return 0;
        }

        /**
         * 垂直滑動的邊界處理
         *
         *
         * @param child Child view being dragged
         * @param top Attempted motion along the Y axis
         * @param dy Proposed change in position for top
         * @return The new clamped position for top
         */
        public int clampViewPositionVertical(View child, int top, int dy) {
            return 0;
        }
    }

大概了解所有的回調(diào)方法后毅桃,回歸主題。
一個上拉抽屜布局准夷,我腦子里首先想到的就是這樣一個東東钥飞,圖1。

Paste_Image.png

既然是這樣冕象,新建一個PullUpDragLayout直接繼承ViewGroup代承,代碼如下:

/**
 * @作 用:上拉的抽屜布局
 * @創(chuàng) 建 人: linguoding 郵箱:linggoudingg@gmail.com
 * @日 期: 2016年11月16日  10:32
 */

public class PullUpDragLayout extends ViewGroup {
    private ViewDragHelper mViewDragHelper;//拖拽幫助類
    private View mBottomView;//底部內(nèi)容View
    private View mContentView;//內(nèi)容View
    private int mBottomBorderHeigth = 20;//底部邊界凸出的高度
    public PullUpDragLayout(Context context) {
        super(context);
    }

    public PullUpDragLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public PullUpDragLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onLayout(boolean b, int i, int i1, int i2, int i3) {
    }
}

PullUpDragLayout 有兩個子View,這兩個子View怎么創(chuàng)建渐扮?可以java代碼new,可以在xml布局里面寫掖棉,也可以自定義屬性墓律,引用屬性得到。new 的話就pass掉幔亥,這里直接使用后面兩種方式耻讽。

<declare-styleable name="PullUpDragLayout">
        <attr name="PullUpDrag_BottomView" format="reference"/><!--底部View-->
        <attr name="PullUpDrag_ContentView" format="reference"/><!--內(nèi)容View-->
        <attr name="PullUpDrag_BottomBorderHeigth" format="reference|integer|dimension"/><!--邊界高度-->
    </declare-styleable>

有了屬性,接下來就可以實(shí)例化mBottomView 帕棉, mContentView 针肥,還有得到邊界高度 mBottomBorderHeigth的值。代碼如下:

/**
 * @作 用:上拉的抽屜布局
 * @創(chuàng) 建 人: linguoding 郵箱:linggoudingg@gmail.com
 * @日 期: 2016年11月16日  10:32
 */
public class PullUpDragLayout extends ViewGroup {
    private ViewDragHelper mViewDragHelper;//拖拽幫助類
    private View mBottomView;//底部內(nèi)容View
    private View mContentView;//內(nèi)容View
    private LayoutInflater mLayoutInflater;
    private int mBottomBorderHeigth = 20;//底部邊界凸出的高度

    public PullUpDragLayout(Context context) {
        this(context, null, 0);
    }

    public PullUpDragLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public PullUpDragLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
        initCustomAttrs(context, attrs);
    }


    private void init(Context context) {
        mLayoutInflater = LayoutInflater.from(context);
    }

    /**
     * 初始化自定義屬性香伴,并實(shí)例化子View
     * @param context
     * @param attrs
     */
    private void initCustomAttrs(Context context, AttributeSet attrs) {
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.PullUpDragLayout);
        if (typedArray != null) {
            if (typedArray.hasValue(R.styleable.PullUpDragLayout_PullUpDrag_ContentView)) {
                inflateContentView(typedArray.getResourceId(R.styleable.PullUpDragLayout_PullUpDrag_ContentView, 0));
            }
            if (typedArray.hasValue(R.styleable.PullUpDragLayout_PullUpDrag_BottomView)) {
                inflateBottomView(typedArray.getResourceId(R.styleable.PullUpDragLayout_PullUpDrag_BottomView, 0));
            }
            if (typedArray.hasValue(R.styleable.PullUpDragLayout_PullUpDrag_BottomBorderHeigth)) {
                mBottomBorderHeigth = (int) typedArray.getDimension(R.styleable.PullUpDragLayout_PullUpDrag_BottomBorderHeigth, 20);
            }
            typedArray.recycle();
        }

    }

    private void inflateContentView(int resourceId) {
        mContentView = mLayoutInflater.inflate(resourceId, this, true);
    }

    private void inflateBottomView(int resourceId) {
        mBottomView = mLayoutInflater.inflate(resourceId, this, true);
    }


    @Override
    protected void onLayout(boolean b, int i, int i1, int i2, int i3) {

    }
}

實(shí)例化了子View后慰枕,PullUpDragLayout 既然是直接繼承ViewGroup,那肯定要計算子View,還有子View在PullUpDragLayout怎么布局即纲。那就要重寫onMeasure(int widthMeasureSpec, int heightMeasureSpec)具帮,還有onLayout(boolean changed, int left, int top, int right, int bottom)方法。

  • onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法計算子View的大小
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
             mContentView = getChildAt(0);
             mBottomView = getChildAt(1);
             measureChild(mBottomView, widthMeasureSpec, heightMeasureSpec);
             int bottomViewHeight = mBottomView.getMeasuredHeight();
             measureChild(mContentView, widthMeasureSpec, heightMeasureSpec);
             int contentHeight = mContentView.getMeasuredHeight();
             setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), bottomViewHeight +contentHeight  + getPaddingBottom() + getPaddingTop());

    }
  • onLayout(boolean changed, int left, int top, int right, int bottom)方法排列子View的位置
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        mContentView = getChildAt(0);
        mBottomView = getChildAt(1);
      
        mContentView.layout(getPaddingLeft(), getPaddingTop(), getWidth() - getPaddingRight(), mContentView.getMeasuredHeight());
        mBottomView.layout(getPaddingLeft(), mContentView.getHeight() - mBottomBorderHeigth, getWidth() - getPaddingRight(), getMeasuredHeight() - mBottomBorderHeigth);
      
    }

應(yīng)用ViewDragHelper實(shí)現(xiàn)上拉效果

到此低斋,就實(shí)現(xiàn)上面圖1的靜態(tài)效果啦蜂厅。想要底部可以上拉,或者點(diǎn)擊上拉到指定位置膊畴。那就要用到ViewDragHelper掘猿。在init()方法中創(chuàng)建ViewDragHelper.create(this, 1.0f,mCallback)。重寫onInterceptTouchEvent()唇跨、onTouchEvent稠通,將event事件交給ViewDragHelper攔截及處理衬衬,代碼如下:

    private void init(Context context) {
        mLayoutInflater = LayoutInflater.from(context);
        mViewDragHelper = ViewDragHelper.create(this, 1.0f, mCallback);
    }

    ViewDragHelper.Callback mCallback = new ViewDragHelper.Callback() {
        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            return mBottomView == child;
        }
    };
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return mViewDragHelper.shouldInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mViewDragHelper.processTouchEvent(event);
        return true;
    }

這樣底部抽屜就可以隨意滑動啦,但是這樣還遠(yuǎn)遠(yuǎn)沒有達(dá)到我要的效果采记。我們要實(shí)現(xiàn)以下效果

  • 指定方向滑動
  • 邊界檢測佣耐、加速度檢測
  • 手指抬起,自動展開/收縮
  • 點(diǎn)擊Button唧龄,展開/關(guān)閉
  • 監(jiān)聽展開/關(guān)閉兼砖,上拉過程發(fā)生改變時回調(diào)

慢慢來,下面一個一個地去實(shí)現(xiàn)既棺。

  • 指定方向滑動
    控制水平方向不滑動讽挟,上下滑動。重寫ViewDragHelper.Callback中的clampViewPositionHorizontal()方法丸冕,還有這個clampViewPositionVertical()耽梅。代碼如下:
        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            final int leftBound = getPaddingLeft();
            final int rightBound = getWidth() - mBottomView.getWidth() - leftBound;
            final int newLeft = Math.min(Math.max(left, leftBound), rightBound);

            return newLeft;
        }

        @Override
        public int clampViewPositionVertical(View child, int top, int dy) {
            int topBound = mContentView.getHeight() - mBottomView.getHeight();
            int bottomBound = mContentView.getHeight() - mBottomBorderHeigth;
            return Math.min(bottomBound, Math.max(top, topBound));
        }
  • 手指抬起,自動展開/收縮

重寫onViewReleased()方法:代碼如下:

        //手指釋放的時候回調(diào)
        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            if (releasedChild == mBottomView) {
                if (releasedChild.getY() < mBoundTopY || yvel <= -1000) {
                    mViewDragHelper.settleCapturedViewAt(mAutoBackTopPos.x, mAutoBackTopPos.y);
                    isOpen = true;
                    if (mOnStateListener != null) mOnStateListener.open();
                } else if (releasedChild.getY() >= mBoundTopY || yvel >= 1000) {
                    mViewDragHelper.settleCapturedViewAt(mAutoBackBottomPos.x, mAutoBackBottomPos.y);
                    isOpen = false;
                    if (mOnStateListener != null) mOnStateListener.close();
                }
                invalidate();
            }
        }
    };

      @Override 
      public void computeScroll() {
             if (mViewDragHelper.continueSettling(true)) { invalidate(); 
          }
      }
  • 點(diǎn)擊Button胖烛,展開/關(guān)閉
    /**
     * 切換底部View
     */
    public void toggleBottomView() {
        if (isOpen) {
            mViewDragHelper.smoothSlideViewTo(mBottomView, mAutoBackBottomPos.x, mAutoBackBottomPos.y);
            if (mOnStateListener != null) mOnStateListener.close();
        } else {
            mViewDragHelper.smoothSlideViewTo(mBottomView, mAutoBackTopPos.x, mAutoBackTopPos.y);
            if (mOnStateListener != null) mOnStateListener.open();
        }
        invalidate();
        isOpen = !isOpen;
    }

  • 監(jiān)聽展開/關(guān)閉眼姐,上拉過程發(fā)生改變時回調(diào)

定義兩個接口

    public void setOnStateListener(OnStateListener onStateListener) {
        mOnStateListener = onStateListener;
    }

    public void setScrollChageListener(OnScrollChageListener scrollChageListener) {
        mScrollChageListener = scrollChageListener;
    }

    public interface OnStateListener {
        void open();

        void close();
    }

    public interface OnScrollChageListener {
        void onScrollChange(float rate);
    }

重寫onViewPositionChanged 監(jiān)聽位置的改變,計算上拉過程的比率值0-1佩番。代碼如下:

        @Override
        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
            if (changedView == mBottomView) {
                float startPosition = mContentView.getHeight() - mBottomView.getHeight();
                float endPosition = mContentView.getHeight() - mBottomBorderHeigth;
                float totalLength = endPosition - startPosition;
                float rate = 1 - ((top - startPosition) / totalLength);
                if (mScrollChageListener != null) {
                    mScrollChageListener.onScrollChange(rate);
                }

            }
        }

到此众旗,整個過程就走一遍了。下面放上完整代碼:

/**
 * @作 用:上拉的抽屜布局
 * @創(chuàng) 建 人:  linguoding 郵箱:linggoudingg@gmail.com
 * @日 期: 2016年10月28日  14:13
 */

public class PullUpDragLayout extends ViewGroup {
    private ViewDragHelper mViewDragHelper;//拖拽幫助類
    private View mBottomView;//底部內(nèi)容View
    private View mContentView;//內(nèi)容View
    LayoutInflater mLayoutInflater;
    private int mBottomBorderHeigth = 20;//底部邊界凸出的高度

    private Point mAutoBackBottomPos = new Point();
    private Point mAutoBackTopPos = new Point();
    private int mBoundTopY;
    private boolean isOpen;
    private OnStateListener mOnStateListener;
    private OnScrollChageListener mScrollChageListener;


    public void setOnStateListener(OnStateListener onStateListener) {
        mOnStateListener = onStateListener;
    }

    public void setScrollChageListener(OnScrollChageListener scrollChageListener) {
        mScrollChageListener = scrollChageListener;
    }

    public interface OnStateListener {
        void open();

        void close();
    }

    public interface OnScrollChageListener {
        void onScrollChange(float rate);
    }


    public PullUpDragLayout(Context context) {
        this(context, null, 0);
    }

    public PullUpDragLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public PullUpDragLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
        initCustomAttrs(context, attrs);
    }

    private void init(Context context) {
        mLayoutInflater = LayoutInflater.from(context);
        mViewDragHelper = ViewDragHelper.create(this, 1.0f,mCallback);
    }

    ViewDragHelper.Callback mCallback = new ViewDragHelper.Callback() {
        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            return mBottomView == child;
        }

        @Override
        public int getViewHorizontalDragRange(View child) {
            return getMeasuredWidth() - child.getMeasuredWidth();
        }

        @Override
        public int getViewVerticalDragRange(View child) {
            return getMeasuredHeight() - child.getMeasuredHeight();
        }

        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            final int leftBound = getPaddingLeft();
            final int rightBound = getWidth() - mBottomView.getWidth() - leftBound;
            final int newLeft = Math.min(Math.max(left, leftBound), rightBound);

            return newLeft;
        }

        @Override
        public int clampViewPositionVertical(View child, int top, int dy) {
            int topBound = mContentView.getHeight() - mBottomView.getHeight();
            int bottomBound = mContentView.getHeight() - mBottomBorderHeigth;
            return Math.min(bottomBound, Math.max(top, topBound));
        }

        @Override
        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
            if (changedView == mBottomView) {
                float startPosition = mContentView.getHeight() - mBottomView.getHeight();
                float endPosition = mContentView.getHeight() - mBottomBorderHeigth;
                float totalLength = endPosition - startPosition;
                float rate = 1 - ((top - startPosition) / totalLength);
                if (mScrollChageListener != null) {
                    mScrollChageListener.onScrollChange(rate);
                }

            }
        }

        //手指釋放的時候回調(diào)
        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            if (releasedChild == mBottomView) {
                if (releasedChild.getY() < mBoundTopY || yvel <= -1000) {
                    mViewDragHelper.settleCapturedViewAt(mAutoBackTopPos.x, mAutoBackTopPos.y);
                    isOpen = true;
                    if (mOnStateListener != null) mOnStateListener.open();
                } else if (releasedChild.getY() >= mBoundTopY || yvel >= 1000) {
                    mViewDragHelper.settleCapturedViewAt(mAutoBackBottomPos.x, mAutoBackBottomPos.y);
                    isOpen = false;
                    if (mOnStateListener != null) mOnStateListener.close();
                }
                invalidate();
            }
        }


    };

    public boolean isOpen() {
        return isOpen;
    }

    /**
     * 切換底部View
     */
    public void toggleBottomView() {
        if (isOpen) {
            mViewDragHelper.smoothSlideViewTo(mBottomView, mAutoBackBottomPos.x, mAutoBackBottomPos.y);
            if (mOnStateListener != null) mOnStateListener.close();
        } else {
            mViewDragHelper.smoothSlideViewTo(mBottomView, mAutoBackTopPos.x, mAutoBackTopPos.y);
            if (mOnStateListener != null) mOnStateListener.open();
        }
        invalidate();
        isOpen = !isOpen;
    }


    private void initCustomAttrs(Context context, AttributeSet attrs) {
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.PullUpDragLayout);
        if (typedArray != null) {
            if (typedArray.hasValue(R.styleable.PullUpDragLayout_PullUpDrag_ContentView)) {
                inflateContentView(typedArray.getResourceId(R.styleable.PullUpDragLayout_PullUpDrag_ContentView, 0));
            }
            if (typedArray.hasValue(R.styleable.PullUpDragLayout_PullUpDrag_BottomView)) {
                inflateBottomView(typedArray.getResourceId(R.styleable.PullUpDragLayout_PullUpDrag_BottomView, 0));
            }
            if (typedArray.hasValue(R.styleable.PullUpDragLayout_PullUpDrag_BottomBorderHeigth)) {
                mBottomBorderHeigth = (int) typedArray.getDimension(R.styleable.PullUpDragLayout_PullUpDrag_BottomBorderHeigth, 20);
            }
            typedArray.recycle();
        }

    }

    private void inflateContentView(int resourceId) {
        mContentView = mLayoutInflater.inflate(resourceId, this, true);
    }

    private void inflateBottomView(int resourceId) {
        mBottomView = mLayoutInflater.inflate(resourceId, this, true);
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        mContentView = getChildAt(0);
        mBottomView = getChildAt(1);
        measureChild(mBottomView, widthMeasureSpec, heightMeasureSpec);
        int bottomViewHeight = mBottomView.getMeasuredHeight();

        measureChild(mContentView, widthMeasureSpec, heightMeasureSpec);
        int contentHeight = mContentView.getMeasuredHeight();
        setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), bottomViewHeight + contentHeight + getPaddingBottom() + getPaddingTop());


    }


    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        mContentView = getChildAt(0);
        mBottomView = getChildAt(1);
        mContentView.layout(getPaddingLeft(), getPaddingTop(), getWidth() - getPaddingRight(), mContentView.getMeasuredHeight());
        mBottomView.layout(getPaddingLeft(), mContentView.getHeight() - mBottomBorderHeigth, getWidth() - getPaddingRight(), getMeasuredHeight() - mBottomBorderHeigth);
        mAutoBackBottomPos.x = mBottomView.getLeft();
        mAutoBackBottomPos.y = mBottomView.getTop();

        mAutoBackTopPos.x = mBottomView.getLeft();
        mAutoBackTopPos.y = mContentView.getHeight() - mBottomView.getHeight();
        mBoundTopY = mContentView.getHeight() - mBottomView.getHeight() / 2;

    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return mViewDragHelper.shouldInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mViewDragHelper.processTouchEvent(event);
        return true;
    }

    @Override
    public void computeScroll() {
        if (mViewDragHelper.continueSettling(true)) {
            invalidate();
        }
    }
}

XML中使用:

    <com.xxx.widget.PullUpDragLayout
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/pull_up_drag_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:PullUpDrag_BottomBorderHeigth="30dp"
        app:PullUpDrag_ContentView="@layout/pp_content_view"
        app:PullUpDrag_BottomView="@layout/view_bottom"
        >
        <!--或者在這里趟畏,注意順序不能變 ContentView 再到 BottomView-->
   </com.xxx.widget.PullUpDragLayout>

效果大概可以是這樣的:

Paste_Image.png

展開時:

Paste_Image.png

大概就是這樣的...............................

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末贡歧,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子赋秀,更是在濱河造成了極大的恐慌利朵,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件猎莲,死亡現(xiàn)場離奇詭異绍弟,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)益眉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進(jìn)店門晌柬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人郭脂,你說我怎么就攤上這事年碘。” “怎么了展鸡?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵屿衅,是天一觀的道長。 經(jīng)常有香客問我莹弊,道長涤久,這世上最難降的妖魔是什么涡尘? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮响迂,結(jié)果婚禮上考抄,老公的妹妹穿的比我還像新娘。我一直安慰自己蔗彤,他們只是感情好川梅,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著然遏,像睡著了一般贫途。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上待侵,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天丢早,我揣著相機(jī)與錄音,去河邊找鬼秧倾。 笑死怨酝,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的那先。 我是一名探鬼主播凫碌,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼胃榕!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起瞄摊,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤勋又,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后换帜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體楔壤,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年惯驼,在試婚紗的時候發(fā)現(xiàn)自己被綠了蹲嚣。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡祟牲,死狀恐怖隙畜,靈堂內(nèi)的尸體忽然破棺而出档押,到底是詐尸還是另有隱情挖藏,我是刑警寧澤,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布腹泌,位于F島的核電站乡恕,受9級特大地震影響言询,放射性物質(zhì)發(fā)生泄漏俯萎。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一运杭、第九天 我趴在偏房一處隱蔽的房頂上張望夫啊。 院中可真熱鬧,春花似錦辆憔、人聲如沸撇眯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽叛本。三九已至,卻和暖如春彤钟,著一層夾襖步出監(jiān)牢的瞬間来候,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工逸雹, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留营搅,地道東北人。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓梆砸,卻偏偏與公主長得像转质,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子帖世,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評論 2 351

推薦閱讀更多精彩內(nèi)容