写点什么

Android 事件分发机制源码解析,最新 Android 通用流行框架大全

用户头像
Android架构
关注
发布于: 2021 年 11 月 05 日

final TouchTarget next = target.next;


// 如果已经在上面的遍历过程中传递过事件,跳过本次传递


if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {


handled = true;


} else {


final boolean cancelChild = resetCancelNextUpFlag(target.child)


|| intercepted;


if (dispatchTransformedTouchEvent(ev, cancelChild,


target.child, target.pointerIdBits)) {


handled = true;


}


...


}


predecessor = target;


target = next;


}


}


// Update list of touch targets for pointer up or cancel, if needed.


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);


}


}


return handled;


}


private void resetTouchState() {


clearTouchTargets();


resetCancelNextUpFlag(this);


mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;


}


private void clearTouchTargets() {


TouchTarget target = mFirstTouchTarget;


if (target != null) {


do {


TouchTarget next = target.next;


target.recycle();


target = next;


} while (target != null);


mFirstTouchTarget = null;


}


}


private TouchTarget addTouchTarget(View child, int pointerIdBits) {


TouchTarget target = TouchTarget.obtain(child, pointerIdBits);


target.next = mFirstTouchTarget;


mFirstTouchTarget = target;


return target;


}


private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,


View child, int desiredPointerIdBits) {


final boolean handled;


...


// 注意传参 child 为 null 时,调用的是自己的 dispatchTouchEvent


if (child == null) {


handled = super.dispatchTouchEvent(event);


} else {


handled = child.dispatchTouchEvent(transformedEvent);


}


return handled;


}


public boolean onInterceptTouchEvent(MotionEvent ev) {


// 默认不拦截事件


return false;


}


这个方法比较长,只要把握住主要脉络,修枝剪叶后还是非常清晰的:


(1) 判断事件是够需要被 ViewGroup 拦截


首先会根据mGroupFlags判断是否可以执行onInterceptTouchEvent方法,它的值可以通过requestDisallowInterceptTouchEvent方法设置:


public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {


if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {


// We're already in this state, assume our ancestors are too


return;


}


if (disallowIntercept) {


mGroupFlags |= FLAG_DISALLOW_INTERCEPT;


} else {


mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;


}


// Pass it up to our parent


if (mParent != null) {


// 层层向上传递,告知所有父 View 不拦截事件


mParent.requestDisallowInterceptTouchEvent(disallowIntercept);


}


}


所以我们在处理某些滑动冲突场景时,可以从子 View 中调用父 View 的requestDisallowInterceptTouchEvent方法,阻止父 View 拦截事件。


如果 view 没有设置FLAG_DISALLOW_INTERCEPT,就可以进入 onInterceptTouchEvent 方法,判断是否应该被自己拦截,ViewGroup 的 onInterceptTouchEvent 直接返回了 false,即默认是不拦截事件的,ViewGroup 的子类可以重写这个方法,内部判断拦截逻辑。


**注意:**只有当事件类型是ACTION_DOWN或者 mFirstTouchTarget 不为空时,才会走是否需要拦截事件这一判断,如果事件是ACTION_DOWN的后续事件(如ACTION_MOVEACTION_UP等),且在传递ACTION_DOWN事件过程中没有找到目标子 View 时,事件将会直接被拦截,交给 ViewGroup 自己处理。mFirstTouchTarget 的赋值会在下一节提到。


(2) 遍历所有子 View,逐个分发事件:


执行遍历分发的条件是:当前事件是ACTION_DOWNACTION_POINTER_DOWN或者ACTION_HOVER_MOVE三种类型中的一个(后两种用的比较少,暂且忽略)。所以,如果事件是ACTION_DOWN的后续事件,如ACTION_UP事件,将不会进入遍历流程!


进入遍历流程后,拿到一个子 View,首先会判断触摸点是不是在子 View 范围内,如果不是直接跳过该子 View;否则通过dispatchTransformedTouchEvent方法,间接调用child.dispatchTouchEvent达到传递的目的;


如果dispatchTransformedTouchEvent返回 true,即事件被子 View 消费,就会把 mFirstTouchTarget 设置为 child,即不为 null,并将 alreadyDispatchedToNewTouchTarget 设置为 true,然后跳出循环,事件不再继续传递给其他子 View。


可以理解为,这一步的主要作用是,在事件的开始,即传递ACTION_DOWN事件过程中,找到一个需要消费事件的子 View,我们可以称之为目标子View,执行第一次事件传递,并把 mFirstTouchTarget 设置为这个目标子 View


(3) 将事件交给 ViewGroup 自己或者目标子 View 处理


