網站首頁 編程語言 正文
1.分發(fā)對象-MotionEvent
事件類型有:
1.ACTION_DOWN-----手指剛接觸屏幕
2.ACTION_MOVE------手指在屏幕上移動
3.ACTION_UP------手指從屏幕上松開的一瞬間
4.ACTION_CANCEL-----事件被上層攔截時觸發(fā)
MotionEvent主要的方法:
getX() | 得到事件發(fā)生的x軸坐標(相對于當前視圖) |
getY() | 得到事件發(fā)生的y軸坐標(相對于當前視圖) |
getRawX() | 得到事件發(fā)生的x軸坐標(相對于屏幕左頂點) |
getRawY() | 得到事件發(fā)生的y軸坐標(相對于屏幕左頂點) |
2.如何傳遞事件
1.傳遞流程
底層IMS->ViewRootImpl->activity->viewgroup->view
2.事件分發(fā)的源碼解析
1.Activity對點擊事件的分發(fā)過程
Activity#dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
//事件交給Activity所附屬的Window進行分發(fā),如果返回true,循環(huán)結束,返回false,沒人處理
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
//所有View的onTouchEvent都返回false,那么Activity的onTouchEvent就會被調用
return onTouchEvent(ev);
}
Window#superDispatchTouchEvent
public abstract boolean superDispatchTouchEvent(MotionEvent event);
PhoneWindow#superDispatchTouchEvent
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
DecorView#superDispatchTouchEvent()
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
ViewGroup#dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent ev) {<!--{cke_protected}{C}%3C!%2D%2D%20%2D%2D%3E-->
2.頂級View對點擊事件的分發(fā)過程
把ViewGroup的dispatchTouchEvent()方法中的代碼進行分段說明
第一段:
描述的是View是否攔截點擊事件這個邏輯
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {//事件類型為down或者mFirstTouchTarget有值
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);//詢問是否攔截,方法返回true就攔截
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;//直接攔截了
}
當事件類型為down或者mFirstTouchTarget有值時,就不攔截當前事件,否則直接攔截了這個事件。那么mFirstTouchTarget什么時候有值?當ViewGroup不攔截事件并且把事件交給子元素處理時,mFirstTouchTarget就有值并且指向子元素。所以當事件類型為down并且攔截事件,那么mFirstTouchTarget為空,這會讓后面的事件move和up無法滿足mFirstTouchTarget有值的條件,直接無法調用onInterceptTouchEvent方法。
特殊情況:通過requestDisallowInterceptTouchEvent方法來設置標記位FLAG_DISALLOW_INTERCEPT,ViewGroup就無法攔截除了ACTION_DOWN以外的點擊事件,這個標記位無法影響ACTION_DOWN事件,因為當事件為ACTION_DOWN時,就會重置這個標記位,將導致子View設置的這個標記位無效。
總結:
1.當ViewGroup決定攔截事件后,那么后續(xù)的點擊事件將會默認交給它處理并且不再調用它的onInterceptTouchEvent方法。
2.當ViewGroup不攔截ACTION_DOWN事件,那么標記位FLAG_DISALLOW_INTERCEPT讓ViewGroup不再攔截事件。
第二段:
當ViewGroup不攔截事件時,分發(fā)事件給子View,看哪個子View處理事件
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x =
isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex);
final float y =
isMouseEvent ? ev.getYCursorPosition() : ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
//對子元素進行排序
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
// If there is a view that has accessibility focus we want it
// to get the event first and if not handled we will perform a
// normal dispatch. We may do a double iteration but this is
// safer given the timeframe.
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
if (!child.canReceivePointerEvents()
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
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;
}
resetCancelNextUpFlag(child);
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();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
遍歷ViewGroup的所有的子元素,判斷子元素是否在播動畫和點擊事件的坐標是否落在子元素的區(qū)域內,如果是,就能接收到點擊事件,并且事件會傳遞給它來處理。
我們來看一下dispatchTransformedTouchEvent方法
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
...
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
...
}
dispatchTransformedTouchEvent實際上調用的是子元素的dispatchTouchEvent方法。
如果子元素的dispatchTouchEvent方法返回true,那么mFirstTouchTarget就會被賦值同時跳出for循環(huán)
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
這幾行代碼完成了mFirstTouchTarget的賦值并終止對子元素的遍歷,如果子元素的dispatchTouchEvent方法返回false,那么ViewGroup就會把事件分發(fā)給下一個子元素。
其實mFirstTouchTarget真正的賦值是在addTouchTarget方法里面,mFirstTouchTarget是一種單鏈表結構。
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
第三段:
執(zhí)行事件
//當前View的事件處理代碼
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
//子View的事件處理代碼
...
dispatchTransformedTouchEvent方法的第三個參數(shù)為null,則會調用super.dispatchTouchEvent方法,也就是View的dispatchTouchEvent方法,所以點擊事件給View處理。
View對點擊事件的處理過程
View(不包含ViewGroup)是一個單獨的元素,沒有子元素,只能自己處理事件。
public boolean dispatchTouchEvent(MotionEvent event) {
...
boolean result = false;
...
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
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;
}
}
...
return result;
}
首先判斷是否設置了OnTouchListener,如果OnTouchListener的onTouch方法返回true,就不會調用onTouchEvent方法,否則就會調用onTouchEvent方法。
public boolean onTouchEvent(MotionEvent event) {
...
if ((viewFlags & ENABLED_MASK) == DISABLED
&& (mPrivateFlags4 & PFLAG4_ALLOW_CLICK_WHEN_DISABLED) == 0) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return clickable;
}
...
}
不可用狀態(tài)下的View照樣會消耗點擊事件
switch (action) {
case MotionEvent.ACTION_UP:
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
if ((viewFlags & TOOLTIP) == TOOLTIP) {
handleTooltipUp();
}
...
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
...
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClickInternal();
}
}
}
}
...
mIgnoreNextUpEvent = false;
break;
當ACTION_UP事件發(fā)生時,會觸發(fā)performClick方法,如果View設置了OnClickListener,那么performClick方法內部會調用它的onClick方法。
private boolean performClickInternal() {
// Must notify autofill manager before performing the click actions to avoid scenarios where
// the app has a click listener that changes the state of views the autofill service might
// be interested on.
notifyAutofillManagerOnClick();
return performClick();
}
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
//關鍵代碼,判斷是否設置了onClickListener
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
...
return result;//最終返回執(zhí)行結果
}
點擊事件的分發(fā)機制的源碼實現(xiàn)已經分析完了。
3.主要方法
1.dispatchTouchEvent:用來進行事件的分發(fā),如果事件可以傳遞給當前View,那么此方法一定會被調用,返回結果受當前View的onTouchEvent和下級View的dispatchTouchEvent方法的影響,表示是否消耗當前事件。
2.onInterceptTouchEvent:用來判斷是否攔截某個事件,如果當前View攔截了某個事件,那么在同一事件序列當中,此方法不會被再次調用,返回結果表示是否攔截當前事件。
3.onTouchEvent:用來處理點擊事件,返回結果表示是否消耗當前事件,如果不消耗,則在同一個事件序列中,當前View無法再次接收到事件。
4.requestDisallowInterceptTouchEvent:一般用于子View中,要求父View不攔截事件。
5.dispatchTransformedTouchEvent:如果child不為空,就發(fā)到child的dispatchTouchEvent中,否則發(fā)給自己。
4.事件傳遞中l(wèi)istener
onTouch,performClick和onClick調用的順序以及onTouch返回值的影響?
當一個View需要處理事件時,View的dispatchTouchEvent方法中,如果設置了OnTouchListener,那么OnTouchListener的onTouch方法會被調用,當onTouch方法返回true時,onTouchEvent就不會被調用,當onTouch方法返回false時,onTouchEvent方法就被調用,在onTouchEvent方法里面進入performClick方法,在performClick方法里面判斷是否設置onClickListener,并且如果設置了onClickListener,那么onClick方法就被調用,performClick方法就返回true,如果沒有設置了onClickListener,performClick方法就返回false。
總的來說方法調用的順序為
5.滑動沖突如何用事件分發(fā)處理
滑動沖突定義:當有內外兩層View都可以響應事件時,事件由誰來決定。
滑動沖突類型:1.當內外兩層View滑動方向不一致
2.當內外兩層滑動方向一致的時候
3.兩種情況疊加
解決思路:
內部攔截:dispatchTouchEvent+dispatchTransformedTouchEvent
重寫子元素的dispatchTouchEvent方法
down事件分發(fā)給子元素,move事件是看條件的,如果不滿足條件,就把事件交給子元素處理,如果滿足條件,就會取消子元素的處理事件,然后把事件交給父元
public boolean dispatchTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
//down事件,父容器不要攔截我
parent.requestDisallowInterceptTouchEvent(true);
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastX;
int deltaY = y - mLastY;
if (父容器需要此類點擊事件) {
//父容器攔截我
parent.requestDisallowInterceptTouchEvent(false);
}
break;
}
case MotionEvent.ACTION_UP: {
break;
}
default:
break;
}
mLastX = x;
mLastY = y;
return super.dispatchTouchEvent(event);
}
(當move事件時,進入第一塊代碼,調用intercepted = onInterceptTouchEvent(ev),我們在onInterceptTouchEvent方法中設置不是down事件就返回true,所以intercepted為true,然后第二塊代碼不會執(zhí)行,進入第三塊代碼,因為intercepted為true,所以cancelChild就為true,取消子元素事件執(zhí)行,調用dispatchTransformedTouchEvent方法,cancel為true->
event.setAction(MotionEvent.ACTION_CANCEL)->handled = child.dispatchTouchEvent(event)
把mFirstTouchTarget設置為空,所以到下一個move事件來的時候,mFirstTouchTarget是為空的,在第一段代碼中intercepted為true,第二段代碼不執(zhí)行,第三塊代碼走dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS),即由當前View的事件處理代碼(父元素))
重寫父元素的onInterceptTouchEvent方法
當為down事件時,要return false,因為在ViewGroup的dispatchTouchEvent方法中,當為down事件時,會調用resetTouchState()方法,在resetTouchState()方法里面會重置狀態(tài),把mGroupFlags也重置,這樣會導致在前面的parent.requestDisallowInterceptTouchEvent(true)沒有用,所以我們在onInterceptTouchEvent方法里面要設置為down事件時返回false,因為在down事件時onInterceptTouchEvent一定會執(zhí)行。
public boolean onInterceptTouchEvent(MotionEvent event) {
int action = event.getAction();
if (action == MotionEvent.ACTION_DOWN) {
super.onInterceptTouchEvent(event);
return false;
} else {
return true;
}
}
外部攔截:onInterceptTouchEvent
點擊事件先經過父容器的攔截處理,如果父容器需要這件事就攔截,不需要就不攔截。
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean intercepted = false;
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
intercepted = false;
break;
}
case MotionEvent.ACTION_MOVE: {
if (滿足父容器的攔截要求) {
intercepted = true;
} else {
intercepted = false;
}
break;
}
case MotionEvent.ACTION_UP: {
intercepted = false;
break;
}
default:
break;
}
mLastXIntercept = x;
mLastYIntercept = y;
return intercepted;
}
原文鏈接:https://blog.csdn.net/weixin_63357306/article/details/128629042
相關推薦
- 2022-04-07 C#異步編程async/await用法詳解_C#教程
- 2022-10-15 C語言庫函數(shù)qsort的使用及模擬實現(xiàn)_C 語言
- 2022-11-07 python學習pymongo模塊的使用方法_python
- 2024-03-21 微信支付的簽名算法
- 2022-06-14 Flutter網絡請求Dio庫的使用及封裝詳解_Android
- 2023-12-14 idea配置tomcat熱部署
- 2022-03-14 Android 截屏在surfaceview上失敗的問題
- 2022-11-01 go語言中for?range使用方法及避坑指南_Golang
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細win安裝深度學習環(huán)境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據結構-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支