RecycleView中按钮长按拖动返回ActionCancel

648 阅读2分钟

背景

最近在开发一个录音的功能,录音一般是button在Action_Down的时候开始录音,Action_Up或者Action_Cancel的时候停止录音。

这时候发现一个问题,当button在recycelview中的时候,如果手指的纵向Action_Move超过一定范围时,onTouchListener就会收到Action_Cancel事件阻止继续录音;而当组件在RelativeLayout或者LinearLayout的时候则不会。

分析问题

1.初步定位问题

通过对比RecycleView和RelativeLayout两个不同ViewGroup中相同View的响应不同,我们大致能定位到问题应该是出在RecycleView上了(万能的控制变量法 手动狗头)

现在的线索就是当button在RecycleView的时候,会收到一个Action_Cancel的回调,那问题应该是出在了RecycleVIew的事件分发上了,当超出一定范围的时候,他发送了一个Action_Cancel方法

2.RecycleView的onInterceptTouchEvent

我们来看看RecycleVIew的onInterceptTouchEvent主要做了些什么操作,主要看Action_Move的判断

public boolean onInterceptTouchEvent(MotionEvent e) {
            ...
            case 2:   // Action_Move
                int index = e.findPointerIndex(this.mScrollPointerId);
                if (index < 0) {
                    Log.e("RecyclerView", "Error processing scroll; pointer index for id " + this.mScrollPointerId + " not found. Did any MotionEvents get skipped?");
                    return false;
                }

                int x = (int)(e.getX(index) + 0.5F);
                int y = (int)(e.getY(index) + 0.5F);
                if (this.mScrollState != 1) {
                    int dx = x - this.mInitialTouchX;
                    int dy = y - this.mInitialTouchY;
                    boolean startScroll = false;
                    if (canScrollHorizontally && Math.abs(dx) > this.mTouchSlop) {
                        this.mLastTouchX = x;
                        startScroll = true;
                    }

                    if (canScrollVertically && Math.abs(dy) > this.mTouchSlop) {
                        this.mLastTouchY = y;
                        startScroll = true;
                    }

                    if (startScroll) {
                        this.setScrollState(1);
                    }
                }
                break;
                        ... 
            return this.mScrollState == 1;
        }

当ActionMove的时候,由于我们是纵向的RecycleView,所以他会进入​canScrollVertically && Math.abs(dy) > this.mTouchSlop​这句判断,这里我们就可以很明显的看到,当我们在自以为做语音长按的滑动的时候,由于拖动超过一定范围,RecycleView把他当做了滑动列表的操作了,所以mScrollState设置为了1,onInterceptTouchEvent return 了true,自己吃掉了事件。

解决方案

了解了问题所在,解决就很简单了,我是自定义了一个RecycleView加了一个标志位的判断,在按钮长按的时候阻止RecycleView拦截事件,长按结束后再放开

public class RateRecyclerView extends RecyclerView {
    public RateRecyclerView(@NonNull Context context) {
        super(context);
    }

    public RateRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public RateRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    private boolean needIntercept = true;

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return needIntercept && super.onInterceptTouchEvent(ev);
    }

    public void disableIntercept() {
        this.needIntercept = false;
    }

    public void enableIntercept() {
        this.needIntercept = true;
    }
}