網站首頁 編程語言 正文
- Android事件類型
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
break;
case MotionEvent.ACTION_CANCEL:
break;
}
return true;
}
如上圖所示android主要有四種事件類型,Down、Move、Up、Cancel。
在一次事件流中:
Down觸發一次,手指在屏幕按下時。
Move事件會觸發0次或多次,手指在屏幕上滑動時。
Up事件會觸發0次或一次,手指在屏幕抬起時。
Cancel事件會觸發0次或一次,滑動超出空間邊界時。
- android App層事件分發機制調用鏈是怎么樣的?
App層事件分發的入口在Activity中,dispatchTouchEvent是在Framework層的WindowManagerService掉用的,WMS中的事件是來源于Native層,Linux層,這塊的事件傳遞有機會在寫文章進行分析,這篇文章主要分析Android事件分發機制在App成是如何展現的。
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
getWindow()返回的是PhoneWindow對象。
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
mDecor是DecorView,是PhoneWindow的一個內部類,繼承FrameLayout。
FrameLayout繼承自ViewGroup,接下來看事件傳遞在ViewGroup中是如何處理的。事件傳遞的主要邏輯就在ViewGroup中。
接下來看ViewGroup,dispatchToucheEvent(MotionEvent ev)是如何對事件進行分發的。
public class ViewGroup {
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Check for interception.
//按下后,第一次調用時actionMasked=down,mFirstTouchTarget=null
//一直按著 actionMasked=move, 如果mFirstTouchTarget!=null說明有子view處理了touch事件
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//調用子類復寫的方法 如果為true表示攔截,false表示不攔截
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.
//如果沒有子view處理touch事件,并且不是第一次按下的down事件,group會對touch事件進行攔截
intercepted = true;
}
TouchTarget newTouchTarget = null;
//第一次按下時,判斷view是否處理事件的標志
boolean alreadyDispatchedToNewTouchTarget = false;
//如果不攔截,則進入事件分發
if (!canceled && !intercepted) {
//注意,只有ACTION_DOWN時才會遍歷子View
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final View[] children = mChildren;
//開始遍歷子view
for (int i = childrenCount - 1; i >= 0; i--) {
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
//isTransformedTouchPointInView 通過比對坐標,來判斷事件是否落在了child上,
// true是,false不是
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
// 如果事件沒有落在child上,則跳過此次循環,執行下一次循環
continue;
}
//通過遍歷TouchTarget鏈表查找TouchTarget,如果找到了,則break跳出循環
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
//第一次進來newTouchTarget==null,進入下面的邏輯
//如果dispatchTransformedTouchEvent 返回true,表示子view對事件進行了處理,則跳出此循環,
// 不會再向子view進行分發
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
//將child緩存到TouchTarget的鏈表中
newTouchTarget = addTouchTarget(child, idBitsToAssign);
//注意這個標志位,true,下面的邏輯會用到
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
if (preorderedList != null) preorderedList.clear();
}
}
}
// Dispatch to touch targets.
//ACTION_DOWN和ACTION_MOVE 的時都會執行下面邏輯
//mFirstTouchTarget==null 說明沒有子View處理touch事件,dispatchTransformedTouchEvent的第三個參數為null。
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 鏈表進行遍歷,TouchTarget里緩存了處理事件的view,
//一次完整事件,move會調用多次,所以在ACTION_DOWN時找到的對應的view,緩存起來,
// 而不是每次都遍歷ViewGroup的子View,這樣太損壞性能
TouchTarget target = mFirstTouchTarget;
//開始對鏈表進行遍歷,為啥是一個鏈表呢,這是為了進行多點觸摸,會有多個view響應觸摸事件。
while (target != null) {
final TouchTarget next = target.next;
//alreadyDispatchedToNewTouchTarget ==true ,并且緩存的target和newTouchTarget是同一個
//直接將handled置為true,防止ACTION_DOWN時dispatchTransformedTouchEvent方法重復調用
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
}
target = next;
}
}
//將handled 返回dispatchTouchEvent的調用者。
return handled;
}
/**
* Gets the touch target for specified child view.
* Returns null if not found.
* 通過遍歷TouchTarget 鏈表,找到touch target
*/
private TouchTarget getTouchTarget(@NonNull View child) {
for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
if (target.child == child) {
return target;
}
}
return null;
}
/**
* Adds a touch target for specified child to the beginning of the list.
* Assumes the target child is not already present.
* 將TouchTarget和view進行關聯,并且把這個TouchTarget放在鏈表的頭部
*/
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// 只看最關鍵的兩行代碼
if (child == null) {
//如果child為null,沒有子view處理touch事件,則調用父類View的dispatchTouchEvent。
handled = super.dispatchTouchEvent(transformedEvent);
} else {
//如果child不為空,則調用child view 的dispatchTouchEvent
//這行代碼很關鍵,如果child是ViewGroup則繼續在ViewGoup的進行事件分發,這會是一個遞歸調用
//如果child 是一個View,則調用View的dispatchTouchEvent,進行事件的分發。
handled = child.dispatchTouchEvent(transformedEvent);
}
//以上兩行代碼最終會調用到View的dispatchTouchEvent方法。
return handled;
}
}
接下來看View dispatchTouchEvent如何處理的
public class View {
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean result = false;
ListenerInfo li = mListenerInfo;
//如果view設置了mOnTouchListener,先調用mOnTouchListener.onTouch,
//如果返回true,則表示消費了此事件,不在向下傳遞,為啥這樣說?那是因為,通過他的返回值,進行了邏輯判斷
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//如果result為false,則調用View.onTouchEvent方法
if (!result && onTouchEvent(event)) {
result = true;
}
return result;
}
public boolean onTouchEvent(MotionEvent event) {
switch (action) {
case MotionEvent.ACTION_UP:
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
//調用performClick mOnClickListener.onClick()方法
performClickInternal();
}
break;
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_CANCEL:
break;
case MotionEvent.ACTION_MOVE:
break;
}
return false;
}
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
return result;
}
}
如果仔細看了ViewGroup和View事件分發邏輯代碼。我們可以總結為以下幾點。
1.ViewGroup接收到DispatchTouchEvent調用后,先調用了onInterceptTouchEvent,通過返回值進行判斷是否對事件進行攔截。我們可以在代碼中復寫這個方法來進行事件的攔截。
2.down事件后,ViewGoup會遍歷子View,找到處理Touch事件的View,并且緩存到了TouchTarget鏈表中,一次事件流中,只有down的時候才會進行子View的遍歷。
只有做的好處提升了性能,緩存的鏈表中也是為了多點觸摸,會有多個view響應觸摸事件。
move事件時,則會從這個鏈表中進行遍歷,通過調用dispatchTransformedTouchEvent方法進行事件的傳遞。
- 在dispatchTransformedTouchEvent方法中,根據child的值是否為null進入不同的分發流程。
1)如果為null說明沒有子view沒有響應事件,則調用父類View的dispatchTouchEvent--->onTouchEvent方法。
2)如果child值不為null。則調用child的dispatchTouchEvent方法,
此時如果child是一個ViewGoup則還是執行ViewGoup中的分發邏輯,進行遞歸調用。
如果child是一個View,則調用View的dispatchTouchEvent--->onTouchEvent方法。
- 在View的dispatchTouchEvent方法中:
1)listener的onTouch優先級要高于View的onTouchEvent
2)會先調用mOnTouchListener.onTouch方法,并根據返回值進行判斷是否消費事件。
3)如果上面的返回值為false,則會調用View的onTouchEvent方法。
5.在View的onTouchEvent方法中UP事件時,才會調用View設置的onClickListener的監聽事件。
這就是為什么了,同時給一個View設置了onTouch監聽和lonClickListener監聽時,當onTouch返回true無法調用onClick事件的原因。
原文鏈接:https://blog.csdn.net/niuyongzhi/article/details/125566675
相關推薦
- 2022-07-23 C++超詳細講解稀疏矩陣_C 語言
- 2022-07-06 c#?復寫Equals方法的實現_C#教程
- 2023-03-03 Fragment通過FragmentManager實現通信功能詳細講解_Android
- 2022-09-05 Linux系統下創建守護進程
- 2022-06-02 Consul的HTTP?API和使用方法_云計算技術
- 2022-12-30 React代碼分割的實現方法介紹_React
- 2022-11-17 React中實現插槽效果的方案詳解_React
- 2022-05-12 Python 正則替換內容
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支