當前位置:首頁 » 安卓系統 » 事件機制android

事件機制android

發布時間: 2022-08-01 16:03:30

1. 誰可以解釋下,android事件分發為什麼要設計成從根view到子view,而不是從子vie

Android事件傳遞流程在網上可以找到很多資料,FrameWork層輸入事件和消費事件,可以參考:
Touch事件派發過程詳解
這篇blog闡述了底層是如何處理屏幕輸,並往上傳遞的。Touch事件傳遞到Activity的DecorView時,往下走就是ViewGroup和子View之間的事件傳遞,可以參考郭神的這兩篇博客
Android事件分發機制完全解析,帶你從源碼的角度徹底理解(上)
Android事件分發機制完全解析,帶你從源碼的角度徹底理解(下)
郭神的兩篇博客清楚明白地說明了View之間事件傳遞的大方向,但是具體的一些晦暗的細節闡述較少,本文主要是總結這兩篇博客的同時,側重於兩點:
事件分發過程中一些細節到底如何實現的?
子view到底如何和父View搶事件,父View又是如何攔截事件不發送給子View,以及如果我們需要處理這種混亂的關系才能讓兩者和諧相處?。
MotionEvent抽象
要明白View的事件傳遞,很有必要先說一下Touch事件是如何在Android系統中抽象的,這主要使用的就是MotionEvent。這個類經歷了幾次重大的修改,一次是在2.x版本支持多點觸摸,一次是4.x將大部分代碼甩給native層處理。
一次簡單的事件
我們先舉個栗子來說明一次完整的事件,用戶觸屏 滑動 到手機離開屏幕,這認為是一次完整動作序列(movement traces)。一個動作序列中包含很多動作Action,比如在用戶按下時,會封裝一個MotionEvent,分發給視圖樹,我們可以通過motionevent.getAction拿到這個動作是ACTION_DOWN。同樣,在手指抬起時,我們可以接收到Action類型是Action_UP的MotionEvent。對於滑動(MOVE)這個操作,Android為了從效率出發,會將多個MOVE動作打包到一個MotionEvent中。通過getX getY可以獲取當前的坐標,如果要訪問打包的緩存數據,可以通過getHistorical**()函數來獲取。
加入多點觸摸
對於單點的操作來看,MotionEvent顯得比較簡單,但是考慮引入多點觸摸呢?我們定義一個接觸點為(Pointer)。每一個觸摸點Pointer都會有一個當次動作序列的唯一Id和Index.MotionEvent中多個手指的操作API大部分都是通過pointerindex來進行的,如:獲取不同Pointer的觸碰位置,getX(int pointerIndex);獲取PointerId等等。大部分情況下,pointerid == pointeridex。
我們從onTouch接受到一個MotionEvent,怎麼拿到多個觸碰點的信息?為了解開筆者剛開始學習這部分知識時的困惑,我們首先樹立起一種概念:一個MotionEvent只允許有一個Action(動作),而且這個Action會包含觸發這次Action的觸碰點信息,對於MOVE操作來說,一定是當前所有觸碰點都在動。只有ACTION_POINTER_DOWN這類事件事件會在Action裡面指定是哪一個POINTER按下。
在MotionEvent的底層實現中,是通過一個16位來存儲Action和Pointer信息(PointerIndex)。低8位表示Action,理論上可以表示255種動作類型;高8位表示觸發這個Action的PointerIndex,理論上Android最多可以支持255點同時觸摸,但是在上層代碼使用的時候,默認多點最多存在32個,不然事件在分發的時候會有問題。
ACTION_DOWN OR ACTION_POINTER_DOWN:
這兩個按下操作的區別是ACTION_DOWN是一個系列動作的開始,而ACTION_POINTER_DOWN是在一個系列動作中間有另外一個觸碰點觸碰到屏幕。
這部分詳細的描述,請參考:
android觸控,先了解MotionEvent
到這里,鋪墊終於結束了,我們開始直奔主題。
View的事件傳遞
Android的Touch事件傳遞到Activity頂層的DecorView(一個FrameLayout)之後,會通過ViewGroup一層層往視圖樹的上面傳遞,最終將事件傳遞給實際接收的View。下面給出一些重要的方法。如果你對這個流程比較熟悉的話,可以跳過這里,直接進入第二部分。
dispatchTouchEvent
事件傳遞到一個ViewGroup上面時,會調用dispatchTouchEvent。代碼有刪減
public boolean dispatchTouchEvent(MotionEvent ev) {

boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;

// Attention 1 :在按下時候清除一些狀態
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
//注意這個方法
resetTouchState();
}

