教你如何手撸一个slidingdrawer简易库

先看看最终实现的效果
这里写图片描述
写这个主要练习一下学习的自定义viewgroup和viewdraghelper,就当自己记录一下中间遇到的问题。
1. 重写viewgroup的基本步骤 自定义流式布局
2. viewdraghelper的基本用法 洪洋大神的博客
了解了这些,我们就来开始我们的slidingDrawer之旅 。

分析slidingDrawer效果,slidingdrawer包含两部分,一部分为content就是我们的主体内容,一般都是match_parent,另一部分就是我们的抽屉了,抽屉里面包含一个手柄handler的view,包括滑动方向,那么我们首先先定义几个属性

   <declare-styleable name="MyDrawer">
        <attr name="content" format="reference" /> //主体view
        <attr name="drag" format="reference" />    //拖动view
        <attr name="handler" format="reference" /> //手柄view
        <attr name="isOpen" format="boolean" /> //是否打开
        <attr name="dragtype"/> //拖动方向
    </declare-styleable>


    <attr name="dragtype">
        <enum name="left" value="0"/> //从左边滑出来
        <enum name="right" value="1"/> //从右边滑出来
        <enum name="top" value="2"/> //从顶部滑出来
        <enum name="bottom" value="3"/> //从底部滑出来
    </attr>

slidingDrawer只支持两个方向的滑动,我们支持4个方向,先定义出来,以后我们用到。
先上完整的代码然后我们再分析。

public class MyDrawer extends ViewGroup {

    private final String TAG = "MyDrawer";

    private static final int TYPE_LEFT = 0; //左边滑出
    private static final int TYPE_RIGHT = 1; //右边滑出
    private static final int TYPE_TOP = 2; //上边滑出
    private static final int TYPE_BOTTOM = 3; //下边滑出



    private ViewDragHelper viewDragHelper; //滑动处理的主要类


    /**  自定义属性CUSTOM START **/
    private boolean isOpen = true; //是否打开
    private int dragType = 3; //默认从下往上滑
    /**  自定义属性CUSTOM END **/


    private int mContentViewID = -1; //主体viewID
    private View mContentView; //主体view

    private int mDragContentViewID = -1; //要拖动的viewID
    private View mDragContextView; //要拖动的view

    private int mHandlerID = -1; //手柄ViewID
    private View mHandler; //手柄View


    private int myWidth = 0; //父类宽度
    private int myHeight = 0; //父类高度


    private int dragContentWidth = 0; //拖动view的宽度
    private int dragContentHeight = 0; //拖动view的高度

    private int handlerWidth = 0; //手柄宽度
    private int handlerHeight = 0; //手柄高度

    private int surPlusWidth = 0; //减去手柄剩余的宽度
    private int surPlusHeight = 0; //减去手柄剩余的高度

    private DefaultDragHelper defaultDragHelper;


    private MyDrawerListener myDrawerListener = null;

    //初始化拖动view的坐标
    private int intitLeft = 0;
    private int initRight = 0;
    private int initTop = 0;
    private int initBottom = 0;



    public interface MyDrawerListener{

        public void open(); //打开

        public void close(); //关闭

    }

    //打开
    public void open(){
        defaultDragHelper.open();
    }
    //关闭
    public void close(){
        defaultDragHelper.close();
    }

    public void setIsOpen(boolean isOpen){
        this.isOpen = isOpen;
    }

    public boolean getIsOpen(){
        return isOpen;
    }

    public void setMyDrawerListener(MyDrawerListener myDrawerListener) {
        this.myDrawerListener = myDrawerListener;
    }

    public View getHandlerView(){
        if(mHandlerID != -1){
            return mHandler;
        }
        return null;
    }

    public View getDragContextView(){
        if(mDragContentViewID != -1){
            return mDragContextView;
        }
        return null;
    }


    public MyDrawer(Context context) {
        super(context);
        init(null, 0);
    }

    public MyDrawer(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(attrs, 0);
    }

    public MyDrawer(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(attrs, defStyle);
    }



