鹅厂系列二 : 仿QQ侧拉删除

——-不知道说些啥..

嗯,前面我们仿着做了QQ的侧拉菜单,现在来做做侧拉删除,复习复习下我们ViewDragHelper的使用,先来看下效果吧
xiaoguo zhenghexiaoguo

右边的是整合上次的侧滑菜单效果,没看过想了解的朋友可以去看看侧滑菜单的实现http://blog.csdn.net/z8z87878/article/details/51731221
额,控件缓慢拖动判断距离是否打开或关闭也做了,后面再录着看效果吧,好了,现在开始我们的自定义侧拉删除吧.其实这个和上次那个侧拉滑动还是挺像的是不是,其实是这样的,像这种自定义拖动容器,就是那几个类那几个方法,所以我们在熟悉这几个类和方法的情况下,其实自定义这种拖动容器是并不难的.开始自定义前,我们的第一个问题就是继承那个容器控件,如果自己继承ViewGroup的话,是挺不好的,它要我们写更多的代码,所以我们应该去继承那些常用的容器控件,其实也没几个是吧.我一开始根据这个控件的特性想的是继承LineLayout,单个条目是没问题的,但是把它整合到ListView的时候出问题了,不能显示,从来没碰过这种情况,我当时就懵了….后来试着继承FrameLayout.可以.所以我们这个控件继承FrameLayout,这为什么继承lineLayout然后ListView不显示,其中有多少不为人知的秘密……请你们探索着然后告诉我….

实现

我们继承frameLayout要实现上面的效果,所以我们要重写onLayout布局方法

  @Override
        protected void onFinishInflate() {  //视图解析完成后调用,这时候就能拿到我们的孩子对象了
            super.onFinishInflate();

            mRightView = getChildAt(0);       //右边选项界面
            mMainView = getChildAt(1);         //主面板
        }

        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {  //测量完成后调用,这时我们可以拿到view的大小
            super.onSizeChanged(w, h, oldw, oldh);

            mMainWidth = mMainView.getMeasuredWidth();
            mMainHeight = mMainView.getMeasuredHeight();

            mRightWidth = mRightView.getMeasuredWidth();


            mRange = mRightWidth;                 //拖动范围
        }



    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);

        mRightView.layout(mMainWidth,0,mMainWidth + mRightWidth,mMainHeight);
    }

这样就实现我们图片中的效果了,静态初始位置布置妥当.我们就可以用我们的ViewDragHelper了来帮助我们处理拖动事件了,这前面侧滑菜单已经讲过了,这里提下大致过程,首选我们得到它的对象ViewDragHelper create(ViewGroup forParent, Callback cb)要写一个类继承它的内部类ViewDragHelper.CallBack, 其中tryCaptureView(View child, int pointerId)是抽象方法,我们必须实现,想处理拖动事件返回true,因为这是水平拖动,所以我们还要重写clampViewPositionHorizontal(View child, int left, int dx)它默认返回0,所以我们要判断left它给我们的建议值,我们要判断它的范围,我们再返回自己计算的left,然后,相应的还要竖直的,我们用不到不重写

 /**
         * @param child 拖动那个孩子
         * @param left  拖动建议值,view根据返回的建议值拖动,相对于左位负,相对于右为正,是view相对物理窗口左边的位置
         * @param dx    变化值
         * @return 默认返回0, 即不拖动
         */
        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {

            if (child == mMainView) {

                if (left > 0) {
                    left = 0;
                } else if (left < -mRange) {
                    left = -mRange;               //拖动范围
                }
            } else if (child == mRightView) {
                if (left < mMainWidth - mRightWidth) {
                    left = mMainWidth - mRightWidth;
                } else if (left > mMainWidth) {
                    left = mRange;
                }
            }

            return left;
        }