// Attention 2:檢查是否需要攔截
final boolean intercepted;
//如果剛剛按下 或者 已經有子View來處理
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 {
// 不是一個動作序列的開始 同時也沒有子View來處理,直接攔截
intercepted = true;
}

//事件沒有取消 同時沒有被當前ViewGroup攔截,去找是否有子View接盤
if (!canceled && !intercepted) {
//如果這是一系列動作的開始 或者有一個新的Pointer按下 我們需要去找能夠處理這個Pointer的子View
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex(); // always 0 for down

//上面說的觸碰點32的限制就是這里導致
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;

final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);

//對當前ViewGroup的所有子View進行排序,在上層的放在開始
final ArrayList<View> preorderedList = buildOrderedChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = customOrder
? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null)
? children[childIndex] : preorderedList.get(childIndex);

// canViewReceivePointerEvents visible的View都可以接受事件
// isTransformedTouchPointInView 計算是否落在點擊區域上
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}

//能夠處理這個Pointer的View是否已經處理之前的Pointer,那麼把
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;
} }
//Attention 3 : 直接發給子View
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);
= true;
break;
}

}
}

}
}

// 前面已經找到了接收事件的子View,如果為NULL,表示沒有子View來接手,當前ViewGroup需要來處理
if (mFirstTouchTarget == null) {
// ViewGroup處理
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {

if() {
//ignore some code
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
}

}
return handled;
}

上面代碼中的Attention在後面部分將會涉及,重點注意。
這里需要指出一點的是,一系列動作中的不同Pointer可以分配給不同的View去響應。ViewGroup會維護一個PointerId和處理View的列表TouchTarget,一個TouchTarget代表一個可以處理Pointer的子View,當然一個View可以處理多個Pointer,比如兩根手指都在某一個子View區域。TouchTarget內部使用一個int來存儲它能處理的PointerId,一個int32位,這也就是上層為啥最多隻能允許同時最多32點觸碰。
看一下Attention 3 處的代碼,我們經常說view的dispatchTouchEvent如果返回false,那麼它就不能系列動作後面的動作,這是為啥呢?因為Attention 3處如果返回false,那麼它不會被記錄到TouchTarget中,ViewGroup認為你沒有能力處理這個事件。
這里可以看到,ViewGroup真正處理事件是在dispatchTransformedTouchEvent裡面,跟進去看看:
dispatchTransformedTouchEvent
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {

//沒有子類處理,那麼交給viewgroup處理
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);
}
return handled;
}