    private void init(AttributeSet attrs, int defStyle) {
        final TypedArray a = getContext().obtainStyledAttributes(
                attrs, R.styleable.MyDrawer, defStyle, 0);

        /**
         *
         * 下面这坨都是自定义属性 详情请看attrs_my_drawer解释 ~\(≧▽≦)/~
         */
        mContentViewID = a.getResourceId(R.styleable.MyDrawer_content,-1);

        mDragContentViewID = a.getResourceId(R.styleable.MyDrawer_drag,-1);

        mHandlerID = a.getResourceId(R.styleable.MyDrawer_handler, -1);

        isOpen = a.getBoolean(R.styleable.MyDrawer_isOpen, false); //默认为关闭

        dragType = a.getInt(R.styleable.MyDrawer_dragtype, TYPE_BOTTOM); //默认为从下边滑出

        defaultDragHelper = new DefaultDragHelper();
        viewDragHelper = ViewDragHelper.create(this, 1.0f, defaultDragHelper);

    }


    @Override
    public void computeScroll() {
        if(viewDragHelper.continueSettling(true)){
            postInvalidate();
        }
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();

        if(mContentViewID == -1){
            Log.e(TAG,TAG+"的content属性不能为空");
            return;
        }

        if(mDragContentViewID == -1){
            Log.e(TAG,TAG+"的drag属性不能为空");
            return;
        }

        //手柄可以为空
        if(mHandlerID != -1){
            mHandler = this.findViewById(mHandlerID);
        }

        mContentView = this.findViewById(mContentViewID);
        mDragContextView = this.findViewById(mDragContentViewID);

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        Log.e(TAG, "onMeasure");


        int measureWidth = measureWidth(widthMeasureSpec);
        int measureHeight = measureHeight(heightMeasureSpec);
        // 计算自定义的ViewGroup中所有子控件的大小
        measureChildren(widthMeasureSpec, heightMeasureSpec);
        // 设置自定义的控件MyViewGroup的大小
        setMeasuredDimension(measureWidth, measureHeight);

        MarginLayoutParams paramsDrag = (MarginLayoutParams)mDragContextView.getLayoutParams();
        final int contentWidthSpec = getChildMeasureSpec(widthMeasureSpec,paramsDrag.leftMargin+paramsDrag.rightMargin,paramsDrag.width);
        final int contentHeightSpec = getChildMeasureSpec(heightMeasureSpec, paramsDrag.topMargin + paramsDrag.bottomMargin, paramsDrag.height);
        mDragContextView.measure(contentWidthSpec,contentHeightSpec);




        MarginLayoutParams paramsContent = (MarginLayoutParams)mContentView.getLayoutParams();
        final int dragWidthSpec = getChildMeasureSpec(widthMeasureSpec, paramsContent.leftMargin + paramsContent.rightMargin, paramsContent.width);
        final int dragHeightSpec = getChildMeasureSpec(heightMeasureSpec, paramsContent.topMargin + paramsContent.bottomMargin, paramsContent.height);
        mContentView.measure(dragWidthSpec, dragHeightSpec);


    }


    private int measureWidth(int pWidthMeasureSpec) {
        int result = 0;
        int widthMode = MeasureSpec.getMode(pWidthMeasureSpec);// 得到模式
        int widthSize = MeasureSpec.getSize(pWidthMeasureSpec);// 得到尺寸

        switch (widthMode) {

            case MeasureSpec.AT_MOST:
            case MeasureSpec.EXACTLY:
                result = widthSize;
                break;
        }
        return result;
    }

    private int measureHeight(int pHeightMeasureSpec) {
        int result = 0;

        int heightMode = MeasureSpec.getMode(pHeightMeasureSpec);
        int heightSize = MeasureSpec.getSize(pHeightMeasureSpec);

        switch (heightMode) {
            case MeasureSpec.AT_MOST:
            case MeasureSpec.EXACTLY:
                result = heightSize;
                break;
        }
        return result;
    }


    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        Log.e(TAG,"onLayout");
        myWidth = getWidth();
        myHeight = getHeight();

        dragContentWidth = mDragContextView.getMeasuredWidth();
        dragContentHeight = mDragContextView.getMeasuredHeight();

