【Android】原始碼分析 - View事件分發機制
事件分發物件
(1)所有 Touch 事件都被封裝成了 MotionEvent 物件,包括 Touch 的位置、時間、歷史記錄以及第幾個手指(多指觸控)等。
(2)事件型別分為 ACTION_DOWN
, ACTION_UP
,ACTION_MOVE
,ACTION_POINTER_DOWN
,ACTION_POINTER_UP
, ACTION_CANCEL
,每個事件都是以 ACTION_DOWN
開始 ACTION_UP
結束。
主要發生的Touch事件有如下四種:
- MotionEvent.ACTION_DOWN:按下View(所有事件的開始)
- MotionEvent.ACTION_MOVE:滑動View
- MotionEvent.ACTION_CANCEL:非人為原因結束本次事件
- MotionEvent.ACTION_UP:擡起View(與DOWN對應)
事件列:從手指接觸螢幕至手指離開螢幕,這個過程產生的一系列事件
任何事件列都是以DOWN事件開始,UP事件結束,中間有無數的MOVE事件,如下圖:
即當一個點選事件發生後,系統需要將這個事件傳遞給一個具體的View去處理。這個事件傳遞的過程就是分發過程。
(3)對事件的處理包括三類,分別:
傳遞——dispatchTouchEvent()函式;
攔截——onInterceptTouchEvent()函式
消費——onTouchEvent()函式和 OnTouchListener
原始碼跟蹤
觸控事件發生後,在Activity內最先接收到事件的是Activity自身的dispatchTouchEven接著Window傳遞給最頂端的View,也就是DecorView。接下來才是我們熟悉的觸控事件流程:首先是最頂端的ViewGroup(這邊便是DecorView)的dispatchTouchEvent接收到事件。並通過onInterceptTouchEvent判斷是否需要攔截。如果攔截則分配到ViewGroup自身的onTouchEvent,如果不攔截則查詢位於點選區域的子View(當事件是ACTION_DOWN的時候,會做一次查詢並根據查詢到的子View設定一個TouchTarget,有了TouchTarget以後,後續的對應id的事件如果不被攔截都會分發給這一個TouchTarget)。查詢到子View以後則呼叫dispatchTransformedTouchEvent把MotionEvent的座標轉換到子View的座標空間,這不僅僅是x,y的偏移,還包括根據子View自身矩陣的逆矩陣對座標進行變換(這就是使用setTranslationX,setScaleX等方法呼叫後,子View的點選區域還能保持和自身繪製內容一致的原因。使用Animation做變換點選區域不同步是因為Animation使用的是Canvas的矩陣而不是View自身的矩陣來做變換)。
事件分發的源頭
觸控事件發生後,在Activity內最先接收到事件的是Activity自身的dispatchTouchEvent()
,然後Activity傳遞給Activity的Window:
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
其中的這個getWindow()
得到的就是Activity的mWindow
物件,它是在attach()
方法中初始化的:
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, IVoiceInteractor voiceInteractor) {
//建立一個Window物件
mWindow = PolicyManager.makeNewWindow(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
mWindowManager = mWindow.getWindowManager();
//...省略其他程式碼...
}
呼叫了PolicyManager的makeNewWindow()
方法建立的Window物件。我們跟進去PolicyManager
這個類(這個類在Android 6.0之後原始碼中刪除了,下面是我找的5.1的原始碼):
public final class PolicyManager {
private static final String POLICY_IMPL_CLASS_NAME =
"com.android.internal.policy.impl.Policy";
private static final IPolicy sPolicy;
static {
// Pull in the actual implementation of the policy at run-time
try {
Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME);
sPolicy = (IPolicy)policyClass.newInstance();
} catch (ClassNotFoundException ex) {
throw new RuntimeException(
POLICY_IMPL_CLASS_NAME + " could not be loaded", ex);
} catch (InstantiationException ex) {
throw new RuntimeException(
POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex);
} catch (IllegalAccessException ex) {
throw new RuntimeException(
POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex);
}
}
// Cannot instantiate this class
private PolicyManager() {}
// The static methods to spawn new policy-specific objects
public static Window makeNewWindow(Context context) {
return sPolicy.makeNewWindow(context);
}
public static LayoutInflater makeNewLayoutInflater(Context context) {
return sPolicy.makeNewLayoutInflater(context);
}
public static WindowManagerPolicy makeNewWindowManager() {
return sPolicy.makeNewWindowManager();
}
public static FallbackEventHandler makeNewFallbackEventHandler(Context context) {
return sPolicy.makeNewFallbackEventHandler(context);
}
}
可以看到實際上呼叫了Policy
類的makeNewWindow()
方法:
public class Policy implements IPolicy {
private static final String TAG = "PhonePolicy";
private static final String[] preload_classes = {
"com.android.internal.policy.impl.PhoneLayoutInflater",
"com.android.internal.policy.impl.PhoneWindow",
"com.android.internal.policy.impl.PhoneWindow$1",
"com.android.internal.policy.impl.PhoneWindow$DialogMenuCallback",
"com.android.internal.policy.impl.PhoneWindow$DecorView",
"com.android.internal.policy.impl.PhoneWindow$PanelFeatureState",
"com.android.internal.policy.impl.PhoneWindow$PanelFeatureState$SavedState",
};
static {
// For performance reasons, preload some policy specific classes when
// the policy gets loaded.
for (String s : preload_classes) {
try {
Class.forName(s);
} catch (ClassNotFoundException ex) {
Log.e(TAG, "Could not preload class for phone policy: " + s);
}
}
}
public Window makeNewWindow(Context context) {
return new PhoneWindow(context);
}
public LayoutInflater makeNewLayoutInflater(Context context) {
return new PhoneLayoutInflater(context);
}
public WindowManagerPolicy makeNewWindowManager() {
return new PhoneWindowManager();
}
public FallbackEventHandler makeNewFallbackEventHandler(Context context) {
return new PhoneFallbackEventHandler(context);
}
}
原來是一個PhoneWindow
物件,我們趕緊看看它的superDispatchTouchEvent
方法,原來是繼續呼叫了DecorView
的superDispatchTouchEvent()
方法:
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
這個DecorView
是PhoneWindow
的一個內部類,它繼承了FrameLayout:
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
//...省略其他程式碼...
}
而FrameLayout本身沒有實現dispatchTouchEvent()
這個方法,它繼承了ViewGroup:
public class FrameLayout extends ViewGroup {...}
下面我們來看一下ViewGroup的dispatchTouchEvent()
方法原始碼。
ViewGroup開始分發
public boolean dispatchTouchEvent(MotionEvent ev) {
...
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
if (actionMasked == MotionEvent.ACTION_DOWN) {
// 觸控事件流開始,重置觸控相關的狀態
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// 關鍵點1: 檢測當前是否需要攔截事件
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
// 處理呼叫requestDisallowInterceptTouchEvent()來決定是否允許ViewGroup攔截事件
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action);
} else {
intercepted = false;
}
} else {
// 當前沒有TouchTarget也不是事件流的起始的話,則直接預設攔截,不通過onInterceptTouchEvent判斷。
intercepted = true;
}
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
// 檢測是否需要把多點觸控事件分配給不同的子View
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
// 當前事件流對應的TouchTarget物件
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
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
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
// 當前事件是事件流的初始事件(包括多點觸控時第二、第三點燈的DOWN事件),清除之前相應的TouchTarget的狀態
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
//通過for迴圈,遍歷了當前ViewGroup下的所有子View
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
// 關鍵點2: 判斷當前遍歷到的子View能否接受事件,如果不能則直接continue進入下一次迴圈
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
// 當前子View能接收事件,為子View建立TouchTarget
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
// 呼叫dispatchTransformedTouchEvent把事件分配給子View
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
// 把TouchTarget新增到TouchTarget列表的第一位
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
if (newTouchTarget == null && mFirstTouchTarget != null) {
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
if (mFirstTouchTarget == null) {
// 目前沒有任何TouchTarget,所以直接傳null給dispatchTransformedTouchEvent
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// 把事件根據pointer id分發給TouchTarget列表內的所有TouchTarget,用來處理多點觸控的情況
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
// 遍歷TouchTarget列表
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
// 根據TouchTarget的pointerIdBits來執行dispatchTransformedTouchEvent
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
// 處理CANCEL和UP事件的情況
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
final int actionIndex = ev.getActionIndex();
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
removePointersFromTouchTargets(idBitsToRemove);
}
}
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}
上面的程式碼比較長,先不用細看。下面一張圖來簡化對照著理解一下:
關鍵點1:只有
ACTION_DOWN
事件或者mFirstTouchTarget
為空時,並且沒有呼叫過requestDisallowInterceptTouchEvent()
去阻止該ViewGroup攔截事件的話,才可能執行攔截方法onInterceptTouchEvent()
關鍵點2:判斷當前遍歷到的子View能否接受事件主要由兩點來衡量:子元素是否在播動畫(
canViewReceivePointerEvents()
方法);點選事件座標是否落在子元素區域內(“)。
//子元素是否在播動畫
private static boolean canViewReceivePointerEvents(View child) {
return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null;
}
//點選事件座標是否落在子元素區域內
protected boolean isTransformedTouchPointInView(float x, float y, View child,
PointF outLocalPoint) {
float localX = x + mScrollX - child.mLeft;
float localY = y + mScrollY - child.mTop;
if (! child.hasIdentityMatrix() && mAttachInfo != null) {
final float[] localXY = mAttachInfo.mTmpTransformLocation;
localXY[0] = localX;
localXY[1] = localY;
child.getInverseMatrix().mapPoints(localXY);
localX = localXY[0];
localY = localXY[1];
}
//檢測座標是否在child區域內
final boolean isInView = child.pointInView(localX, localY);
if (isInView && outLocalPoint != null) {
outLocalPoint.set(localX, localY);
}
return isInView;
}
當子View滿足這兩個條件之後,ViewGroup就會呼叫dispatchTransformedMotionEvent()
方法去交給子元素處理:
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
final int oldAction = event.getAction();
// 處理CANCEL的情況,直接把MotionEvent的原始資料分發給子View或者自身的onTouchEvent
// (這邊呼叫View.dispatchTouchEvent,而View.dispatchTouchEvent會再呼叫onTouchEvent方法,把MotionEvent傳入)
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
// 對MotionEvent自身的pointer id和當前我們需要處理的pointer id做按位與,得到共有的pointer id
final int oldPointerIdBits = event.getPointerIdBits();
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
// 沒有pointer id需要處理,直接返回
if (newPointerIdBits == 0) {
return false;
}
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
// 關鍵點1: 子View為空,直接交還給自身的onTouchEvent處理
handled = super.dispatchTouchEvent(event);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
// 關鍵點2:交給子view的dispatchTouchEvent()方法去處理
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
// MotionEvent自身的pointer id和當前需要處理的pointer id不同,把不需要處理的pointer id相關的資訊剔除掉。
transformedEvent = event.split(newPointerIdBits);
}
if (child == null) {
// 子View為空,直接交還給自身的onTouchEvent處理
handled = super.dispatchTouchEvent(transformedEvent);
} else {
// 根據當前的scrollX、scrollY和子View的left、top對MotionEvent的觸控座標x、y進行偏移
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
// 獲取子View自身矩陣的逆矩陣,並對MotionEvent的座標相關資訊進行矩陣變換
transformedEvent.transform(child.getInverseMatrix());
}
// 把經過偏移以及矩陣變換的事件傳遞給子View處理
handled = child.dispatchTouchEvent(transformedEvent);
}
transformedEvent.recycle();
return handled;
}
子View消費事件
然後我們看看View的dispatchTouchEvent()
方法:
public boolean dispatchTouchEvent(MotionEvent event) {
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
if (onFilterTouchEventForSecurity(event)) {
// 如果存在mOnTouchListener,直接交給它消費Touch事件
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
// 交給onTouchEvent()方法消費Touch事件
if (!result && onTouchEvent(event)) {
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
// Clean up after nested scrolls if this is the end of a gesture;
// also cancel it if we tried an ACTION_DOWN but we didn't want the rest
// of the gesture.
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
注意這裡View的mOnTouchListener.onTouch(this, event)
和onTouchEvent(event)
都是放在if判斷條件裡的,也就是說他們的返回值會影響事件是否繼續往下傳遞。如果mOnTouchListener.onTouch(this, event)
返回true的話,就不會再執行此子View的onTouchEvent(event)
方法了。
最後我們再看下View的onTouchEvent()
方法是如何消費事件的呢?
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
// The button is being released before we actually
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
setPressed(true, x, y);
}
if (!mHasPerformedLongPress) {
// This is a tap, so remove the longpress check
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)) {
//關鍵點
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
break;
case MotionEvent.ACTION_DOWN:
mHasPerformedLongPress = false;
if (performButtonActionOnTouchDown(event)) {
break;
}
// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean isInScrollingContainer = isInScrollingContainer();
// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
checkForLongClick(0);
}
break;
case MotionEvent.ACTION_CANCEL:
setPressed(false);
removeTapCallback();
removeLongPressCallback();
break;
case MotionEvent.ACTION_MOVE:
drawableHotspotChanged(x, y);
// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
removeTapCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
// Remove any future long press/tap checks
removeLongPressCallback();
setPressed(false);
}
}
break;
}
return true;
}
return false;
}
我們這裡只注意一下在這個View接收到ACTION_UP
事件之後,會呼叫到performClick()
方法:
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
//通知回撥mOnClickListener的onClick方法
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
return result;
}
這裡能說明View的OnClickListener
的onClick()
事件的執行時機是在整個TouchEvent事件列的最後才會執行。
Touch案例分析
問題:當ViewGroup的
onInterceptTouchEvent()
函式分別返回true和false時,這個ViewGroup和View1分別能接收到DOWN、MOVE、UP中的什麼事件?
ViewGroup的onInterceptTouchEvent() 方法 |
ViewGroup | View1 |
---|---|---|
return true | 僅能接收到DOWN事件 | 什麼都接收不到 |
return false | 三種都能接收到 | 三種都能接收到 |
另一個案例可以參考這篇文章:Android 程式設計下 Touch 事件的分發和消費機制
總結
(1)Touch事件分發中只有兩個主角:ViewGroup和View。ViewGroup包含onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent三個相關事件。View包含dispatchTouchEvent、onTouchEvent兩個相關事件。其中ViewGroup又繼承於View。
(2)事件從 Activity.dispatchTouchEvent()開始傳遞,只要沒有被停止或攔截,從最上層的 ViewGroup開始一直往下(子View)傳遞。子View可以通過
onTouchEvent()
對事件進行處理。(3)事件由ViewGroup傳遞給子 View,ViewGroup 可以通過
onInterceptTouchEvent()
對事件做攔截,停止其往下傳遞。(4)如果事件從上往下傳遞過程中一直沒有被停止,且最底層子 View 沒有消費事件,事件會反向往上傳遞,這時父 View(ViewGroup)可以進行消費,如果還是沒有被消費的話,最後會到 Activity 的 onTouchEvent()函式。
(5) 如果 View 沒有對 ACTION_DOWN 進行消費,之後的其他事件不會傳遞過來。
(6)OnTouchListener 優先於 onTouchEvent()對事件進行消費。
(7)當Acitivty接收到Touch事件時,將遍歷子View進行Down事件的分發。ViewGroup的遍歷可以看成是遞迴的。分發的目的是為了找到第一個真正要處理本次完整觸控事件的View,這個View會在onTouchuEvent結果返回true。
(8)當某個子View返回true時,會中止Down事件的分發,同時在ViewGroup中記錄該子View。接下去的Move和Up事件將由該子View直接進行處理。由於子View是儲存在ViewGroup中的,多層ViewGroup的節點結構時,上級ViewGroup儲存的會是真實處理事件的View所在的ViewGroup物件:如ViewGroup0-ViewGroup1-TextView的結構中,TextView返回了true,它將被儲存在ViewGroup1中,而ViewGroup1也會返回true,被儲存在ViewGroup0中。當Move和UP事件來時,會先從ViewGroup0傳遞至ViewGroup1,再由ViewGroup1傳遞至TextView。
(9)當ViewGroup中所有子View都不捕獲Down事件時,將觸發ViewGroup自身的onTouch事件。觸發的方式是呼叫super.dispatchTouchEvent函式,即父類View的dispatchTouchEvent方法。在所有子View都不處理的情況下,觸發Acitivity的onTouchEvent方法。
(10)ViewGroup預設不攔截任何事件。原始碼中的ViewGroup的
onInterceptTouchEvent()
方法預設返回false。(11)View沒有
onInterceptTouchEvent()
方法。一旦點選事件傳遞給它,就會呼叫它的onTouchEvent
方法(12)我們可以發現ViewGroup沒有onTouchEvent事件,說明他的處理邏輯和View是一樣的。
(13)子view如果消耗了事件,那麼ViewGroup就不會在接受到事件了。