还要个方法叫onViewPositionChanged(View changedView, int left, int top, int dx, int dy) 重写这个方法可以帮助我们在一个view拖动的时候,我们动态layout另一个view达到跟着动的效果,也可以在这设置回调之类的东西

@Override
        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {


            if (changedView == mMainView) {

                mRightView.layout(mMainWidth + left, mRightView.getTop(), mMainWidth + left + mRightWidth, mRightView
                        .getBottom());

            } else if (changedView == mRightView) {
                mMainView.layout(mRightView.getLeft() - mMainWidth, 0, mRightView.getLeft(),
                        mMainHeight);
            }

            if (mOnDraeListener != null) {

                int mManLeft = mMainView.getLeft();

                if (!isOpen && mManLeft == - mRightWidth){     //打开状态,原来是打开的就不用重新回调了
                    mOnDraeListener.onOpen(mDragLayout);
                    isOpen = true;

                }else if (isOpen && mManLeft == 0){
                    mOnDraeListener.onclose(mDragLayout);
                    isOpen = false;
                    isStartOpen = false;
                }else{

                    mOnDraeListener.onDrag(changedView, left);  //接口回调

                    if (!isOpen && !isStartOpen){                //只有在关闭的情况下才是要开始打开了

                        mOnDraeListener.onStartOpen(mDragLayout);
                        isStartOpen = true;
                    }

                }




            }

            invalidate();
        }

接口回调,用来处理比如滑动条目的时候关闭其它已经打开的条目之类的


    public interface onDraeListener {

        void onDrag(View view, int left);

        void onOpen(DragLayout dragLayout);  //打开了

        void onclose(DragLayout dragLayout);  //关闭了

        void onStartOpen(DragLayout dragLayout); //开始打开,不一定打开
    }

    private onDraeListener mOnDraeListener;   //引用

    public void setOnDraeListener(onDraeListener onDraeListener) {  //由外部传个名字
        mOnDraeListener = onDraeListener;
    }

还有一个就是onViewReleased(View releasedChild, float xvel, float yvel)我们在这个方法里根据画出的距离或者滑的速度方向,判断我们是要打开,还是关闭.

 // xvel           //水平速度
        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {


            if (xvel < 0) {      //认为松手后还有左的,可以叫惯性速度么...就打开

                open();
            } else if (xvel == 0) {                                           //不确定是关闭还是打开状态

                if (isOpen) {
                    if (mMainView.getLeft() > -mRange / mRightChildeCount * (mRightChildeCount - 1)) {
                        close();                                         //打开状态,即向右滑超过三分之一关闭
                    } else {
                        open();
                    }
                } else {                                                 //关闭状态跟上面一样向左滑

                    if (mMainView.getLeft() > -mRange / mRightChildeCount) {

                        close();
                    } else {
                        open();
                    }
                }

            } else if (xvel > 0) {                                        //打开状态即向右滑
                close();
            }
        }


    }

    public void open() {
        mViewDragHelper.smoothSlideViewTo(mMainView, -mRange, 0);
        ViewCompat.postInvalidateOnAnimation(this);

    }

    public void close() {
        mViewDragHelper.smoothSlideViewTo(mMainView, 0, 0);
        ViewCompat.postInvalidateOnAnimation(this);

    }

    @Override
    public void computeScroll() {
        super.computeScroll();
        if (mViewDragHelper.continueSettling(true)) {
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }

这里讲下自定义属性mRightChildCount

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

        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.DragLayout, defStyleAttr, 0);
        mRightChildeCount = typedArray.getInt(R.styleable.DragLayout_rightChildCount, -1);
        if (mRightChildeCount < 2) {
            throw new IllegalArgumentException("DragLayout右面板孩子数最少为两个");
        }
        mViewDragHelper = ViewDragHelper.create(this, new MyCallBack());
        mDragLayout = this;
    }