        if(mHandler != null){
            handlerWidth = mHandler.getMeasuredWidth();
            handlerHeight = mHandler.getMeasuredHeight();
        }

        //减去手柄的宽度和高度
        surPlusWidth = dragContentWidth - handlerWidth;
        surPlusHeight = dragContentHeight - handlerHeight;



        //默认里面只有两个view

        //设置contentview的位置
        MarginLayoutParams contentParam = (MarginLayoutParams) mContentView.getLayoutParams();
        mContentView.layout(contentParam.leftMargin, contentParam.topMargin, contentParam.leftMargin+mContentView.getMeasuredWidth(),  contentParam.topMargin+mContentView.getMeasuredHeight());

        //判断滑动方向和默认是否打开来确定滑动view的具体位置
        MarginLayoutParams cParams = (MarginLayoutParams) mDragContextView.getLayoutParams();



        //计算坐标真烦
        if(dragType==TYPE_LEFT){
            if(isOpen){
                intitLeft = cParams.leftMargin;
                initRight = intitLeft + dragContentWidth;
            }else{
                intitLeft = -surPlusWidth;
                initRight = intitLeft + dragContentWidth;
            }
            initTop = cParams.topMargin;
            initBottom = initTop + dragContentHeight;
        }else if(dragType==TYPE_RIGHT){
            if(isOpen){
                intitLeft = myWidth-dragContentWidth;
                initRight = intitLeft + dragContentWidth;
            }else{
                intitLeft = myWidth-handlerWidth;
                initRight = intitLeft + dragContentWidth;
            }
            initTop = cParams.topMargin;
            initBottom = initTop + dragContentHeight;
        }else if(dragType==TYPE_TOP){
            if(isOpen){
                initTop = cParams.topMargin;
                initBottom = initTop+dragContentHeight;
            }else{
                initTop = -surPlusHeight;
                initBottom = initTop + dragContentHeight;
            }
            intitLeft = cParams.leftMargin;
            initRight = intitLeft + dragContentWidth;
        }else if(dragType==TYPE_BOTTOM){
            if(isOpen){
                initTop = myHeight - dragContentHeight;
                initBottom = initTop + dragContentHeight;
            }else{
                initTop = myHeight-handlerHeight;
                initBottom = myHeight + surPlusHeight;
            }
            intitLeft = cParams.leftMargin;
            initRight = intitLeft + dragContentWidth;
        }