经过上面一步后,如果 mFirstTouchTarget 仍然为空,说明没有任何一个子 View 消费事件,将同样会调用 dispatchTransformedTouchEvent,但此时这个方法的View child参数为 null,所以调用的其实是super.dispatchTouchEvent(event),即事件交给 ViewGroup 自己处理。ViewGroup 是 View 的子 View,所以事件将会使用 View 的 dispatchTouchEvent(event)方法判断是否消费事件。


反之,如果 mFirstTouchTarget 不为 null,说明上一次事件传递时,找到了需要处理事件的目标子 View,此时,ACTION_DOWN的后续事件,如ACTION_UP等事件,都会传递至 mFirstTouchTarget 中保存的目标子 View 中。这里面还有一个小细节,如果在上一节遍历过程中已经把本次事件传递给子 View,alreadyDispatchedToNewTouchTarget 的值会被设置为 true,代码会判断 alreadyDispatchedToNewTouchTarget 的值,避免做重复分发。


小结:dispatchTouchEvent 方法首先判断事件是否需要被拦截,如果需要拦截会调用onInterceptTouchEvent,若该方法返回 true,事件由 ViewGroup 自己处理,不在继续传递。若事件未被拦截,将先遍历找出一个目标子 View,后续事件也将交由目标子 View 处理。若没有目标子 View,事件由 ViewGroup 自己处理。

此外,如果一个子 View 没有消费ACTION_DOWN类型的事件,那么事件将会被另一个子 View 或者 ViewGroup 自己消费,之后的事件都只会传递给目标子 View(mFirstTouchTarget)或者 ViewGroup 自身。简单来说,就是如果一个 View 没有消费ACTION_DOWN事件,后续事件也不会传递进来。

View

现在回头看上一节的第 2、3 步,不管是对子 View 分发事件,还是将事件分发给 ViewGroup 自身,最后都殊途同归,调用到了 View 的dispatchTouchEvent,这就是我们这一节分析的目标。


public boolean dispatchTouchEvent(MotionEvent event) {


...


if (onFilterTouchEventForSecurity(event)) {


// 判断事件是否先交给 ouTouch 方法处理


if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&


mOnTouchListener.onTouch(this, event)) {


return true;


}


// onTouch 未消费事件,传给 onTouchEvent


if (onTouchEvent(event)) {


return true;


}


}


...


return false;


}


代码量不多,主要做了三件事:


  1. 若 View 设置了 OnTouchListener,且处于 enable 状态时,会先调用 mOnTouchListener 的 onTouch 方法

  2. 若 onTouch 返回 false,事件传递给onTouchEvent方法继续处理

  3. 若最后 onTouchEvent 也没有消费这个事件,将返回 false,告知上层 parent 将事件给其他兄弟 View


这样,我们的分析转到了 View 的onTouchEvent方法:


public boolean onTouchEvent(MotionEvent event) {


final int viewFlags = mViewFlags;


if ((viewFlags & ENABLED_MASK) == DISABLED) {


if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PRESSED) != 0) {


mPrivateFlags &= ~PRESSED;


refreshDrawableState();


}


// 如果一个 View 处于 DISABLED 状态,但是 CLICKABLE 或者 LONG_CLICKABLE 的话,这个 View 仍然能消费事件


return (((viewFlags & CLICKABLE) == CLICKABLE ||


(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));


}


...


if (((viewFlags & CLICKABLE) == CLICKABLE ||


(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {


switch (event.getAction()) {


case MotionEvent.ACTION_UP:


boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;


if ((mPrivateFlags & PRESSED) != 0 || prepressed) {


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.


mPrivateFlags |= PRESSED;


refreshDrawableState();


}


if (!mHasPerformedLongPress) {


// This is a tap, so remove the longpress check


removeLongPressCallback();


// Only per


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


form 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 |= PREPRESSED;


if (mPendingCheckForTap == null) {


mPendingCheckForTap = new CheckForTap();


}


postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());


} else {


// Not inside a scrolling container, so show the feedback right away


mPrivateFlags |= PRESSED;


refreshDrawableState();


checkForLongClick(0);


}


break;


case MotionEvent.ACTION_CANCEL:


mPrivateFlags &= ~PRESSED;


refreshDrawableState();


removeTapCallback();


break;


case MotionEvent.ACTION_MOVE:


final int x = (int) event.getX();


final int y = (int) event.getY();


// Be lenient about moving outside of buttons


if (!pointInView(x, y, mTouchSlop)) {


// Outside button


removeTapCallback();


if ((mPrivateFlags & PRESSED) != 0) {


// Remove any future long press/tap checks


removeLongPressCallback();


// Need to switch from pressed to not pressed


mPrivateFlags &= ~PRESSED;


refreshDrawableState();


}


}


break;


}


return true;


}


return false;


}

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
Android事件分发机制源码解析,最新Android通用流行框架大全