转载请以连接形式标明出处: 本文出自:103style的博客java
code base on android-28
android
ACTION_DOWN
事件, 可是父控件拦截了 ACTION_MOVE
事件,子控件不会收到ACTION_CANCEL
。ACTION_DOWN
事件, 可是父控件拦截了 ACTION_UP
事件,当再次点击父控件,子控件会收到 ACTION_CANCEL
。即 子控件 在处理了 ACTION_DOWN
以后,若是没有收到 ACTION_UP
事件,则在下次点击父控件的时候会收到 ACTION_CANCEL
事件。bash
新建一个项目, 修改activity_main.xml
以下:ide
<?xml version="1.0" encoding="utf-8"?>
<包名.TestLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<包名.TestTextView
android:id="@+id/test"
android:layout_width="300dp"
android:layout_height="300dp"
android:layout_gravity="center"
android:background="@color/colorPrimary" />
</包名.TestLayout>
复制代码
添加 TestLayout.java
和 TestTextView.java
源码分析
public class TestLayout extends FrameLayout {
public TestLayout(@NonNull Context context) {
super(context);
}
public TestLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public TestLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (
ev.getAction() == MotionEvent.ACTION_MOVE
|| ev.getAction() == MotionEvent.ACTION_UP
) {
return true;
}
return super.dispatchTouchEvent(ev);
}
}
复制代码
public class TestTextView extends TextView {
private static final String TAG = "TestTextView";
public TestTextView(Context context) {
super(context);
}
public TestTextView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public TestTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e(TAG, System.currentTimeMillis() + " onTouchEvent: action = " + event.getAction());
return true;
}
}
复制代码
首先咱们注释 TestLayout
中 || ev.getAction() == MotionEvent.ACTION_UP
这句,运行点击中间的子控件,而后移动一下,再抬起,而后重复一次,打印日志以下:测试
TestTextView: 1571728312400 onTouchEvent: action = 0
TestTextView: 1571728314213 onTouchEvent: action = 1
TestTextView: 1571728316088 onTouchEvent: action = 0
TestTextView: 1571728318113 onTouchEvent: action = 1
复制代码
发现只收到 ACTION_DOWN
和 ACTION_UP
事件。ui
而后咱们把 TestLayout
中 || ev.getAction() == MotionEvent.ACTION_UP
这句恢复,重复操做,发现日志图下:spa
TestTextView: 1571728458713 onTouchEvent: action = 0
TestTextView: 1571728460379 onTouchEvent: action = 3
TestTextView: 1571728460379 onTouchEvent: action = 0
复制代码
根据时间戳咱们知道在第二次点击时,才会先收到ACTION_CANCEL
,再收到 ACTION_DOWN
。日志
第一种只拦截 ACTION_MOVE
咱们很好分析,由于事件是从上面一层一层传下来的,当上层拦截了 消耗了 ACTION_MOVE
事件以后,就不会往下传递了,咱们的操做是由一个 ACTION_DOWN
多个 ACTION_MOVE
和 一个 ACTION_UP
组成的, 因此子控件只能收到 ACTION_DOWN
和 ACTION_UP
。code
第二种由于拦截了ACTION_MOVE
和 ACTION_UP
事件,因此子控件只能收到 ACTION_DOWN
。
而后在每次收到 ACTION_DOWN
的时候,ViewGroup 都会去清除以前的状态。 即下面的 cancelAndClearTouchTargets(ev)
、dispatchTransformedTouchEvent(...)
、child.dispatchTouchEvent(event)
,即将 MotionEvent.ACTION_CANCEL
传给子控件 。
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);//清除以前的状态
}
}
}
private void cancelAndClearTouchTargets(MotionEvent event) {
if (mFirstTouchTarget != null) {
boolean syntheticEvent = false;
for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
//传递ACTION_CANCEL给子View
dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits);
}
}
}
private boolean dispatchTransformedTouchEvent(...) {
final boolean handled;
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
// cancel 为 true
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
//传递ACTION_CANCEL给子View
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
}
复制代码
那这个 mFirstTouchTarget
是什么,何时被赋值的呢?
private TouchTarget mFirstTouchTarget;
private static final class TouchTarget {
public View child;
public TouchTarget next;
}
复制代码
mFirstTouchTarget
是一个保存处理了事件的 子View
的 链表
结构。
赋值也在 dispatchTouchEvent
中,在 dispatchTransformedTouchEvent(...)
中 判断子View是否处理了事件,处理了则添加到 mFirstTouchTarget
链表的头部。
public boolean dispatchTouchEvent(MotionEvent ev) {
if (onFilterTouchEventForSecurity(ev)) {
if (!canceled && !intercepted) {
if (...) {
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(...);
final View child = getAndVerifyPreorderedView(...);
//判断子View是否处理了事件
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
//把处理了事件的子View 添加到链表头部
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
...
}
//判断子View是否处理了事件
private boolean dispatchTransformedTouchEvent(...) {
final boolean handled;
final int oldAction = event.getAction();
final MotionEvent transformedEvent;
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
handled = child.dispatchTouchEvent(transformedEvent);
}
transformedEvent.recycle();
return handled;
}
//添加到mFirstTouchTarget链表的头部
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
复制代码
因此流程大体就是, 当第一次点击 TestLayout
, 因为 TestTextView
消耗了 ACTION_DOWN
事件,而后被保存在 TestLayout
的 mFirstTouchTarget
链表中, 在第二次点击的时候,经过 cancelAndClearTouchTargets
方法,遍历 mFirstTouchTarget
链表,将 ACTION_CANCEL
事件传递给子view。
若是以为不错的话,请帮忙点个赞呗。
以上
扫描下面的二维码,关注个人公众号 Android1024, 点关注,不迷路。