         mDragContextView.layout(intitLeft, initTop, initRight, initBottom);


    }




    @Override
    protected LayoutParams generateDefaultLayoutParams()
    {
        return new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
    }

    public LayoutParams generateLayoutParams(AttributeSet attrs)
    {
        return new MarginLayoutParams(getContext(), attrs);
    }

    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p)
    {
        return new MarginLayoutParams(p);
    }


    private class DefaultDragHelper extends ViewDragHelper.Callback{

        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            if(child == mDragContextView){
                return true;
            }
            return false;
        }

        //手指释放的时候回调
        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            super.onViewReleased(releasedChild, xvel, yvel);

            if(dragType==TYPE_LEFT){
                int top = mDragContextView.getLeft();
                int critical = (int)(- surPlusWidth/2.0f);
                if(top>=critical){
                    //打开
                    open();
                }else{
                    close();
                }
            }

            if(dragType==TYPE_RIGHT){
                if(mDragContextView.getLeft()<=myWidth-dragContentWidth+(surPlusWidth/2.0f)){
                    //打开
                    open();
                }else{
                    close();
                }
            }

            if(dragType==TYPE_TOP){
                int top = mDragContextView.getTop();
                int critical = (int)(- surPlusHeight/2.0f);
                if(top>=critical){
                    //打开
                    open();
                }else{
                    close();
                }
            }

            if(dragType==TYPE_BOTTOM){
                int top = mDragContextView.getTop();
                int critical = (int)(myHeight - surPlusHeight/2.0f - handlerHeight);
                if(top<=critical){
                    //打开
                    open();
                }else{
                    close();
                }
            }

        }


        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            if(dragType==TYPE_LEFT){
                if(left<-surPlusWidth){
                    return -surPlusWidth;
                }else if(left>0){
                    return 0;
                }
                return left;
            }
            if(dragType==TYPE_RIGHT){
                if(left<myWidth-dragContentWidth){ //说明已经拖到左边了
                    return myWidth-dragContentWidth;
                }else if(left>myWidth - handlerWidth){ //说明已经拖到右边了
                    return myWidth - handlerWidth;
                }
                return left;
            }
            return intitLeft;
        }

        @Override
        public int clampViewPositionVertical(View child, int top, int dy) {
            if(dragType==TYPE_TOP){
                if(top<-surPlusHeight){
                    return -surPlusHeight;
                }else if(top>0){
                    return 0;
                }
                return top;
            }
            if(dragType==TYPE_BOTTOM){
                if(top<myHeight-dragContentHeight){ //说明已经拖到顶部了
                    return myHeight-dragContentHeight;
                }else if(top>myHeight - handlerHeight){ //说明已经拖到底部了
                    return myHeight - handlerHeight;
                }
                return top;
            }
            return initTop;
        }

        @Override
        public void onViewDragStateChanged(int state) {
            super.onViewDragStateChanged(state);
        }

        //没有这个方法拖动view里面的控件就不能点击了
        @Override
        public int getViewVerticalDragRange(View child) {
            return myHeight;
        }

        @Override
        public int getViewHorizontalDragRange(View child) {
            return myHeight;
        }


        //打开
        private void open(){
            int finalLeft = 0;
            int finalTop = 0;

            if(dragType==TYPE_LEFT){
                finalLeft = 0;
                finalTop = initTop;
            }

            if(dragType==TYPE_RIGHT){
                finalLeft = myWidth - dragContentWidth;
                finalTop = initTop;
            }

            if(dragType==TYPE_TOP){
                finalLeft = intitLeft;
                finalTop = 0;
            }

            if(dragType==TYPE_BOTTOM){
                finalLeft = intitLeft;
                finalTop = myHeight - dragContentHeight;
            }

            if(myDrawerListener != null && getIsOpen() == false){
                myDrawerListener.open();
            }

            isOpen = true;
            viewDragHelper.smoothSlideViewTo(mDragContextView,finalLeft,finalTop);
            invalidate();
        }

        //关闭
        private void close(){
            int finalLeft = 0;
            int finalTop = 0;

            if(dragType==TYPE_LEFT){
                finalLeft = -surPlusWidth;
                finalTop = initTop;
            }

            if(dragType==TYPE_RIGHT){
                finalLeft = myWidth - handlerWidth;
                finalTop = initTop;
            }

            if(dragType==TYPE_TOP){
                finalLeft = intitLeft;
                finalTop = -surPlusHeight;
            }

            if(dragType==TYPE_BOTTOM){
                finalLeft = intitLeft;
                finalTop = myHeight - handlerHeight;
            }

            if(myDrawerListener != null && getIsOpen() == true){
                myDrawerListener.close();
            }

            isOpen = false;
            viewDragHelper.smoothSlideViewTo(mDragContextView,finalLeft,finalTop);
            invalidate();
        }


    }


    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        final int action = MotionEventCompat.getActionMasked(event);
        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
            viewDragHelper.cancel();
            return false;
        }
        boolean flag=viewDragHelper.shouldInterceptTouchEvent(event);
        return flag;

    }

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


}

这里面最重要的两个方法为 onMeasure 和 onLayout
onMeasure

