基础概念
- 触摸事件:手指触摸屏幕时生成的事件,即
MotionEvent
。常见的触摸事件有:ACTION_DOWN
、ACTION_MOVE
、ACTION_UP
以及ACTION_CANCEL
,当存在多个手指触摸屏幕时,还会触发ACTION_POINTER_DOWN
和ACTION_POINTER_UP
事件。 - 焦点事件:
ACTION_DOWN
和ACTION_POINTER_DOWN
属于焦点事件,通过MotionEvent
中的PointerId
进行描述; - 触摸事件序列:从手指触摸屏幕开始到手指离开屏幕结束的过程中触发的一系列事件,通常以
ACTION_DOWN
事件开始、ACTION_UP
事件结束,中间有不定数量的ACTION_MOVE
事件的一系列事件。 - 滑动冲突:
View
树中相邻的层级上均存在可滑动的View
,当用户触摸屏幕时触发了ACTION_MOVE
事件导致有多个View
可以处理的情况。 - 事件分发机制:触摸屏幕产生的事件
MotionEvent
在整个View
树上分发处理的逻辑,基于事件分发机制才能更好地自定义View
和解决滑动冲突问题。
源码分析
事件分发流程
当用户触摸屏幕时,经过触摸屏、传感器等一系列硬件处理,最终生成触摸事件并由SystemServer
进程的InputMangerService
服务通过Socket
发送到目标应用进程,接着由目标Activity
的dispatchTouchEvent
方法进行处理。
// android.app.Activity
/**
* Called to process touch screen events. You can override this to
* intercept all touch screen events before they are dispatched to the
* window. Be sure to call this implementation for touch screen events
* that should be handled normally.
*
* @param ev The touch screen event.
*
* @return boolean Return true if this event was consumed.
*
* @see #onTouchEvent(MotionEvent)
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
// 通过Window进行分发最终交由根布局DecorView进行处理,如果处理成功则将事件从队列中移除,否则交由onTouchEvent继续处理;
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
// 如果整个View树都没有处理触摸事件,则由Activity的onTouchEvent处理
return onTouchEvent(ev);
}
/**
* Retrieve the current {@link android.view.Window} for the activity.
* This can be used to directly access parts of the Window API that
* are not available through Activity/Screen.
*
* @return Window The current window, or null if the activity is not
* visual.
*/
public Window getWindow() {
return mWindow;
}
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,
IBinder shareableActivityToken) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
mActivityInfo = info;
mWindow = new PhoneWindow(this, window, activityConfigCallback);
// 省略其他代码
}
接着,先将触摸事件分发给目标Activity
的窗口Window
进行处理,Window
的唯一实现类是PhoneWindow
(在Activity
实例创建之后调用attach
方法中进行创建)。经过PhoneWindow.superDispatchTouchEvent
将触摸事件交由DecorView.superDispatchTouchEvent
。因为DecorView
继承自FrameLayout
,而FrameLayout
继承自ViewGroup
,所以触摸事件最终由ViewGroup.dispatchTouchEvent
开始分发处理。
// com.android.internal.policy.PhoneWindow
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
// com.android.internal.policy.DecorView
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
mDecor.setWindow(this);
}
// 省略其他代码
}
至此,触摸事件已经被分发到View
树的根节点,开始在View
树上进行遍历(深度遍历)和分发处理。为了对触摸事件的分发流程进行分析,下面会对主要代码进行分析,和触发事件分发流程本身关联不大的部分直接略过;
// android.view.ViewGroup
// First touch target in the linked list of touch targets.
// 记录当前父View下第一个处理触摸事件的View对象,内部通过链表的形式进行维护
@UnsupportedAppUsage
private TouchTarget mFirstTouchTarget;
@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.
// step 1: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); // 清空之前事件序列处理过程中的所有TouchTarget
resetTouchState(); // 重置之前事件序列处理过程中的触摸状态标记
}
// Check for interception.
final boolean intercepted;
// step 2:根据事件类型以及子View的操作判断是否由当前ViewGroup进行拦截,如果不拦截则直接分发给子View处理(如果没有子View能处理触摸事件则交由当前ViewGroup处理),否则直接由当前ViewGroup进行处理;
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;
}
// Check for cancelation.
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
// Update list of touch targets for pointer down, if needed.
final boolean isMouseEvent = ev.getSource() == InputDevice.SOURCE_MOUSE;
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0
&& !isMouseEvent;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
// If the event is targeting accessibility focus we give it to the
// view that has accessibility focus and if it does not handle it
// we clear the flag and dispatch the event to all children as usual.
// We are looking up the accessibility focused host to avoid keeping
// state since these events are very rare.
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;
// Clean up earlier touch targets for this pointer id in case they
// have become out of sync.
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
// 获取当前触摸事件在当前ViewGroup中的相对坐标 x, y 值
final float x = ev.getXDispatchLocation(actionIndex);
final float y = ev.getYDispatchLocation(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;
// step 3:遍历并分发触摸事件给所有的子View进行处理
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;
}
// 判断触摸事件是否落在子View的区域内
if (!child.canReceivePointerEvents()
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
// 判断子View是否消费过之前的触摸事件,如果消费过之前的触摸事件,则直接结束遍历,进入下面的处理流程
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);
// // step 4:将触摸事件交给子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 = x;
mLastTouchDownY = y;
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
// The accessibility focus didn't handle the event, so clear
// the flag and do a normal dispatch to all children.
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
// 如果遍历完子View之后触摸事件没有被消费,则交给之前最早消费触摸事件的View进行处理
if (newTouchTarget == null && mFirstTouchTarget != null) {
// Did not find a child to receive the event.
// Assign the pointer to the least recently added target.
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
// step 5:如果没有子View能够处理触摸事件,则交给当前ViewGroup进行处理
// Dispatch to touch targets.
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 predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
// 之前遍历子View的过程中已经消费过触摸事件的子View
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
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;
}
}
// Update list of touch targets for pointer up or cancel, if needed.
// 如果取消或者ACTION_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;
}
/**
* Resets all touch state in preparation for a new cycle.
*/
private void resetTouchState() {
clearTouchTargets();
resetCancelNextUpFlag(this);
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; // 清除子View对父View的拦截标志
mNestedScrollAxes = SCROLL_AXIS_NONE;
}
代码分支比较多,画一个流程图将整个流程串联起来:
主要流程是:从根view
出发通过ViewGroup
的dispatchTouchEvent
将触摸事件分发给所有的子view
(如果触摸事件没有被根view
拦截的情况下),根据子view
的类型进行进一步的分发,如果子view
是ViewGroup
则重复上面的过程,否则,通过View
的dispatchTouchEvent
方法来尝试处理触摸事件,如果子view
不处理,则分发给下一个子view
进行处理,直到触摸事件被某个View
处理或者整个View
树都没有处理,整个事件分发流程结束,
对部分步骤进行进一步梳理:
step 1:首先,如果当前触摸事件是ACTION_DOWN
事件,会取消之前子View
调用当前ViewGroup
的requestDisallowInterceptTouchEvent
方法设置禁止拦截的标识位,因此子View
是无法禁止当前ViewGroup
对ACTION_DOWN
事件的拦截动作的,即对于ACTION_DOWN
事件必定会走到当前ViewGroup
的onInterceptTouchEvent
;
// Handle an initial 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();
}
step 2:根据当前触摸事件类型以及之前的触摸事件是否有对应的处理者来决定是否由当前ViewGroup
进行拦截:
- 如果当前触摸事件是
ACTION_DOWN
,则disallowIntercept
为false
(原因见step 1),此时是否拦截取决于当前ViewGroup
的onInterceptTouchEvent
方法的返回值; - 如果当前触摸事件不是
ACTION_DOWN
,但是本次事件序列中的前置事件已经被子View
消费,则根据子View
是否调用requestDisallowInterceptTouchEvent
禁止当前ViewGroup
拦截触摸事件来进行处理,禁止的情况下当前ViewGroup
无法拦截,否则调用当前ViewGroup
的onInterceptTouchEvent
方法尝试拦截; - 如果当前触摸事件不是
ACTION_DOWN
,并且之前的触摸事件是由当前ViewGroup
消费的,则后续的触摸事件直接被当前ViewGroup
拦截处理;
// 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;
}
step 3&4:根据当前ViewGroup
是否拦截来决定是否遍历子View
来处理本次触摸事件,如果当前ViewGroup
不拦截则遍历子View
来处理本次触摸事件,调用ViewGroup.dispatchTransformedTouchEvent
间接调用到View.dispatchTouchEvent
处理本次触摸事件,如果触摸事件落在了子View
的区域内,则调用View.dispatchTouchEvent
进行以下处理:
- 如果子
View
设置了OnTouchListener
则调用其onTouch
方法进行处理; - 如果没有设置
OnTouchListener
或onTouch
方法返回false
,则交由View.onTouchEvent
进行处理; - 如果子
View
是可点击的,则直接返回true
,否则继续执行; - 如果设置了
TouchDelegate
则调用其onTouchEvent
方法进行处理,如果onTouchEvent
方法返回true
,则直接返回true
; - 否则,如果子
View
是不可点击的,则直接返回false
;如果是可点击的,则根据事件类型进行对应的处理,但是最终还是返回true
;
注意点:
ViewGroup
负责拦截和分发触摸事件,View
负责处理触摸事件;
step 5:如果遍历之后此触摸事件没有被处理,则交由当前ViewGroup
进行处理,最终调用到调用到当前ViewGroup
的dispatchTouchEvent
分发处理本次触摸事件;
事件处理流程
源码分析如下:
/**
* Implement this method to handle touch screen motion events.
* <p>
* If this method is used to detect click actions, it is recommended that
* the actions be performed by implementing and calling
* {@link #performClick()}. This will ensure consistent system behavior,
* including:
* <ul>
* <li>obeying click sound preferences
* <li>dispatching OnClickListener calls
* <li>handling {@link AccessibilityNodeInfo#ACTION_CLICK ACTION_CLICK} when
* accessibility features are enabled
* </ul>
*
* @param event The motion event.
* @return True if the event was handled, false otherwise.
*/
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
// 是否可点击
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
// 是否enable
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;
}
// 如果有代理则交由代理处理
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
// ACTION_UP:如果View不可点击则移除callback并结束处理流程
if (!clickable) {
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
}
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);
}
// ACTION_UP:如果没有触发长按动作,则移除长按回调并执行点击处理
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// 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)) {
performClickInternal();
}
}
}
removeTapCallback();
}
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_DOWN:
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
}
mHasPerformedLongPress = false;
// ACTION_DOWN:如果View不可点击则通过post一个延迟任务(默认400ms)来检测是否为长按行为
if (!clickable) {
checkForLongClick(
ViewConfiguration.getLongPressTimeout(),
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
break;
}
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.
// ACTION_DOWN:如果所在的容器是可滑动的,则延迟处理本次事件(默认100ms)
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
// ACTION_DOWN:如果所在的容器是不可滑动的,则设置按压态并post一个延迟任务(默认400ms)来检测是否为长按行为
setPressed(true, x, y);
checkForLongClick(
ViewConfiguration.getLongPressTimeout(),
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
}
break;
case MotionEvent.ACTION_CANCEL:
// ACTION_CANCEL:如果View可点击,则清除按压态
if (clickable) {
setPressed(false);
}
// ACTION_CANCEL:移除所有callback
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
break;
case MotionEvent.ACTION_MOVE:
if (clickable) {
drawableHotspotChanged(x, y);
}
// Be lenient about moving outside of buttons
// ACTION_MOVE:如果移动到了View范围以外,则移除所有callback
if (!pointInView(x, y, touchSlop)) {
// Outside button
// Remove any future long press/tap checks
removeTapCallback();
removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
final boolean deepPress =
motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS;
if (deepPress && hasPendingLongPressCallback()) {
// process the long click action immediately
removeLongPressCallback();
checkForLongClick(
0 /* send immediately */,
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS);
}
break;
}
return true;
}
return false;
}
思路借鉴
从Android事件分发机制的源码实现中存在一些比较不错的思路,可以在日常开发借鉴学习。
一、模版方法模式:在分发触摸事件给子View之前,加入钩子来实现触摸事件的拦截;
// 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;
}
二、职责分离:ViewGroup
负责拦截和分发触摸事件,View
负责处理触摸事件;
/**
* Transforms a motion event into the coordinate space of a particular child view,
* filters out irrelevant pointer ids, and overrides its action if necessary.
* If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
*/
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// 省略其余代码
// Perform any necessary transformations and dispatch.
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);
}
// Done.
transformedEvent.recycle();
return handled;
}
三、缓存加速:TouchTarget
记录上次触摸事件的处理者,加速后续触摸事件的分发处理;
- 遍历子
View
分发触摸事件,记录处理触摸事件的子View
为TouchTarget
;
/**
* Adds a touch target for specified child to the beginning of the list.
* Assumes the target child is not already present.
*/
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
- 新的事件序列开始或者发生取消事件时,清空之前的
TouchTarget
记录;
/**
* Clears all touch targets.
*/
private void clearTouchTargets() {
TouchTarget target = mFirstTouchTarget;
if (target != null) {
do {
TouchTarget next = target.next;
target.recycle();
target = next;
} while (target != null);
mFirstTouchTarget = null;
}
}
四、动画处理
通过对触摸事件进行转换处理,同时兼容子View的动画,保证点击事件在动画区域得到响应;
if (!child.canReceivePointerEvents() || !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
本站资源均来自互联网,仅供研究学习,禁止违法使用和商用,产生法律纠纷本站概不负责!如果侵犯了您的权益请与我们联系!
转载请注明出处: 免费源码网-免费的源码资源网站 » 浅析Android中的View事件分发机制
发表评论 取消回复