当前位置: 首页 > news >正文

浅析Android中的View事件分发机制

Android事件分发机制浅析

  • 基础概念
  • 源码分析
    • 事件分发流程
    • 事件处理流程
  • 思路借鉴

基础概念

  1. 触摸事件:手指触摸屏幕时生成的事件,即MotionEvent。常见的触摸事件有:ACTION_DOWNACTION_MOVEACTION_UP以及ACTION_CANCEL,当存在多个手指触摸屏幕时,还会触发ACTION_POINTER_DOWNACTION_POINTER_UP事件。
  2. 焦点事件:ACTION_DOWNACTION_POINTER_DOWN属于焦点事件,通过MotionEvent中的PointerId进行描述;
  3. 触摸事件序列:从手指触摸屏幕开始到手指离开屏幕结束的过程中触发的一系列事件,通常以ACTION_DOWN事件开始、ACTION_UP事件结束,中间有不定数量的ACTION_MOVE事件的一系列事件。
  4. 滑动冲突:View树中相邻的层级上均存在可滑动的View,当用户触摸屏幕时触发了ACTION_MOVE事件导致有多个View可以处理的情况。
  5. 事件分发机制:触摸屏幕产生的事件MotionEvent在整个View树上分发处理的逻辑,基于事件分发机制才能更好地自定义View和解决滑动冲突问题。

源码分析

事件分发流程

当用户触摸屏幕时,经过触摸屏、传感器等一系列硬件处理,最终生成触摸事件并由SystemServer进程的InputMangerService服务通过Socket发送到目标应用进程,接着由目标ActivitydispatchTouchEvent方法进行处理。

	// 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@Overridepublic boolean superDispatchTouchEvent(MotionEvent event) {return mDecor.superDispatchTouchEvent(event);}// com.android.internal.policy.DecorViewpublic 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对象,内部通过链表的形式进行维护@UnsupportedAppUsageprivate TouchTarget mFirstTouchTarget;@Overridepublic 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); // 清空之前事件序列处理过程中的所有TouchTargetresetTouchState(); // 重置之前事件序列处理过程中的触摸状态标记}// 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 downfinal 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 indexfor (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的过程中已经消费过触摸事件的子Viewif (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出发通过ViewGroupdispatchTouchEvent将触摸事件分发给所有的子view(如果触摸事件没有被根view拦截的情况下),根据子view的类型进行进一步的分发,如果子viewViewGroup则重复上面的过程,否则,通过ViewdispatchTouchEvent方法来尝试处理触摸事件,如果子view不处理,则分发给下一个子view进行处理,直到触摸事件被某个View处理或者整个View树都没有处理,整个事件分发流程结束,
在这里插入图片描述
对部分步骤进行进一步梳理:

step 1:首先,如果当前触摸事件是ACTION_DOWN事件,会取消之前子View调用当前ViewGrouprequestDisallowInterceptTouchEvent方法设置禁止拦截的标识位,因此子View是无法禁止当前ViewGroupACTION_DOWN事件的拦截动作的,即对于ACTION_DOWN事件必定会走到当前ViewGrouponInterceptTouchEvent

	// 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进行拦截:

  1. 如果当前触摸事件是ACTION_DOWN,则disallowInterceptfalse(原因见step 1),此时是否拦截取决于当前ViewGrouponInterceptTouchEvent方法的返回值;
  2. 如果当前触摸事件不是ACTION_DOWN,但是本次事件序列中的前置事件已经被子View消费,则根据子View是否调用requestDisallowInterceptTouchEvent禁止当前ViewGroup拦截触摸事件来进行处理,禁止的情况下当前ViewGroup无法拦截,否则调用当前ViewGrouponInterceptTouchEvent方法尝试拦截;
  3. 如果当前触摸事件不是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进行以下处理:

  1. 如果子View设置了OnTouchListener则调用其onTouch方法进行处理;
  2. 如果没有设置OnTouchListeneronTouch方法返回false,则交由View.onTouchEvent进行处理;
  3. 如果子View是可点击的,则直接返回true,否则继续执行;
  4. 如果设置了TouchDelegate则调用其onTouchEvent方法进行处理,如果onTouchEvent方法返回true,则直接返回true
  5. 否则,如果子View是不可点击的,则直接返回false;如果是可点击的,则根据事件类型进行对应的处理,但是最终还是返回true

注意点:ViewGroup负责拦截和分发触摸事件,View负责处理触摸事件;

step 5:如果遍历之后此触摸事件没有被处理,则交由当前ViewGroup进行处理,最终调用到调用到当前ViewGroupdispatchTouchEvent分发处理本次触摸事件;

事件处理流程

源码分析如下:

    /*** 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;// 是否enableif ((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 checkremoveLongPressCallback();// Only perform take click actions if we were in the pressed stateif (!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:移除所有callbackremoveTapCallback();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范围以外,则移除所有callbackif (!pointInView(x, y, touchSlop)) {// Outside button// Remove any future long press/tap checksremoveTapCallback();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 immediatelyremoveLongPressCallback();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记录上次触摸事件的处理者,加速后续触摸事件的分发处理;
在这里插入图片描述

  1. 遍历子View分发触摸事件,记录处理触摸事件的子ViewTouchTarget
	/*** 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;}
  1. 新的事件序列开始或者发生取消事件时,清空之前的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;}

http://www.mrgr.cn/news/35268.html

相关文章:

  • vue项目PC端和移动端实现在线预览docx、excel、pdf文件
  • 嵌入式硬件实战基础篇(一)-STM32+DAC0832 可调信号发生器-产生方波-三角波-正弦波
  • 解锁微前端的优秀库
  • 外星人入侵
  • 自动化爬虫DrissionPage
  • PostgreSQL 数据加密和数据解密
  • 2024 年最新 Protobuf 结构化数据序列化和反序列化详细教程
  • 【alist】宝塔面板docker里的alist默认admin无法登录
  • 分享6个icon在线生成网站,支持AI生成
  • “被卷”还是“破卷”,咱有得选
  • 虚幻引擎的射线检测/射线追踪
  • 水墨风度——书圣故里书画名家晋京展亮相荣宝斋大厦
  • 【machine learning-17-分类(逻辑回归sigmod)】
  • YOLOX预测图片是无法保存
  • Vue|插件
  • `#include <vector>`
  • Unity图形用户界面!*★,°*:.☆( ̄▽ ̄)/$:*.°★* 。(万字解析)
  • 【C++】检测TCP链接超时——时间轮组件设计
  • CF542E Playing on Graph 题解
  • 9/24作业
  • 【ROS2】spin、spinOnce、spin_some、spin_until_future_complete
  • 利用F.interpolate()函数进行插值操作
  • 一些做题中总结的零散的python函数的简单运用
  • 【工具类】——图片缩放
  • 828华为云征文|Flexus云服务器X实例:在Docker环境下搭建java开发环境
  • 拦截器filter