int measureWidth = measureWidth(widthMeasureSpec);
        int measureHeight = measureHeight(heightMeasureSpec);
        // 计算自定义的ViewGroup中所有子控件的大小
        measureChildren(widthMeasureSpec, heightMeasureSpec);
        // 设置自定义的控件MyViewGroup的大小
        setMeasuredDimension(measureWidth, measureHeight);


        MarginLayoutParams paramsDrag = (MarginLayoutParams)mDragContextView.getLayoutParams();
        final int contentWidthSpec = getChildMeasureSpec(widthMeasureSpec,paramsDrag.leftMargin+paramsDrag.rightMargin,paramsDrag.width);
        final int contentHeightSpec = getChildMeasureSpec(heightMeasureSpec, paramsDrag.topMargin + paramsDrag.bottomMargin, paramsDrag.height);
        mDragContextView.measure(contentWidthSpec,contentHeightSpec);



        MarginLayoutParams paramsContent = (MarginLayoutParams)mContentView.getLayoutParams();
        final int dragWidthSpec = getChildMeasureSpec(widthMeasureSpec, paramsContent.leftMargin + paramsContent.rightMargin, paramsContent.width);
        final int dragHeightSpec = getChildMeasureSpec(heightMeasureSpec, paramsContent.topMargin + paramsContent.bottomMargin, paramsContent.height);
        mContentView.measure(dragWidthSpec, dragHeightSpec);

getChildMeasureSpec必须计算好子view的大小,这里注意要算上子view的margin属性,不然你会发现子view的位置会有所偏差,因为我们默认只有两个子view所以就没有循环指定view的大小,简单粗暴。
mContentView和mDragContextView就是我们里面的两个子view。

接下来就是最最重要的onLayout方法了,指定所有子view的位置。
这时候view的宽度和高度必须算好然后设置,表达能力太差,可以移步洪洋大神的博客理解 >_>.

 //计算坐标真烦
        if(dragType==TYPE_LEFT){
            if(isOpen){
                intitLeft = cParams.leftMargin;
                initRight = intitLeft + dragContentWidth;
            }else{
                intitLeft = -surPlusWidth;
                initRight = intitLeft + dragContentWidth;
            }
            initTop = cParams.topMargin;
            initBottom = initTop + dragContentHeight;
        }else if(dragType==TYPE_RIGHT){
            if(isOpen){
                intitLeft = myWidth-dragContentWidth;
                initRight = intitLeft + dragContentWidth;
            }else{
                intitLeft = myWidth-handlerWidth;
                initRight = intitLeft + dragContentWidth;
            }
            initTop = cParams.topMargin;
            initBottom = initTop + dragContentHeight;
        }else if(dragType==TYPE_TOP){
            if(isOpen){
                initTop = cParams.topMargin;
                initBottom = initTop+dragContentHeight;
            }else{
                initTop = -surPlusHeight;
                initBottom = initTop + dragContentHeight;
            }
            intitLeft = cParams.leftMargin;
            initRight = intitLeft + dragContentWidth;
        }else if(dragType==TYPE_BOTTOM){
            if(isOpen){
                initTop = myHeight - dragContentHeight;
                initBottom = initTop + dragContentHeight;
            }else{
                initTop = myHeight-handlerHeight;
                initBottom = myHeight + surPlusHeight;
            }
            intitLeft = cParams.leftMargin;
            initRight = intitLeft + dragContentWidth;
        }


         mDragContextView.layout(intitLeft, initTop, initRight, initBottom);

这一坨东西都是在计算坐标,因为你设置的方向不同,它的坐标就不同,还要考虑初始化的时候是否打开,这边算起来蛋疼。

onViewReleased是我们手指离开要调用的方法,这里我们要判断手指离开的时候坐标与(dragView-handler)/ 2.0f 至于这里判断的时getLeft()还是getTop()就要根据设置的方向来进行判断了 ,然后算出是否打开or关闭,滑动到指定位置我们用的是viewDragHelper.smoothSlideViewTo方法。

其它的就没什么可讲的了。
一定要记得layout方法的坐标位置一定要算对!
一定要记得layout方法的坐标位置一定要算对!
一定要记得layout方法的坐标位置一定要算对!
重要的事情说三遍,算的不对会出现不能显示or显示有偏差的情况。
(最后dragview的跟布局最好用linearLayout。我发现用relativeLayout作为跟布局并且为右边时候子布局的layout_marginRight竟然不显示,这貌似是系统的一个bug,我记得drawerlayout什么的都建议子布局为linearLayout)

下载链接:点个start吧 里面有完整的gif演示
这里写图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值