可以看到這里不管怎麼樣,都會調用View的dispatchTouchEvent,這是真正處理這一次點擊事件的地方。
dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent event) {
if (onFilterTouchEventForSecurity(event)) {
//先走View的onTouch事件,如果onTouch返回True
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;
}

我們給View設置的onTouch事件處在一個較高的優先順序,如果onTouch執行返回true,那麼就不會去走view的onTouchEvent,而我們一些點擊事件都是在onTouchEvent中處理的,這也是為什麼onTouch中返回true,view的點擊相關事件不會被處理。

2. android事件分發機制是什麼設計模式

android事件分發機制 就是一個觸摸事件發生了,從一個窗口傳遞到一個視圖,再傳遞到另外一個視圖,最後被消費的過程,在android中還是比較復雜的傳遞流程如下:

(1) 事件從Activity.dispatchTouchEvent()開始傳遞,只要沒有被停止或攔截,從最上層的View(ViewGroup)開始一直往下(子View)傳遞。子View可以通過onTouchEvent()對事件進行處理。

(2) 事件由父View(ViewGroup)傳遞給子View,ViewGroup可以通過onInterceptTouchEvent()對事件做攔截,停止其往下傳遞。

(3) 如果事件從上往下傳遞過程中一直沒有被停止,且最底層子View沒有消費事件,事件會反嚮往上傳遞,這時父View(ViewGroup)可以進行消費,如果還是沒有被消費的話,最後會到Activity的onTouchEvent()函數。

(4) 如果View沒有對ACTION_DOWN進行消費,之後的其他事件不會傳遞過來。

(5) OnTouchListener優先於onTouchEvent()對事件進行消費。

上面的消費即表示相應函數返回值為true。

3. android事件分發機制怎麼寫

ViewGroup就是一組View的集合,它包含很多的子View和子VewGroup,是Android中所有布局的父類或間接父類,像LinearLayout、RelativeLayout等都是繼承自ViewGroup的。
但ViewGroup實際上也是一個View,只不過比起View,它多了可以包含子View和定義布局參數的功能。

4. Android事件分發機制是怎麼判斷手勢類型,並決定分發給哪個view的

可以參考如下內容:
dispatchTouchEvent -> onTouch -> onTouchEvent
等上述的up事件分發完結後,再調用onClick
看起來一目瞭然,事實上,我只是用了默認的調用,在TextView的dispatchTouchEvent和onTouchEvent都是讓它返回super.xxx。而且在onTouch監聽里返回的是false。
所以,實際的情況更多,基於此,為了更清晰地熟悉事件分發機制,我們只能通過看源碼了。
源碼分析
我目前查看的源碼是API 22環境下的。這部分關於View的事件分發的源碼和之前的有很大的區別,但是,萬變不如其中,有些根本的邏輯流程一般是不會改變的。
我們直接看View的代碼,因為控制項都繼承自View。

5. android事件分發機制是怎麼判斷手勢類型,並決定分發給哪個view的

參考如下:
在父布局中

if(event.getAction() == MotionEvent.ACTION_DOWN){
//do
return true;
}else{
return false;
}

在子布局中

if(event.getAction() == MotionEvent.ACTION_MOVE){
//do
return true;
}

6. android的事件處理機制有兩種

1.基於監聽的事件處理機制,有一個關鍵就是事件注冊。 但是我們在實踐的時候並沒有自己手動的為某個視圖控制項注冊監聽器。
解答: 我們會經常用到 諸如 setOnclickListener(),OnTouchListener()方法等。 從字面意義理解,它為設置...監聽器。 但是,它 跟注冊還是頗有一些區別的。 我想注冊實踐監聽器,就是將它掛在在一個線程上,也就是說有一個事件監聽線程,那麼,有事件的視圖,就至少是雙線程的程序了。 不過很可惜,在去看set..Listener的源碼的時候,是看不到它在java源碼方面的具體實現的。 也就是說,要麼它依賴操作系統實現,要麼它依賴jni實現,並且,事件線程由jni管理。 換言之,實現注冊監聽是由ni實現的。
2.事件源的觸發流程:
解答: 學習過操作系統朋友應該知道,操作系統的很多操作都是通過中斷來完成。 同理,比如一個點擊事件,android手機硬體中,包括了一個觸摸屏的硬體,它分為內屏和外屏。 其中負責觸發屏幕點擊和觸摸中斷的為內屏。 內屏大概由五個層次構成,具體有什麼用不知道,反正我拆過~~~ 從內屏上,當有電容屏感應的時候,會接收到你觸摸的位置信息,甚至觸摸力度!!! 這個消息經由系統中斷(具有最高優先順序,應該是由最高優先順序的進程通知)發送給cpu,經由cpu通過進程間的消息機制傳遞給這個進程(當前正在用戶界面運行的進程,這時候只有一個),也就是這個程序運行的內存空間的某個點。(或者說通過廣播機制,將這個事件發送給所有的app也是有可能的)。

7. android 如何獲取一個界面最頂層的view並處理單擊事件的分發機制

android事件分發機制 就是一個觸摸事件發生了,從一個窗口傳遞到一個視圖,再傳遞到另外一個視圖,最後被消費的過程,在android中還是比較復雜的傳遞流程如下:

(1) 事件從Activity.dispatchTouchEvent()開始傳遞,只要沒有被停止或攔截,從最上層的View(ViewGroup)開始一直往下(子View)傳遞。子View可以通過onTouchEvent()對事件進行處理。

(2) 事件由父View(ViewGroup)傳遞給子View,ViewGroup可以通過onInterceptTouchEvent()對事件做攔截,停止其往下傳遞。

8. android中touch事件的傳遞機制是怎樣的

不多說,給你一個鏈接
http://www.cnblogs.com/virtual-young/p/4118890.html

9. 請簡述什麼是android事件處理,並分析兩種android事件處理機制的實現過程和區別

UI編程通常都會伴隨事件處理,Android也不例外,它提供了兩種方式的事件處理:基於回調的事件處理和基於監聽器的事件處理。

對於基於監聽器的事件處理而言,主要就是為Android界面組件綁定特定的事件監聽器;對於基於回調的事件處理而言,主要做法是重寫Android組件特定的回調函數,Android大部分界面組件都提供了事件響應的回調函數,我們主要重寫它們就行。


一 基於監聽器的事件處理

相比於基於回調的事件處理,這是更具「面向對象」性質的事件處理方式。在監聽器模型中,主要涉及三類對象:

1)事件源Event Source:產生事件的來源,通常是各種組件,如按鈕,窗口等。

2)事件Event:事件封裝了界面組件上發生的特定事件的具體信息,如果監聽器需要獲取界面組件上所發生事件的相關信息,一般通過事件Event對象來傳遞。

