轉載請以連接形式標明出處: 本文出自: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, 點關注,不迷路。