其实我这里是没有必要自定义属性的,但我代码已经上传了,有兴趣的自己把自定义属性去了吧,既然我当初做了,就教不会的朋友一下怎么自定义属性的,其实我一开始也是不会的,但我们可以想想,那些控件的属性是怎么定义的呢,所以我到LineLayout的构造函数中去看了下,它是这样的

自定义属性

没见过猪跑,你吃过猪肉吧.属性是通过attrs来的,所以我们去找找SDK的attrs文件看看,路径是SDK\platforms\android-23\data\res\values,打开,我们找到LineLayout,是这样的

<declare-styleable name="LinearLayout">
        <!-- Should the layout be a column or a row?  Use "horizontal"
             for a row, "vertical" for a column.  The default is
             horizontal. -->
        <attr name="orientation" />
        <attr name="gravity" />
        <!-- When set to false, prevents the layout from aligning its children's
             baselines. This attribute is particularly useful when the children
             use different values for gravity. The default value is true. -->
        <attr name="baselineAligned" format="boolean" />
        <!-- When a linear layout is part of another layout that is baseline
          aligned, it can specify which of its children to baseline align to
          (that is, which child TextView).-->
        <attr name="baselineAlignedChildIndex" format="integer" min="0"/>
        <!-- Defines the maximum weight sum. If unspecified, the sum is computed
             by adding the layout_weight of all of the children. This can be
             used for instance to give a single child 50% of the total available
             space by giving it a layout_weight of 0.5 and setting the weightSum
             to 1.0. -->
        <attr name="weightSum" format="float" />
        <!-- When set to true, all children with a weight will be considered having
             the minimum size of the largest child. If false, all children are
             measured normally. -->
        <attr name="measureWithLargestChild" format="boolean" />
        <!-- Drawable to use as a vertical divider between buttons. -->
        <attr name="divider" />
        <!-- Setting for which dividers to show. -->
        <attr name="showDividers">
            <flag name="none" value="0" />
            <flag name="beginning" value="1" />
            <flag name="middle" value="2" />
            <flag name="end" value="4" />
        </attr>
        <!-- Size of padding on either end of a divider. -->
        <attr name="dividerPadding" format="dimension" />
    </declare-styleable>

所以我们写自己的attrs的一般格式为

<declare-styleable name="xxxxxx">

    <attr name="xxx" format="xxx"/>   

</declare-styleable>

即我是这样写的

<declare-styleable name="DragLayout">

        <attr name="rightChildCount" format="integer"></attr>

    </declare-styleable>

写好了这个类,我们还要把touch事件传给我们的ViewDragOpenHelper

@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {


        return mViewDragHelper.shouldInterceptTouchEvent(ev);  //交给我们的mViewDragHelper去判断该不该拦截
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        try {

            mViewDragHelper.processTouchEvent(event);               //交给我们的mViewDragHelper去操作触摸事件,事件被拦截有可能会出错,try一下
        } catch (Exception e) {
            e.printStackTrace();
        }
        return true;
    }

好了,来看看不整合到ListView的效果
效果

好吧,鼠标不好控制,刚开始的鼠标抬起带了点向左的速度所以打开了/没问题现在就可以整合到listView了,你会发现,打开后,滑右面板,向右滑动不了,为什么呢O(∩_∩)O~,因为和上一次侧拉一样,我们没有重写ViewDragHelper.CallBack中的这个方法

 @Override
        public int getViewHorizontalDragRange(View child) {  //水平移动范围,我们最好重写这个方法!不然碰到listView拖不动

            return mRange;
        }

这个方法一定要重写!!!!!!!!如果是竖直方向的就重写竖直方向的,不然碰到listView你就等着哭吧,其实我一开始是写了的,昨天晚上我写好了还没整到ListView上,说不信邪看看能不能滑动,然后今天上午因为一开始继承的是LineLayout的,listView不显示就把我整懵了,然后就忘了,将近一个小时候看代码才发现…我真是作死小能手….重写后就没问题了,这里再说明下滑动关闭条目的处理,我们用一个ArrayList在回调onOpen的时候存储我们的打开的条目,然后close的时候移除它,在onStartOpen的时候遍历的List关闭里面的条目,并清空,在ListView的滑动也是遍历这个list关闭所有条目,并清空,详情可以下载下来看看,下载地址http://download.csdn.net/detail/z8z87878/9557318
这里我贴下这个控件的完整代码

