- 事件传递每次都要递归查找处理事件的View吗?
- ACTION_CANCEL存在的意义?
- 父View再那个事件拦截子View?
- 子View再那个时机告诉父View不要对自己的事件进行拦截呢?
// ViewGroup父容器
public class MyLinearLayout extends LinearLayout {
public MyLinearLayout(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
Log.e(Constant.TAG, this.getClass().getName() + " onTouchEvent + " + event.getAction());
// 需要注意的是父容器不能拦截DOWN事件,如果拦截了就没子View啥事了
if (event.getAction() == MotionEvent.ACTION_DOWN) {
return super.onInterceptHoverEvent(event);
} else if (event.getAction() == MotionEvent.ACTION_MOVE) {
// 父容器拦截MOVE事件的时候改父容器的mFirstTarget = TouchTarget(child = MyButton)
reflectTouchTargetInstance(this, "onInterceptTouchEvent");
return true;
} else {
return true;
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// reflectTouchTargetInstance(this, "onTouchEvent");
Log.e(Constant.TAG, this.getClass().getName() + " onTouchEvent + " + event.getAction());
return super.onTouchEvent(event);
}
public static void reflectTouchTargetInstance(View _view, String tag) {
try {
Field filed = _view.getClass().getSuperclass().getSuperclass().getDeclaredField("mFirstTouchTarget");
filed.setAccessible(true);
Object obj = filed.get(_view);
Log.e(Constant.TAG, _view.getClass().getName() + " " + tag + "-------" + obj.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 子View
public class MyButton extends Button {
public MyButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e(Constant.TAG, this.getClass().getName() + " onTouchEvent + " + event.getAction());
return super.onTouchEvent(event);
}
}
运行结果
其中
MyLinearLayout onTouchEvent + 0
MyButton onTouchEvent + 0
MyLinearLayout onTouchEvent + 2
MyLinearLayout onInterceptTouchEvent-------android.view.ViewGroup$TouchTarget@41f7bc9
MyButton onTouchEvent + 3
MyLinearLayout onTouchEvent + 2
MyLinearLayout onTouchEvent + 2
MyLinearLayout onTouchEvent + 1
// 0 1 2 3分别代表
public static final int ACTION_DOWN = 0;
public static final int ACTION_UP = 1;
public static final int ACTION_MOVE = 2;
public static final int ACTION_CANCEL = 3;
- mFirstTouchTaget
result = {ViewGroup$TouchTarget@10336}
child = {MyButton@10340} "MyButton{ead02f0 VFED..C.. ...P.... 0,0-277,96 #7f080315 app:id/show_fragment}"
next = null
pointerIdBits = 1
shadow$_klass_ = {Class@2881} "class android.view.ViewGroup$TouchTarget"
shadow$_monitor_ = -2078311479
结论
- 当父容器拦截了子View的MOVE事件,会给子View发送一个ACTION_CANCEL事件
- 当从当前View的MVOE事件的时候移动到其它区域该View也会收到ACTION_CANCEL事件
// 带着结论看一下源代码
public boolean dispatchTouchEvent(MotionEvent ev) {
if (!canceled && !intercepted) {
// 不拦截事件的处理逻辑忽略
}
// Dispatch to touch targets.
// 由于当前的事件是MOVE事件,mFirstTouchTarget肯定有值了,这一点从上面的log也可以验证
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// Dispatch to touch targets, excluding the new touch target if we already
// dispatched to it. Cancel touch targets if necessary.
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
// 当前拦截 intercepted = true
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
// taget.child = MyButton
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
// Update list of touch targets for pointer up or cancel, if needed.
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
final int actionIndex = ev.getActionIndex();
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
removePointersFromTouchTargets(idBitsToRemove);
}
}
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}
- dispatchTransformedTouchEvent
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// Canceling motions is a special case. We don't need to perform any transformations
// or filtering. The important part is the action, not the contents.
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
}
mFirstTarget的链表
// 获取当前DecorView
((Activity)_view.getContext()).getWindow().getDecorView()
result = {DecorView@10368} "DecorView@6fe3e02[MainActivity]"
mFirstTouchTarget = {ViewGroup$TouchTarget@10432}
child = {LinearLayout@10416} "android.widget.LinearLayout{e16384e V.E...... ........ 0,0-720,1424}"
mFirstTouchTarget = {ViewGroup$TouchTarget@10466}
child = {FrameLayout@10490} "android.widget.FrameLayout{bddb6f3 V.E...... ........ 0,55-720,1424}"
mFirstTouchTarget = {ViewGroup$TouchTarget@10826}
child = {ActionBarOverlayLayout@11515} "androidx.appcompat.widget.ActionBarOverlayLayout{26242e3 V.E...... ........ 0,0-720,1369 #7f0800f8 app:id/decor_content_parent}"
mFirstTouchTarget = {ViewGroup$TouchTarget@11537}
child = {ContentFrameLayout@11524} "androidx.appcompat.widget.ContentFrameLayout{96c2a3f V.E #1020002 android:id/content}"
mFirstTouchTarget = {ViewGroup$TouchTarget@11566}
child = {NestedScrollView@11579} "androidx.core.widget.NestedScrollView{ef15ff8 VFED..... ........ 0,0-720,1257}"
mFirstTouchTarget = {ViewGroup$TouchTarget@11593}
child = {LinearLayout@11604} "android.widget.LinearLayout{63f2237 V.E...... ........ 0,0-720,1355}"
mFirstTouchTarget = {ViewGroup$TouchTarget@11610}
child = {ConstraintLayout@11619} "androidx.constraintlayout.widget.ConstraintLayout{324350d V.E...... ........ 0,0-720,1355}"
mFirstTouchTarget = {ViewGroup$TouchTarget@11629}
child = {MyLinearLayout@10328} "MyLinearLayout{3dff6f9 V.E...... ........ 0,0-720,1355}"
mFirstTouchTarget = {ViewGroup$TouchTarget@10336}
child = {MyButton@10340} "MyButton{ead02f0 VFED..C.. ...P.... 0,0-277,96 #7f080315 app:id/show_fragment}"
next = null
pointerIdBits = 1
next = null
pointerIdBits = 1
next = null
pointerIdBits = 1
next = null
next = null
pointerIdBits = 1
next = null
next = null
pointerIdBits = 1
next = null
pointerIdBits = 1
next = null
总结
- 当ACTION_DOWN事件之后的事件传递都是寻找mFirstTouchTarget找到目标View来提高查找速度
- 当父View拦截子View的MOVE事件之后,子View会收到ACTION_CANCEL事件做善后处理
- 当前View的MVOE事件移出当前View的区域,当前View也会收到ACTION_CANCEL事件
- ACTION_DOWN是事件序列的开始,所以父View不能要DOWN事件对子View进行拦截,否则子View无法收到事件
- 子View需要在ACTION_DOWN事件告诉父View不要对自己进行事件拦截,其它事件被父View拦截了,无法到达到子View也无法禁止父View进行拦截了。