从ListView的滑动删除了解事件分发机制与冲突

本文探讨了ListView中实现滑动删除手势的原理,涉及事件分发过程中的dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent方法。在滑动删除功能中,ACTION_DOWN事件传递到ListView,接着传递到自定义滑动item。ACTION_MOVE事件根据滑动方向决定是否继续传递,item根据滑动速度和距离执行相应的滑动操作。文章通过分析代码细节,展示了如何自定义ListView和item来实现这一功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ListView滑动删除手势:

  1. item 从右向左滑动
  2. item 内部点击事件

事件分发过程中的主要方法:

  1. boolean dispatchTouchEvent(MotionEvent ev):当事件能够传递到View时,那么该View的dispatchTouchEvent方法就会被调用,返回结果表示是否消耗当前事件
  2. boolean onInterceptTouchEvent(MotionEvent ev):在dispatchTouchEvent方法内调用,判断是否拦截某个事件,如果拦截,事件将不会再向下一层传递
  3. boolean onTouchEvent(MotionEvent ev):当onInterceptTouchEvent拦截事件后调用该方法,用来进行对应事件的处理

    上述三个方法的关系下面的伪代码解释的比较清楚:

public boolean dispatchTouchEvent(MotionEvent ev){
    boolean consume = false;
    if(onInterceptTouchEvent(ev){
        consume = onTouchEvent(ev);
    }else{
        consume = child.dispathcTouchEvent(ev);
    }
    return consume;
}

分析滑动删除功能

事件的传递过程主要看是否消费事件。根据滑动删除功能从右向左的滑动的触摸事件分析一下事件分发:

  1. 用户点击屏幕准备滑动item按钮时会产生一个ACTION_DOWN事件
  2. ACTION_DOWN事件传递到ListView时,ListView不消费、不拦截,记录初始位置,继续向下传递到自定义滑动item
  3. item 接收到ACTION_DOWN事件时,记住初始位置,用来后续测量滑动速度与距离
  4. 用户滑动时,传递一系列的ACTION_MOVE事件
  5. ACTION_MOVE事件传递到ListView时,ListView判断滑动方向,看是否向下s传递滑动,如果滑动方向不是从右向左,则不向下继续传递,如果是,则继续向下传递
  6. item接收到ACTION_MOVE事件时,判断滑动速度,滑动View

上述就是滑动事件的大概流程,下面分析一下具体的代码:

自定义 ListView

public class SlideListView extends ListView {
    private SlideItemLayout mCurrentItemView;
    /**
     * 记录之前滑动的位置
     */
    private int prePosition = -1;       

    public SlideListView(Context context) {
        super(context);
    }

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

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

    /**
     * 测量,主要处理 ListView 在 ScrollView 等试图中的高度问题
     * 
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
                MeasureSpec.AT_MOST);
        super.onMeasure(widthMeasureSpec, expandSpec);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //当之前有item滑动过,再次滑动另一item时重置前一个item
                int position = pointToPosition((int) ev.getX(), (int) ev.getY());
                if (position >= 0 && position != prePosition) {
                    View currentItemView = getChildAt(position - getFirstVisiblePosition());
                    if (mCurrentItemView != null) {
                        mCurrentItemView.reset();
                    }
                    mCurrentItemView = (SlideItemLayout) currentItemView;
                    prePosition = position;
                }
                break;
            case MotionEvent.ACTION_MOVE:
                //当设置状态为不能启用时不再向下传递滑动事件
                if (!isEnabled()) {
                    return true;
                }
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }
}

自定义item

public class SlideItemLayout extends ViewGroup {
    private float MAX_SLIDE_SPEED = 1000f;  //最大滑动速度
    private float deleteWidth;

    public SlideItemLayout(Context context) {
        super(context);
    }

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

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

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        // 计算自定义的ViewGroup中所有子控件的大小
        measureChildren(widthMeasureSpec, heightMeasureSpec);

        setMeasuredDimension(measureSize(widthMode, widthSize), measureSize(heightMode, heightSize));
    }

    /**
     * 测量高度与宽度
     *
     * @param mode
     * @return
     */
    private int measureSize(int mode, int s) {
        int size = 0;
        switch (mode) {
            case MeasureSpec.AT_MOST:
            case MeasureSpec.EXACTLY:
                size = s;
                break;
        }
        return size;
    }

    @Override
    protected void onLayout(boolean change, int l, int t, int r, int b) {
        int right = 0;
        int size = getChildCount();
        //获取删除按钮的宽度
        deleteWidth = getChildAt(size - 1).getMeasuredWidth();
        for (int i = 0; i < size; i++) {
            View view = getChildAt(i);

            int childHeight = view.getMeasuredHeight();
            int chileWidth = view.getMeasuredWidth();

            view.layout(right, 0, right + chileWidth, childHeight);
            right += chileWidth;
        }
    }

    private float startX = 0;
    private float endX = 0;

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                startX = ev.getX();
                break;
            case MotionEvent.ACTION_MOVE:
                if (slideSpeed(ev) >= MAX_SLIDE_SPEED) {
                    return true;
                }
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_MOVE:
                endX = event.getX();
                if (endX - startX < 0 && startX - endX <= deleteWidth) {      //从右向左滑动
                    scrollTo((int) (startX - endX), 0);
                }
                break;
            case MotionEvent.ACTION_UP:
                endX = event.getX();
                if (startX - endX < deleteWidth / 2) {
                    scrollTo(0, 0);
                } else {
                    scrollTo((int) deleteWidth, 0);
                }
                releaseVelocityTracker();
                break;
            case MotionEvent.ACTION_CANCEL:
                endX = event.getX();
                if (startX - endX < deleteWidth / 2) {
                    scrollTo(0, 0);
                } else {
                    scrollTo((int) deleteWidth, 0);
                }
                releaseVelocityTracker();
                break;
        }
        return super.onTouchEvent(event);
    }

    private VelocityTracker mVelocityTracker;

    /**
     * 计算侧滑速度
     *
     * @param event
     * @return
     */
    private float slideSpeed(MotionEvent event) {
        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
        mVelocityTracker.addMovement(event);

        final VelocityTracker velocityTracker = mVelocityTracker;
        velocityTracker.computeCurrentVelocity(1000, MAX_SLIDE_SPEED);

        return velocityTracker.getXVelocity();
    }

    /**
     * 释放 VelocityTracker
     */
    private void releaseVelocityTracker() {
        if (mVelocityTracker != null) {
            mVelocityTracker.clear();
            mVelocityTracker.recycle();
            mVelocityTracker = null;
        }
    }

    /**
     * 返回到初始状态
     */
    public void reset() {
        scrollTo(0, 0);
    }
}

具体实现效果

注:本文参考《Android开发艺术探索》—— 任玉刚

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值