3)事件監聽器Event Listener:負責監聽事件源發生的事件,並對不同的事件做相應的處理。


基於監聽器的事件處理機制是一種委派式Delegation的事件處理方式,事件源將整個事件委託給事件監聽器,由監聽器對事件進行響應處理。這種處理方式將事件源和事件監聽器分離,有利於提供程序的可維護性。

舉例:

View類中的OnLongClickListener監聽器定義如下:(不需要傳遞事件)


[java] view plainprint?

public interface OnLongClickListener {

boolean onLongClick(View v);

}

public interface OnLongClickListener {
boolean onLongClick(View v);
}


View類中的OnLongClickListener監聽器定義如下:(需要傳遞事件MotionEvent)

[java] view plainprint?

public interface OnTouchListener {

boolean onTouch(View v, MotionEvent event);

}

public interface OnTouchListener {
boolean onTouch(View v, MotionEvent event);
}

二 基於回調的事件處理

相比基於監聽器的事件處理模型,基於回調的事件處理模型要簡單些,該模型中,事件源和事件監聽器是合一的,也就是說沒有獨立的事件監聽器存在。當用戶在GUI組件上觸發某事件時,由該組件自身特定的函數負責處理該事件。通常通過重寫Override組件類的事件處理函數實現事件的處理。

舉例:

View類實現了KeyEvent.Callback介面中的一系列回調函數,因此,基於回調的事件處理機制通過自定義View來實現,自定義View時重寫這些事件處理方法即可。

[java] view plainprint?

public interface Callback {

// 幾乎所有基於回調的事件處理函數都會返回一個boolean類型值,該返回值用於

// 標識該處理函數是否能完全處理該事件

// 返回true,表明該函數已完全處理該事件,該事件不會傳播出去

// 返回false,表明該函數未完全處理該事件,該事件會傳播出去

boolean onKeyDown(int keyCode, KeyEvent event);

boolean onKeyLongPress(int keyCode, KeyEvent event);

boolean onKeyUp(int keyCode, KeyEvent event);

boolean onKeyMultiple(int keyCode, int count, KeyEvent event);

}

public interface Callback {
// 幾乎所有基於回調的事件處理函數都會返回一個boolean類型值,該返回值用於
// 標識該處理函數是否能完全處理該事件
// 返回true,表明該函數已完全處理該事件,該事件不會傳播出去
// 返回false,表明該函數未完全處理該事件,該事件會傳播出去
boolean onKeyDown(int keyCode, KeyEvent event);
boolean onKeyLongPress(int keyCode, KeyEvent event);
boolean onKeyUp(int keyCode, KeyEvent event);
boolean onKeyMultiple(int keyCode, int count, KeyEvent event);
}

三 比對

基於監聽器的事件模型符合單一職責原則,事件源和事件監聽器分開實現;

Android的事件處理機制保證基於監聽器的事件處理會優先於基於回調的事件處理被觸發;

某些特定情況下,基於回調的事件處理機制會更好的提高程序的內聚性。


四 基於自定義監聽器的事件處理流程

在實際項目開發中,我們經常需要自定義監聽器來實現自定義業務流程的處理,而且一般都不是基於GUI界面作為事件源的。這里以常見的app自動更新為例進行說明,在自動更新過程中,會存在兩個狀態:下載中和下載完成,而我們的程序需要在這兩個狀態做不同的事情,「下載中」需要在UI界面上實時顯示軟體包下載的進度,「下載完成」後,取消進度條的顯示。這里進行一個模擬,重點在說明自定義監聽器的事件處理流程。

4.1)定義事件監聽器如下:

熱點內容
二級程序編譯答案 發布:2024-05-03 18:41:35 瀏覽:653
領動自動精英版是哪個配置 發布:2024-05-03 18:37:30 瀏覽:149
java編譯器中cd什麼意思 發布:2024-05-03 18:36:00 瀏覽:389
傳奇伺服器如何刷錢 發布:2024-05-03 18:36:00 瀏覽:977
安卓版twitter怎麼注冊 發布:2024-05-03 18:28:05 瀏覽:893
Python邏輯優先順序 發布:2024-05-03 18:26:14 瀏覽:267
linux查看svn密碼 發布:2024-05-03 18:12:47 瀏覽:803
地鐵逃生怎麼進入游戲安卓 發布:2024-05-03 17:49:35 瀏覽:992
aws雲存儲 發布:2024-05-03 17:48:50 瀏覽:954
安卓微信王者號怎麼轉成蘋果 發布:2024-05-03 17:44:38 瀏覽:745