import android.content.Context;
import android.content.res.TypedArray;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;

import com.it.draglayout.R;

/**
 * Created by Root on 2016/6/22.
 */
public class DragLayout extends FrameLayout {

    private View mMainView;
    private View mRightView;
    private int mMainWidth;
    private int mRightWidth;
    private ViewDragHelper mViewDragHelper;
    private int mRange;
    private int mMainHeight;
    private boolean isOpen = false;
    private int mRightChildeCount;
    private boolean isStartOpen = false;

    DragLayout mDragLayout;

    public DragLayout(Context context) {
        this(context, null);
    }


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

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

        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.DragLayout, defStyleAttr, 0);
        mRightChildeCount = typedArray.getInt(R.styleable.DragLayout_rightChildCount, -1);
        if (mRightChildeCount < 2) {
            throw new IllegalArgumentException("DragLayout右面板孩子数最少为两个");
        }
        mViewDragHelper = ViewDragHelper.create(this, new MyCallBack());
        mDragLayout = this;
    }


        @Override
        protected void onFinishInflate() {  //视图解析完成后调用,这时候就能拿到我们的孩子对象了
            super.onFinishInflate();

            mRightView = getChildAt(0);       //右边选项界面
            mMainView = getChildAt(1);         //主面板
        }

        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {  //测量完成后调用,这时我们可以拿到view的大小
            super.onSizeChanged(w, h, oldw, oldh);

            mMainWidth = mMainView.getMeasuredWidth();
            mMainHeight = mMainView.getMeasuredHeight();

            mRightWidth = mRightView.getMeasuredWidth();


            mRange = mRightWidth;                 //拖动范围
        }



    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);

        mRightView.layout(mMainWidth,0,mMainWidth + mRightWidth,mMainHeight);
    }


    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {


        return mViewDragHelper.shouldInterceptTouchEvent(ev);  //交给我们的mViewDragHelper去判断该不该拦截
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        try {

            mViewDragHelper.processTouchEvent(event);               //交给我们的mViewDragHelper去操作触摸事件,事件被拦截有可能会出错,try一下
        } catch (Exception e) {
            e.printStackTrace();
        }
        return true;
    }

    class MyCallBack extends ViewDragHelper.Callback {

        @Override
        public boolean tryCaptureView(View child, int pointerId) {  //要求我们重写,返回true执行拖到事件,false不执行
            return true;
        }

        @Override
        public int getViewHorizontalDragRange(View child) {  //水平移动范围,我们最好重写这个方法!不然碰到listView拖不动

            return mRange;
        }

        /**
         * @param child 拖动那个孩子
         * @param left  拖动建议值,view根据返回的建议值拖动,相对于左位负,相对于右为正,是view相对物理窗口左边的位置
         * @param dx    变化值
         * @return 默认返回0, 即不拖动
         */
        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {

            if (child == mMainView) {

                if (left > 0) {
                    left = 0;
                } else if (left < -mRange) {
                    left = -mRange;               //拖动范围
                }
            } else if (child == mRightView) {
                if (left < mMainWidth - mRightWidth) {
                    left = mMainWidth - mRightWidth;
                } else if (left > mMainWidth) {
                    left = mRange;
                }
            }

            return left;
        }


        @Override
        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {


            if (changedView == mMainView) {

                mRightView.layout(mMainWidth + left, mRightView.getTop(), mMainWidth + left + mRightWidth, mRightView
                        .getBottom());

            } else if (changedView == mRightView) {
                mMainView.layout(mRightView.getLeft() - mMainWidth, 0, mRightView.getLeft(),
                        mMainHeight);
            }

            if (mOnDraeListener != null) {

                int mManLeft = mMainView.getLeft();

                if (!isOpen && mManLeft == - mRightWidth){     //打开状态,原来是打开的就不用重新回调了
                    mOnDraeListener.onOpen(mDragLayout);
                    isOpen = true;

                }else if (isOpen && mManLeft == 0){
                    mOnDraeListener.onclose(mDragLayout);
                    isOpen = false;
                    isStartOpen = false;
                }else{

                    mOnDraeListener.onDrag(changedView, left);  //接口回调

                    if (!isOpen && !isStartOpen){                //只有在关闭的情况下才是要开始打开了

                        mOnDraeListener.onStartOpen(mDragLayout);
                        isStartOpen = true;
                    }

                }




            }

            invalidate();
        }

        // xvel           //水平速度
        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {


            if (xvel < 0) {      //认为松手后还有左的,可以叫惯性速度么...就打开

                open();
            } else if (xvel == 0) {                                           //不确定是关闭还是打开状态

                if (isOpen) {
                    if (mMainView.getLeft() > -mRange / mRightChildeCount * (mRightChildeCount - 1)) {
                        close();                                         //打开状态,即向右滑超过三分之一关闭
                    } else {
                        open();
                    }
                } else {                                                 //关闭状态跟上面一样向左滑

                    if (mMainView.getLeft() > -mRange / mRightChildeCount) {

                        close();
                    } else {
                        open();
                    }
                }

            } else if (xvel > 0) {                                        //打开状态即向右滑
                close();
            }
        }


    }

    public void open() {
        mViewDragHelper.smoothSlideViewTo(mMainView, -mRange, 0);
        ViewCompat.postInvalidateOnAnimation(this);

    }

    public void close() {
        mViewDragHelper.smoothSlideViewTo(mMainView, 0, 0);
        ViewCompat.postInvalidateOnAnimation(this);

    }

    @Override
    public void computeScroll() {
        super.computeScroll();
        if (mViewDragHelper.continueSettling(true)) {
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }


    public boolean isOpen() {
        return isOpen;
    }


    public interface onDraeListener {

        void onDrag(View view, int left);

        void onOpen(DragLayout dragLayout);  //打开了

        void onclose(DragLayout dragLayout);  //关闭了

        void onStartOpen(DragLayout dragLayout); //开始打开,不一定打开
    }

    private onDraeListener mOnDraeListener;   //引用

    public void setOnDraeListener(onDraeListener onDraeListener) {  //由外部传个名字
        mOnDraeListener = onDraeListener;
    }
}

至于整合到我们上次写的QQ侧滑菜单,我是这样做的,先把那个ViewPager的onInterceptTouchEvent 和 onTouchEvent都返回false了,即它已经不能滑动了,然后把我们这个作为一个Fragment放到MyViewPage上去了,然后这还是会和侧滑产生滑动事件冲突的,向右滑的时候会滑菜单,因为SlideLayout是父容器嘛,事件被它拦截了,所以我在它的onInterceptTouchEvent做了一下修改

 private float mStartX = 0;
    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {

        if (celaIsOpen){                      //如果侧拉打开了,不拦截向右滑动事件

            switch (event.getAction()){

                case MotionEvent.ACTION_DOWN:
                    mStartX = event.getRawX();
                    break;

                case MotionEvent.ACTION_MOVE:
                    float movX = event.getRawX();
                    if (movX > mStartX){
                        return  false;
                    }
                    break;

                case MotionEvent.ACTION_UP:
                    mStartX = 0;
                    break;
            }
        }


        return mViewDragHelper.shouldInterceptTouchEvent(event);   //交给它去判断该不该拦截
    }

有兴趣的可以去这下载整合的代码http://download.csdn.net/detail/z8z87878/9557522

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值