Android 事件冲突处理逻辑手写实现
事件冲突是Android开发中常见的问题,主要发生在父子View或同级View之间对同一事件的竞争处理。下面我将手写实现几种常见的事件冲突处理方案。
一、基础冲突场景
假设有以下两种典型冲突场景:
- 横向滑动冲突:外层ViewGroup(如ViewPager)和内层View(如横向ListView)都处理横向滑动
- 垂直滑动冲突:ScrollView内部嵌套ListView
二、外部拦截法实现(父容器优先)
public class ParentView extends ViewGroup {
private int mLastX;
private int mLastY;
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean intercepted = false;
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
intercepted = false; // 必须返回false,否则子View无法接收事件
break;
case MotionEvent.ACTION_MOVE:
int deltaX = x - mLastX;
int deltaY = y - mLastY;
// 父容器决定拦截条件:横向滑动距离大于纵向
if (Math.abs(deltaX) > Math.abs(deltaY)) {
intercepted = true; // 拦截横向滑动
} else {
intercepted = false;
}
break;
case MotionEvent.ACTION_UP:
intercepted = false;
break;
}
mLastX = x;
mLastY = y;
return intercepted;
}
}
三、内部拦截法实现(子View优先)
1. 子View实现
public class ChildView extends View {
private int mLastX;
private int mLastY;
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
getParent().requestDisallowInterceptTouchEvent(true); // 禁止父容器拦截
break;
case MotionEvent.ACTION_MOVE:
int deltaX = x - mLastX;
int deltaY = y - mLastY;
// 子View决定何时允许父容器拦截
if (父容器需要处理该事件的条件) {
getParent().requestDisallowInterceptTouchEvent(false); // 允许父容器拦截
}
break;
case MotionEvent.ACTION_UP:
break;
}
mLastX = x;
mLastY = y;
return super.dispatchTouchEvent(event);
}
}
2. 父容器配合实现
public class ParentView extends ViewGroup {
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
return false; // 必须返回false
} else {
return true; // 其他情况默认拦截(但会被子View的requestDisallowIntercept影响)
}
}
}
四、滑动方向冲突解决方案
1. 水平/垂直滑动冲突判断
public class CustomViewPager extends ViewGroup {
private float mStartX;
private float mStartY;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean intercept = false;
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mStartX = ev.getX();
mStartY = ev.getY();
intercept = false; // 必须不拦截DOWN事件
break;
case MotionEvent.ACTION_MOVE:
float endX = ev.getX();
float endY = ev.getY();
float distanceX = Math.abs(endX - mStartX);
float distanceY = Math.abs(endY - mStartY);
// 横向滑动距离大于纵向,且横向距离超过阈值,则拦截
if (distanceX > distanceY && distanceX > touchSlop) {
intercept = true;
} else {
intercept = false;
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
intercept = false;
break;
}
return intercept;
}
}
2. 嵌套滑动冲突解决方案
public class NestedScrollView extends ViewGroup {
private float mLastY;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean intercept = false;
float y = ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastY = y;
intercept = false;
break;
case MotionEvent.ACTION_MOVE:
float dy = y - mLastY;
// 如果子View已经滚动到顶部且向下滑动,或者已经滚动到底部且向上滑动
if ((dy > 0 && isChildScrolledToTop()) ||
(dy < 0 && isChildScrolledToBottom())) {
intercept = true; // 拦截事件
}
break;
}
mLastY = y;
return intercept;
}
private boolean isChildScrolledToTop() {
// 判断子View是否已经滚动到顶部
return true; // 简化实现
}
private boolean isChildScrolledToBottom() {
// 判断子View是否已经滚动到底部
return true; // 简化实现
}
}
五、多点触控冲突处理
public class MultiTouchView extends View {
private SparseArray<PointF> mActivePointers = new SparseArray<>();
@Override
public boolean onTouchEvent(MotionEvent event) {
int pointerIndex = event.getActionIndex();
int pointerId = event.getPointerId(pointerIndex);
int maskedAction = event.getActionMasked();
switch (maskedAction) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN: {
PointF f = new PointF();
f.x = event.getX(pointerIndex);
f.y = event.getY(pointerIndex);
mActivePointers.put(pointerId, f);
break;
}
case MotionEvent.ACTION_MOVE: {
for (int i = 0; i < event.getPointerCount(); i++) {
PointF point = mActivePointers.get(event.getPointerId(i));
if (point != null) {
point.x = event.getX(i);
point.y = event.getY(i);
}
}
break;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
case MotionEvent.ACTION_CANCEL: {
mActivePointers.remove(pointerId);
break;
}
}
// 处理多点触控逻辑
if (mActivePointers.size() == 2) {
// 双指操作处理
handleTwoFingers();
}
return true;
}
private void handleTwoFingers() {
// 双指缩放或旋转逻辑
}
}
六、复杂冲突处理策略
1. 优先级队列策略
public class PriorityTouchHandler {
private List<View> mTouchTargets = new ArrayList<>();
public void addTarget(View view, int priority) {
mTouchTargets.add(view);
// 按优先级排序
Collections.sort(mTouchTargets, (v1, v2) -> {
// 比较优先级逻辑
return 0;
});
}
public boolean dispatchTouchEvent(MotionEvent event) {
for (View view : mTouchTargets) {
if (view.dispatchTouchEvent(event)) {
return true; // 高优先级View消费了事件
}
}
return false;
}
}
2. 事件代理策略
public class TouchDelegateGroup extends View {
private List<View> mDelegates = new ArrayList<>();
public void addDelegate(View delegate) {
mDelegates.add(delegate);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
// 将事件转换坐标后分发给各个代理View
for (View delegate : mDelegates) {
MotionEvent transformedEvent = transformEvent(event, delegate);
if (delegate.dispatchTouchEvent(transformedEvent)) {
return true;
}
}
return super.dispatchTouchEvent(event);
}
private MotionEvent transformEvent(MotionEvent event, View target) {
// 坐标转换逻辑
return event;
}
}
关键点总结
-
拦截时机:
- DOWN事件必须不拦截,否则后续事件无法传递到子View
- MOVE事件根据业务逻辑决定是否拦截
-
滑动冲突判断:
- 比较横向和纵向滑动距离
- 考虑touchSlop阈值(系统认为的最小滑动距离)
-
多点触控处理:
- 使用PointerId跟踪不同手指
- 注意ACTION_POINTER_DOWN/UP事件
-
性能优化:
- 避免在事件处理方法中创建对象
- 减少不必要的计算
以上实现可以根据具体业务需求进行调整和组合,实际开发中可能需要结合多种策略来解决复杂的事件冲突问题。












网友评论