事件傳遞方向
activity -> viewGroup -> view
事件的傳遞入口
事件觸發 -> 硬件 -> Native -> 通過JNI ->
Activity.dispatchTouchEvent() ->
PhoneWindow.superDispatchTouchEvent() -> DecorView.superDispatchTouchEvent() ->
ViewGroup.dispatchTouchEvent()
事件的開始
事件的觸發從手指觸摸屏幕開始,屏幕硬件接受到觸摸事件後,交由底層系統處理(native層)。native層再通過JNI通知Activity,觸發dispatchTouchEvent方法,開始事件在view中的一系列傳遞。
事件的傳遞
Activity對事件的傳遞最終是交給DecorView(頂層容器)處理,DecorView繼承自FrameLayout,也是一個ViewGroup。
事件的傳遞始終是在ViewGroup 和 View中逐層傳遞。
ViewGroup控制事件傳遞的3個方法
* dispatchTouchEvent;事件分發,向下傳遞事件
* onInterceptTouchEvent;事件攔截
* onTouchEvent;事件消費/處理
View控制事件傳遞的2個方法
* dispatchTouchEvent;事件分發,最終調用事件處理
* onTouchEvent;事件消費/處理
需要注意兩個dispatchTouchEvent方法:ViewGroup繼承自View,重寫了View的dispatchTouchEvent方法,主要功能是對 子View 或 子ViewGroup進行事件的傳遞;而View的dispatchTouchEvent方法,主要功能是調用事件處理的前期工作。
本文源碼版本,api:28
Activity中的傳遞路徑
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();//空方法,重寫用於事件觸發時被調用
}
//最終調用DecorView的dispatchTouchEvent()
//即ViewGroup的dispatchTouchEvent()
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
Activity的dispatchTouchEvent()分兩種情況:
事件被消費,return true
沒有被消費,調用activity的onTouchEvent方法
ViewGroup中的傳遞路徑
//ViewGroup的dispatchTouchEvent()中
final boolean intercepted;//攔截標記
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//獲取攔截方法中的值
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
在事件分發的過程中,先獲取是否需要攔截,調用onInterceptTouchEvent()。
拓展點
disallowIntercept :不允許攔截的標記。
子view可以調用父容器的/requestDisallowInterceptTouchEvent/方法對其進行修改。
//ViewGroup的dispatchTouchEvent()中
for (int i = childrenCount - 1; i >= 0; i—) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
。。。
//尋找可傳遞的子view代碼
。。。
//重點1
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
//重點2
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
。。。
}
//——————————dispatchTransformedTouchEvent()——————————
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
handled = child.dispatchTouchEvent(transformedEvent);
}
//——————————dispatchTransformedTouchEvent()——————————
遍歷查找可傳遞事件的子View。
重點1
在尋找到子View後,調用dispatchTransformedTouchEvent()。
子View如果爲空,就調用自己的父類方法
子View不爲空,就調用子View的事件分發,向下一層View傳遞事件。
子View繼續重複dispatchTouchEvent()方法,類似於遞歸方法的調用。
等待返回handled值。
舉個例子:當前有ViewGroupA,ViewGroupB,ViewA。ViewGroup包裹ViewGroupB,ViewGroupB包裹ViewA。
addTouchTarget方法
主要功能:記錄被消費的事件傳遞過程中每個view,形成完整的事件傳遞路徑。
下次的MOVE,UP事件等都只需要查找傳遞路徑即可分發事件,不必再重複查找View。
View的事件處理
//View的dispatchTouchEvent()
。。。
ListenerInfo li = mListenerInfo;//包含各種點擊,雙擊,長按等監聽
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
。。。
首先判斷ListenerInfo是否爲空,ListenerInfo中包含各種點擊,雙擊,長按等監聽。所以這類監聽的優先級是高於onTouchEvent。
在沒有設置監聽的情況下,調用onTouchEvent方法,如果onTouchEvent方法進行了消費,返回true。
View的/dispatchTouchEvent/返回的true,被父容器ViewGroup的/dispatchTransformedTouchEvent/接收到,一路繼續向上傳遞,最終完成了事件路徑的記錄。
事件傳遞的完整路徑
總結
事件的傳遞迴顧
- Activity接受事件,交給ViewGroup處理。
- ViewGroup遍歷子View,遞歸查找被消費的子View。
- 記錄遞歸handled=true的View,形成完整的事件傳遞鏈。
- 其餘一系列事件直接經過 傳遞鏈 傳遞 事件。
ViewGroup的3個方法和View的2個方法之間的傳遞過程比較簡單。
關鍵點是ViewGroup的向下傳遞,遞歸查找消費子View。
最後
如果你看到了這裏,覺得文章寫得不錯就給個讚唄!歡迎大家評論討論!如果你覺得那裏值得改進的,請給我留言。一定會認真查詢,修正不足,定期免費分享技術乾貨。喜歡的小夥伴可以關注一下哦。謝謝!