仿QQ左滑删除

本篇文章主要谈谈QQ左滑删除效果的制作,本ScrollDeleteView跟QQ的实现思路有些差别,QQ的实现是将整块列表作为一个容器操作的,使得效果更加绚丽,更好的控制左滑删除的效果和事件分发冲突,本View不存在滑动冲突。
这里写图片描述
先讲解一下我的思路,这个列表中承载的是ScrollDeleteView,分为两个部分
1.cotentView;
2.隐藏的View;
那么我们怎么让其滑动起来?,使用scrollTo()和Scroller工具类来配合完成。
难点:
1.如何让隐藏的View获得down和up事件,因为我们需要点击,那么这两个事件是必不可少的。
2.如何是的item整体的点击事件能响应;
3.如何控制隐藏部分的显示和影藏;

一、重写onMeasure()和onLayout(),集成LinearLayour会简单一些,这边也是按照LinearLayout的逻辑进行操作的。

      //这里制定如下规则,第一个child作为content,后面的child作为附加视图
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //这里制定如下规则,第一个child作为content,后面的child作为附加视图
        int count = getChildCount();
        if (count == 0) return;
        int w = MeasureSpec.getSize(widthMeasureSpec);
        int wModel = MeasureSpec.getMode(widthMeasureSpec);
        int h = MeasureSpec.getSize(heightMeasureSpec);
        int hModel = MeasureSpec.getMode(heightMeasureSpec);
        measureChildren(widthMeasureSpec, heightMeasureSpec);
        //width
        if (wModel != MeasureSpec.EXACTLY)
            w = measureWidth();
        if (hModel != MeasureSpec.EXACTLY)
            h = measureHeight(count);
        setMeasuredDimension(w, h);
    }

    private int measureWidth() {
        int w = 0;
        View child = getChildAt(0);
        MarginLayoutParams lp;
        if (child.getLayoutParams() instanceof MarginLayoutParams) {
            lp = (MarginLayoutParams) child.getLayoutParams();
        } else {
            lp = new MarginLayoutParams(child.getLayoutParams());
        }
        //加上child的宽度和左右的Margin
        w += child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;

        //还要加上容器的左右的Padding值
        return w + getPaddingLeft() + getPaddingRight();
    }

    private int measureHeight(int count) {
        int h = 0;
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            MarginLayoutParams lp;

            if (child.getLayoutParams() instanceof MarginLayoutParams) {
                lp = (MarginLayoutParams) child.getLayoutParams();
            } else {
                lp = new MarginLayoutParams(child.getLayoutParams());
            }
            //要加上top和bottom的距离
            h = Math.max(h, child.getHeight() + lp.topMargin + lp.bottomMargin);
        }
        //加上top和bottom的padding的距离
        return h + getPaddingTop() + getPaddingBottom();
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        //这里制定如下规则,第一个child作为content,后面的child作为附加视图
        int count = getChildCount();
        if (count == 0) return;
        int left;
        int right;
        int top;
        int bottom;
        int currentW = getMeasuredWidth();
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            MarginLayoutParams lp;
            if (child.getLayoutParams() instanceof MarginLayoutParams) {
                lp = (MarginLayoutParams) child.getLayoutParams();
            } else {
                lp = new MarginLayoutParams(child.getLayoutParams());
            }

            if (i == 0)
                left = getPaddingLeft() + lp.leftMargin;
            else left = currentW + lp.leftMargin;

            top = getPaddingTop() + lp.topMargin;
            right = left + child.getMeasuredWidth();
            bottom = top + child.getMeasuredHeight();
            child.layout(left, top, right, bottom);
            currentW = right + lp.rightMargin;
        }
        //获得可滑动的最大边界
        MaxScrollX = currentW - getMeasuredWidth();
    }

现在我们已经绘制出了界面,接下来,就要考虑什么时候拦截事件了

