背景
最近在开发一个录音的功能,录音一般是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;
}
}