1.事件的源頭在哪里
Android系統中將輸入事件定義為InputEvent,而InputEvent根據輸入事件的型別又分為了KeyEvent和MotionEvent,前者對應鍵盤事件,后者則對應螢屏觸摸事件,這些事件統一由系統輸入管理器InputManagerService進行分發,在 SystemServer.startOtherServices中會啟動 IMS(InputManagerService)和WMS(WindowManagerService),InputManagerService會開啟InputReader執行緒讀取輸入事件,然后交給InputDispatcher執行緒去將事件派發到目標視窗,目標視窗的確定和視窗的狀態,層級等相關,而所有視窗都是通過WindowManagerService添加的,
這里需要看一下Activity的啟動流程:
- ActivityThread.performLaunchActivity() -> activity.attach(),Activity.attach()中進行了PhoneWindow初始化, 在PhoneWindow中會創建DecorView.
- ActivityThread.handleResumeActivity() 中會呼叫WindowManagerImpl.addView() ->WindowManagerGlobal.addView(),在WindowManagerGlobal.addView()中會創建一個ViewRootImpl實體,然后呼叫ViewRootImpl的setView函式,在setView()中會進行Input通道的創建;此外addView的最終邏輯是ViewRootImpl.setView(decorView), 也就是將decorView賦值給ViewRootImpl的變數mView,后面的事件分發也會用到這個mView,
//WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
//ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
...
requestLayout();
if ((mWindowAttributes.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
<!--創建InputChannel容器-->
mInputChannel = new InputChannel();
}
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
<!--添加視窗,并請求開辟Socket Input通信通道-->
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
}...
<!--監聽,開啟Input信道-->
if (mInputChannel != null) {
if (mInputQueueCallback != null) {
mInputQueue = new InputQueue();
mInputQueueCallback.onInputQueueCreated(mInputQueue);
}
<!--監聽InputEvent-->
mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
Looper.myLooper());
}
WMS負責APP和IMS通信信道的建立,高版本的采用的都是Socket的通信方式,而比較舊的版本采用的是Pipe管道的方式,之后讓InputManager將Input通信信道與當前的視窗ID系結,這樣就能知道哪個視窗用哪個信道通信了;
信道建立后還需要注冊事件監聽WindowInputEventReceiver用來接收事件,至此APP就可以收到InputEvent了,詳細分析請查看十分鐘了解Android觸摸事件原理(InputManagerService)