二、事件的拦截

 @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
            //一般不拦截
                isIntercept = false;
                mTouchX = ev.getRawX();
                break;
            case MotionEvent.ACTION_MOVE:
            //分两种情况去拦截MineDistance 表示的是系统认为的最小滑动距离
                float distance = mTouchX - ev.getRawX();
                switch (mState) {
                    case HIDE:
                        if (distance >= MineDistance && distance <= MaxScrollX) isIntercept = true;
                        break;
                    case EXPAEND:
                        if (distance <= -MineDistance && distance >= -MaxScrollX)
                            isIntercept = true;
                        break;
                    default:
                        isIntercept = false;
                        break;
                }
                break;
            case MotionEvent.ACTION_UP:
            //必须不能拦截,如果要选择性拦截的话,太过于复杂,原因后面会讲到
                isIntercept = false;
                break;
        }
        return isIntercept;
    }

为什么UP事件不能拦截呢?因为当我们拦截了UP事件了,后面的隐藏View的点击事件将失效,原因是,点击事件的激发的条件是,UP和DOWN事件必须成对出现,否则将不会调用该方法,所以我们不能拦截DOWN事件。

三、移动View,重写OnTuchEvent方法

 @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //这里改变背景
                if (mState == HIDE)
                    setBackgroundColor(Color.BLUE);
                isclick = true;
                break;
            case MotionEvent.ACTION_MOVE:
                int distance = (int) (mTouchX - event.getRawX());
                if (Math.abs(distance) < MineDistance) {
                    isMove = false;
                } else {
                    isMove = true;
                    setBackgroundColor(Color.WHITE);
                }

                if (mState == HIDE && distance <= MaxScrollX && distance >= MineDistance)
                    scrollTo(distance, 0);
                if (mState == EXPAEND && distance >= -MaxScrollX && distance <= -MineDistance)
                    scrollTo(MaxScrollX + distance, 0);
                break;
            case MotionEvent.ACTION_UP:
                setBackgroundColor(Color.WHITE);
                if (isclick && !isMove) {
                    if (mOnAppendClickListener != null)
                        mOnAppendClickListener.clickBg();
                }
                if (getScrollX() >= MaxScrollX) {
                    mScroller.startScroll(getScrollX(), 0, -getScrollX(), 0, 500);
                    invalidate();
                    mState = HIDE;
                    break;
                }
                if (getScrollX() >= MaxScrollX / 2) {
                    mScroller.startScroll(getScrollX(), 0, MaxScrollX - getScrollX(), 0, 500);
                    invalidate();
                    mState = EXPAEND;
                } else {
                    mScroller.startScroll(getScrollX(), 0, -getScrollX(), 0, 500);
                    invalidate();
                    mState = HIDE;
                }
                break;
            case MotionEvent.ACTION_CANCEL:
                setBackgroundColor(Color.WHITE);
                isclick = false;
                //这里是事件丢失
                if (getScrollX() >= MaxScrollX / 2) {
                    mScroller.startScroll(getScrollX(), 0, MaxScrollX - getScrollX(), 0, 500);
                    invalidate();
                    mState = EXPAEND;
                } else {
                    mScroller.startScroll(getScrollX(), 0, -getScrollX(), 0, 500);
                    invalidate();
                    mState = HIDE;
                }
                break;
        }
        return true;
    }

这里说明一点,由于我们的滑动删除空间只是一个Item,事件响应范围比较窄,当事件序列中断的话,我们要处理滑动布局的滑动位置,要么恢复,要么我显示隐藏部分,不作处理的话很可能停在任何一个位置,UP事件是不会调用的,只会调用CANCLE这个事件。

四、处理点击事件,本容器内部的子View的点击事件好解决,让收到UP和DOWN事件就ok,但是本身的点击事件就不好处理了,在OnTuchEvent中消费了事件了,就不会触发点击事件的回调,如果要做条件判断的话,有点复杂,所以我在OnTuchEvent中档HIDE和UP和DOWN没有MOVE的情况下,而我们认为点击了该容器。

 public interface OnAppendClickListener {
        //点击隐藏部分的监听
        void click(View v, int position);
        //点击容器的监听   
        void clickBg();
    }

当我们点击了隐藏部分时,我们要将不就还原

public void hide() {
        scrollTo(0, 0);
        invalidate();
        mState = HIDE;
    }

这么下来,就基本完成了,欢迎留言讨论。
Github地址:https://github.com/yzzAndroid/ScrollDeleteView

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值