2.事件是如何到達View的
App端與服務端建立了雙向通信之后,InputManager就能夠將產生的輸入事件從底層硬體分發過來,Android提供了InputEventReceiver類,以接收分發這些訊息:
public abstract class InputEventReceiver {
// Called from native code.
private void dispatchInputEvent(int seq, InputEvent event) {
mSeqMap.put(event.getSequenceNumber(), seq);
onInputEvent(event);
}
}
InputEventReceiver是一個抽象類,其默認的實作是將接收到的輸入事件直接消費掉,因此真正的實作是ViewRootImpl.WindowInputEventReceiver類:
public final class ViewRootImpl {
final class WindowInputEventReceiver extends InputEventReceiver {
@Override
public void onInputEvent(InputEvent event, int displayId) {
// 將輸入事件加入佇列
enqueueInputEvent(event, this, 0, true);
}
}
}
ViewRootImpl.WindowInputEventReceiver,連帶呼叫了enqueueInputEvent()->doProcessInputEvents-> deliverInputEvent(q) -> stage.deliver(q);
接下來就是對事件的分發了,設計者在這里使用了經典的 責任鏈 模式:對于一個輸入事件的分發而言,必然有其對應的消費者,在這個程序中為了使多個物件都有處理請求的機會,從而避免了請求的發送者和接收者之間的耦合關系,將這些物件串成一條鏈,并沿著這條鏈一直傳遞該請求,直到有物件處理它為止,
設計者針對事件分發的整個責任鏈設計了InputStage類作為基類,作為責任鏈中的模版,并實作了若干個子類,為輸入事件按順序分階段進行分發處理:
abstract class InputStage {
private final InputStage mNext;
···
}
類InputStage提供了職責鏈的模板,也提供了一系列onProcess、forward、finish、apply方法,其目的也不言而喻:提供子類進行擴展的便捷,
// ----------------- InputStage的子類 ----------------------------
// 將預先輸入事件提供給視圖層次結構,視圖預處理輸入法事件階段,將輸入法的事件派發到視圖的樹,
final class ViewPreImeInputStage extends InputStage {}
// 執行事后輸入事件的早期處理,輸入法早期處理階段,
final class EarlyPostImeInputStage extends InputStage {}
// 將后期輸入事件提供給視圖層次結構,視圖輸入處理階段,主要處理按鍵、軌跡球、手指觸摸及一般性的運動事件,觸摸事件的分發物件是View,
final class ViewPostImeInputStage extends InputStage {}
// 從未處理的輸入事件執行新輸入事件的合成,綜合性的事件處理階段,該類主要軌跡球、操作桿、導航面板及未捕獲的事件使用鍵盤進行處理,
final class SyntheticInputStage extends InputStage {}
// 用于實作支持輸入事件的異步和無序處理的輸入流水線級的基類,
abstract class AsyncInputStage extends InputStage {}
// ----------------- AsyncInputStage的子類----------------------------
// 將預先輸入事件提供給 NativeActivity,本地方法預處理輸入法事件階段,可用于實作類似adb 輸入的功能,
final class NativePreImeInputStage extends AsyncInputStage
implements InputQueue.FinishedInputEventCallback {}
// 將預先輸入事件提供給視圖層次結構,輸入法事件處理階段,處理一些輸入法字符等,如果對輸入的內容無法識別,則繼續往下轉發,
final class ImeInputStage extends AsyncInputStage
implements InputMethodManager.FinishedInputEventCallback {}
// 將事后輸入事件提交到 NativeActivity 本地方法處理階段,則構建可延遲的重用佇列,此時執行操作將會異步回呼結果,
final class NativePostImeInputStage extends AsyncInputStage
implements InputQueue.FinishedInputEventCallback {}
然后下面就是責任鏈模式的拼裝,是在ViewRootImpl.setView()函式中:
//ViewRootImpl#setView()
// Set up the input pipeline.
CharSequence counterSuffix = attrs.getTitle();
mSyntheticInputStage = new SyntheticInputStage();
InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
"aq:native-post-ime:" + counterSuffix);
InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
InputStage imeStage = new ImeInputStage(earlyPostImeStage,
"aq:ime:" + counterSuffix);
InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
"aq:native-pre-ime:" + counterSuffix);
mFirstInputStage = nativePreImeStage;
mFirstPostImeInputStage = earlyPostImeStage;
···
最終構造成如下的職責鏈:mSyntheticInputStage --> viewPostImeStage --> nativePostImeStage --> earlyPostImeStage --> imeStage --> viewPreImeStage --> nativePreImeStage,
這說明ViewRootImpl.setView()函式非常重要,該函式也正是ViewRootImpl本身職責的體現:
-
1.鏈接WindowManager和DecorView的紐帶,更廣一點可以說是Window和View之間的紐帶;
-
2.完成View的繪制程序,包括measure、layout、draw程序;
-
3.向DecorView分發收到的用戶發起的InputEvent事件,
其中ViewPostImeInputStage就是負責UI層事件分發的,判斷如果是觸摸事件,呼叫processPointerEvent(),然后內部呼叫mView.dispatchPointerEvent(), 在前面提到過,mView為DecorView,輾轉呼叫到了DecorView.dispatchTouchEvent(),此時事件也就分發到了DecorView中;DecorView實際上就是Activity中Window的根布局,
private int processPointerEvent(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent)q.mEvent;
mAttachInfo.mUnbufferedDispatchRequested = false;
mAttachInfo.mHandlingPointerEvent = true;
//分發到View
boolean handled = mView.dispatchPointerEvent(event);
maybeUpdatePointerIcon(event);
maybeUpdateTooltip(event);
mAttachInfo.mHandlingPointerEvent = false;
...
return handled ? FINISH_HANDLED : FORWARD;
}
3.事件在View中是如何分發的
現在來看事件在DecorView中是如何分發的:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final Window.Callback cb = mWindow.getCallback();
return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}
DecorView.dispatchTouchEvent 中會呼叫 Window.Callback 的 dispatchTouchEvent 方法, 我們再來看看這個 Window.Callback 實體是什么,mWindow是從DecorView建構式中傳過來的;
這里需要再看一下Activity的啟動流程:
- ActivityThread.performLaunchActivity() -> activity.attach(),Activity.attach()中進行了PhoneWindow初始化,并設定CallBack物件,
- ActivityThread.handleResumeActivity() 中通過PhoneWindow獲取DecorView,在PhoneWindow初始化DecorView時,將window自身傳給了DecorView,因此DecorView中的mWindow就是PhoneWindow,
//ActivityThread.java
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
if (activity != null) {
...
//可以看到此處呼叫了Activity的attach()方法
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.configCallback,
r.assistToken);
...
}
}
//Activity.java
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
//PhoneWindow初始化
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(mWindowControllerCallback);
//設定callback為activity自身
mWindow.setCallback(this);
...
}
PhoneWindow創建DecorView的程序:
@Override
public final @NonNull View getDecorView() {
if (mDecor == null || mForceDecorInstall) {
//組裝DecorView
installDecor();
}
return mDecor;
}
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
//獲取DecorView
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
}
//回傳值就是DecorView
protected DecorView generateDecor(int featureId) {
// System process doesn't have application context and in that case we need to directly use
// the context we have. Otherwise we want the application context, so we don't cling to the
// activity.
Context context;
if (mUseDecorContext) {
Context applicationContext = getContext().getApplicationContext();
if (applicationContext == null) {
context = getContext();
} else {
context = new DecorContext(applicationContext, this);
if (mTheme != -1) {
context.setTheme(mTheme);
}
}
} else {
context = getContext();
}
return new DecorView(context, featureId, this, getAttributes());
}
至此我們知道window.callback就是activity, 事件傳遞到了Activity中;然后追蹤可以看出事件的傳遞流程:DecorView->Activity->Window->DecorView->ViewGroup
// 偽代碼
public class DecorView extends FrameLayout {
// 1.將事件分發給Activity
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
return window.callback.dispatchTouchEvent(ev)
}
}
// 2.將事件分發給Window
public class Activity {
public boolean dispatchTouchEvent(MotionEvent ev) {
return getWindow().superDispatchTouchEvent(ev);
}
}
// 3.將事件再次分發給DecorView
public class PhoneWindow extends Window {
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
}
//4.執行ViewGroup 的 dispatchTouchEvent,將事件分發到View體系中
public class DecorView extends FrameLayout {
// 4.執行ViewGroup 的 dispatchTouchEvent
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
}
對于DecorView而言,它承擔了2個職責:
-
1.在接收到輸入事件時,DecorView不同于其它View,它需要先將事件轉發給最外層的Activity,使得開發者可以通過重寫Activity.onTouchEvent()函式以達到對當前螢屏觸摸事件攔截控制的目的,這里DecorView履行了自身(根節點)特殊的職責;
-
2.從Window接收到事件時,作為View樹的根節點,將事件分發給子View,這里DecorView履行了一個普通的View的職責,
接下來就該看事件在Activity,ViewGroup和View中是如何傳遞和消費的了,這一階段終于到了面向用戶的最上層了,因此也需要考慮事件是否被消費,事件先從外層傳遞到內層 Acitivty -> ViewGroup -> View,然后消費結果再有由內層傳遞到外層,
分發消費流程
事件分發程序中主要通過三個重要的方法來處理:
- dispatchTouchEvent() 負責事件的分發
- onTouchEvent() 負責事件的消費,回傳true則表示消費當前事件,回傳false則不消費
- onInterceptTouchEvent() 負責事件的攔截,只有ViewGroup有該方法,另外兩個方法Activity,ViewGroup和View都擁有
當事件到達dispatchTouchEvent()時,若其有child,則呼叫子View的dispatchTouchEvent(event)再將事件分發給child...以此類推,直至將事件分發到底部的View,子View會將是否消費的結果再一層層傳遞到最上層的dispatchTouchEvent()方法中,是一個遞回的流程;若ViewGroup中有子View消費了事件,則ViewGroup的dispatchTouchEvent()回傳true表示事件已經被消費,若子View沒有消費事件,則將事件再交給自身的onTouchEvent()處理,最終確定事件是否被消費,將結果回傳給上一層,
// 偽代碼實作
// ViewGroup.dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent event) {
boolean consume = false;
// 1.將事件分發給Child
if (hasChild) {
consume = child.dispatchTouchEvent();
}
// 2.若Child不消費該事件,或者沒有child,判斷自身是否消費該事件
if (!consume) {
consume = super.dispatchTouchEvent();
}
// 3.將結果向上層傳遞
return consume;
}
事件序列
MotionEvent有四種型別:
- MotionEvent.ACTION_DOWN:手指按下螢屏的瞬間(一切事件的開始)
- MotionEvent.ACTION_MOVE:手指在螢屏上移動
- MotionEvent.ACTION_UP:手指離開螢屏瞬間
- MotionEvent.ACTION_CANCEL :取消手勢,一般由程式產生,不會由用戶產生
通常一個ACTION_DOWN, 多個ACTION_MOVE,1個ACTION_UP組成一個事件序列,
當接收到一個ACTION_DOWN時,意味著一次完整事件序列的開始,通過遞回遍歷找到View樹中真正對事件進行消費的Child,并將其進行保存,這之后接收到ACTION_MOVE和ACTION_UP行為時,則跳過遍歷遞回的程序,將事件直接分發給Child這個事件的消費者;當接收到ACTION_DOWN時,則重置整個事件序列:

如圖所示,其代表了一個View樹,若序號為4的View是實際事件的消費者,那么當接收到ACTION_DOWN事件時,上層的ViewGroup則會通過遞回找到它,接下來該事件序列中的其它事件到來時,也交給4號View去處理,
這個思路似乎沒有問題,但是目前的設計中我們還缺少一把關鍵的鑰匙,那就是如何在ViewGroup中保存實際消費事件的View?
為此設計者根據View的樹形結構,設計了一個TouchTarget類,為作為一個成員屬性,描述ViewGroup下一級事件分發的目標:
public abstract class ViewGroup extends View {
// 指向下一級事件分發的`View`
private TouchTarget mFirstTouchTarget;
private static final class TouchTarget {
public View child;
public TouchTarget next;
}
}
這里應用到了樹的 深度優先搜索演算法(Depth-First-Search,簡稱DFS演算法),正如代碼所描述的,每個ViewGroup都持有一個mFirstTouchTarget, 當接收到一個ACTION_DOWN時,通過遞回遍歷找到View樹中真正對事件進行消費的Child,并保存在mFirstTouchTarget屬性中,依此類推組成一個完整的分發鏈,
比如上文的樹形圖中,序號為1的ViewGroup中的mFirstTouchTarget指向序號為2的ViewGroup,后者的mFirstTouchTarget指向序號為3的ViewGroup,依此類推,最終組成了一個 1 -> 2 -> 3 -> 4 事件的分發鏈,
對于一個 事件序列 而言,第一次接收到ACTION_DOWN事件時,通過DFS演算法為View樹事件的 分發鏈 進行初始化,在這之后,當接收到同一事件序列的其它事件如ACTION_MOVE、ACTION_UP時,則會跳過遞回流程,將事件直接分發給 分發鏈 下一級的Child中:
// ViewGroup.dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent event) {
boolean consume = false;
// ...
if (event.isActionDown()) {
// 1.第一次接收到Down事件,遞回尋找分發鏈的下一級,即消費該事件的View
// 這里可以看到,遞回深度搜索的演算法只執行了一次
mFirstTouchTarget = findConsumeChild(this);
}
// ...
if (mFirstTouchTarget == null) {
// 2.分發鏈下一級為空,說明沒有子`View`消費該事件
consume = super.dispatchTouchEvent(event);
} else {
// 3.mFirstTouchTarget不為空,必然有消費該事件的`View`,直接將事件分發給下一級
consume = mFirstTouchTarget.child.dispatchTouchEvent(event);
}
// ...
return consume;
}
在進行事件分發的時候,只有對ACTION_DOWN事件回傳true,才會收到后續的ACTION_MOVE和ACTION_UP的事件,這一事件序列的后續的事件ACTION_MOVE和ACTION_UP傳到這個View后也不再往下傳遞了,都交由自身處理,當然也可以自己強行再將事件傳遞給子View,
事件攔截機制
ViewGroup提供了onInterceptTouchEvent()以達到讓ViewGroup跳過子View的事件分發,提前結束 遞流程 ,并自身決定是否消費事件,并將結果反饋給上層級的ViewGroup處理,
額外設計這樣一個介面是否有必要?讀者認真思考可以得知,這是有必要的,最經典的使用場景就是通過重寫onInterceptTouchEvent()函式以解決開發中常見的 滑動沖突 事件;
ViewGroup的onInterceptTouchEvent()會在dispatchTouchEvent()中被呼叫,如果onInterceptTouchEvent回傳true, 則事件不會再向子View分發,而是交給ViewGroup自身處理,ViewGroup默認是不攔截的
// 偽代碼實作
// ViewGroup.dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent event) {
// 1.若需要對事件進行攔截,直接中止事件向下分發,讓自身決定是否消費事件,并將結果回傳
if (onInterceptTouchEvent(event)) {
return super.dispatchInputEvent(event);
}
// ...
// 2.若不攔截當前事件,開始事件分發流程
}
此外,為了避免額外的開銷,設計者根據 事件序列 為 事件攔截機制 做出了額外的優化處理,保證了 事件攔截的判斷在一個事件序列中只處理一次,偽代碼簡單實作如下:
// ViewGroup.dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent event) {
if (mFirstTouchTarget != null) {
// 1.若需要對事件進行攔截,直接中止事件向下分發,讓自身決定是否消費事件,并將結果回傳
if (onInterceptTouchEvent(event)) {
// 2.確定對該事件序列攔截后,因此就沒有了下一級要分發的Child
mFirstTouchTarget = null;
// 下一個事件傳遞過來時,最外層的if判斷就會為false,不會再重復執行onInterceptTouchEvent()了
return super.dispatchInputEvent(event);
}
}
// ...
// 3.若不攔截當前事件,開始事件分發流程
}
滑動沖突的處理
處理滑動沖突我們常用外部攔截法和內部攔截法,外部攔截法是在ViewGroup中通過重寫 onInterceptTouchEvent,在其中判斷什么時候需要攔截事件由自身處理,什么時候需要放行將事件傳給內層控制元件處理,內部控制元件不需要做任何處理,
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean intercepted = false;
switch (ev.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;
}
return intercepted;
}
內部攔截法是在子View中使用getParent().requestDisallowInterceptTouchEvent(true) 來進行處理,在內層控制元件的重寫方法dispatchTouchEvent中,根據邏輯來決定外層控制元件何時需要攔截事件,何時需要放行,偽代碼如下:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
if (需要父容器需要處理該事件) {
//允許外層控制元件攔截事件
getParent().requestDisallowInterceptTouchEvent(false);
} else {
//需要內部控制元件處理該事件,不允許上層viewGroup攔截
getParent().requestDisallowInterceptTouchEvent(true);
}
break;
case MotionEvent.ACTION_UP:
break;
default:
break;
}
return super.dispatchTouchEvent(ev);
}
除此之外,還需要外層控制元件在onInterceptTouchEvent中做一點處理:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
return false;
} else {
return true;
}
}
我們通過原始碼來查看實作原理:
//ViewGroup
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
···
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
//重置狀態 ACTION_DOWN
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// Check for interception.
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;
}
return handled;
}
呼叫getParent().requestDisallowInterceptTouchEvent(true) 方法會影響上面的 FLAG_DISALLOW_INTERCEPT 這個標志位,
FLAG_DISALLOW_INTERCEPT 一旦設定后,ViewGroup 將無法攔截除了 ACTION_DOWN 以外的其他點擊事件,這是因為 ViewGroup 在分發事件時,如果是 ACTION_DOWN 就會重置 FLAG_DISALLOW_INTERCEPT 這個標志位,將導致子 View 中設定的這個標記位無效,因此,當面對 ACTION_DOWN 事件時,ViewGroup 總是會呼叫自己的 onInterceptTouchEvent 方法來詢問自己是否要攔截事件,也就是說 onInterceptTouchEvent 可能在子 View 設定 requestDisallowInterceptTouchEvent 后不一定被呼叫到,所以當我們想提前處理所有的點擊事件,要選擇 dispatchTouchEvent,只有這個方法才能確保每次都被呼叫,
事件是如何被消費的
事件的消費最終是交給View來處理的,在View的dispatchTouchEvent中可以看到事件會先交給mOnTouchListener.onTouch()處理,如果未消費再由onTouchEvent()處理,
public boolean dispatchTouchEvent(MotionEvent event) {
if (onFilterTouchEventForSecurity(event)) {
ListenerInfo li = mListenerInfo;
//1.先交給mOnTouchListener.onTouch()處理
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//2.再交給onTouchEvent()處理
if (!result && onTouchEvent(event)) {
result = true;
}
}
}
在onTouchEvent中會判斷是否有TouchDelegate,有則交給TouchDelegate處理;接下來會判斷clickable屬性,clickable為true時才會消費事件,該屬性由具體View來設定;在ACTION_UP事件中會呼叫performClick()方法來觸發onClickListener的呼叫;因此我們在自定義View事件處理時,也可以通過performClick()來觸發onClickListener.onClick回呼,
public boolean onTouchEvent(MotionEvent event) {
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
//交給代理處理
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
//如果clickable為true,才會消費
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
...
performClickInternal();
...
return true;
}
return false;
}
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;
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/271596.html
標籤:其他
下一篇:常用C++代碼技巧
