您现在的位置是:首页 > 文章详情

从源码看 Android 事件分发

日期:2018-07-25点击:508

Android 事件分发是安卓中一个很精妙的地方,很多开发人员有些不解,为什么呢?因为不知道怎么去运用,其实这个地方理解起来比较简单,我写这么一篇文章就是为了串一串这个知识点。

脱离源码浅谈分发

  1. 事件分发是在最上层 View 一直往下层传递事件,并通过 onTouchEvent(MotionEvent ev) 这个方法来分发的,如果某个 View 想处理这个事件就需要重写这个 onTouchEvent() 并且返回 True ,然后写自己的逻辑处理事件。
  2. 如果是 ViewGroup 想处理这个事件呢,那么他就要重写 onInterceptTouchEvent(MotionEvent ev) 方法并返回 True ,来拦截这个事件并交给 ViewGroup 的 onTouchEvent() 来处理这个事件。
  3. 如果 ViewGroup 里面的子 View 想处理这个事件怎么办,不要担心 Android 给我们提供了 requestDisallowInterceptTouchEvent(boolean b) 方法然后传入 True 就会阻止上一级 ViewGroup 拦截事件,然后子 View 自己来处理这个事件。
  4. 上面三条结论我们怎么得出的呢?这就是 dispatchTouchEvent(MotionEvent ev) 方法的具体实现
  5. 处理 ACTION_DOWN 事件的 View 就是事件分发的目标,剩余的事件会直接分发到当前 View

滑动冲突有两种办法

  1. 如果两个上下级 View 冲突,那么给上级 View 设置 setOnTouchListener() 然后先把 ACTION_DOWN 分发给子 View ,然后在 ACTION_MOVE 中去判断具体的业务逻辑,确定到底那个 View 去处理本次事件。根据业务决定返回 True,False。
  2. 如果两个上下级 View 冲突,那么在下级 View 重写 onTouchEvent() ,然后先把 ACTION_DOWN 用 requestDisallowInterceptTouchEvent(true) 把上层事件处理拦截掉 ,然后在 ACTION_MOVE 中去判断具体的业务逻辑,确定那个 View 去处理本次事件,如果确定是上层 View 处理事件,则用 requestDisallowInterceptTouchEvent(false) 让上层 View 继续处理事件。

从源码看待事件分发

本次源码基于 Android 8.0 API 27

首先我们要知道事件分发的事件先发到 window 然后调用 Activity 或 Dialog 等组件的事件分发,然后分发到 window 再分发到 DecorView,再往我们自己的布局分发,这个流程我们将到 window 解析的时候讲,我们现在就当他是在 Activity 直接分发到我们自己的布局

1. 我们先从 ViewGroup 的 dispatchTouchEvent() 方法来说起

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) { //如果之前有未处理完的事件则取消掉,具体实现看方法里面的源码,本质是往下分发 ACTION_CANCEL 事件 cancelAndClearTouchTargets(ev); //清除 requestDisallowInterceptTouchEvent() 方法的标志,也就是每次 ACTION_DOWN 的时候这个标志都会取消 resetTouchState(); } //检查是否拦截事件 final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { // 如果 onInterceptTouchEvent 返回 True 则拦截事件,系统控件一般情况都是 False intercepted = onInterceptTouchEvent(ev); ev.setAction(action); // restore action in case it was changed } else { intercepted = false; } } else { //没有触摸目标默认为拦截掉事件 intercepted = true; } if (intercepted || mFirstTouchTarget != null) { ev.setTargetAccessibilityFocus(false); } // 检查是否取消事件 final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL; final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0; 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; 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; //查找点击的 View for (int i = childrenCount - 1; i >= 0; i--) { ... resetCancelNextUpFlag(child); if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { mLastTouchDownTime = ev.getDownTime(); ... //把新找到的触摸对象放到链表里 newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; } ev.setTargetAccessibilityFocus(false); } if (preorderedList != null) preorderedList.clear(); } if (newTouchTarget == null && mFirstTouchTarget != null) { //没有找到接受事件的 View,交给最近处理事件的 View newTouchTarget = mFirstTouchTarget; while (newTouchTarget.next != null) { newTouchTarget = newTouchTarget.next; } newTouchTarget.pointerIdBits |= idBitsToAssign; } } } if (mFirstTouchTarget == null) { //没有触摸目标 handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { //寻找到或已经有触摸目标 TouchTarget predecessor = null; TouchTarget target = mFirstTouchTarget; while (target != null) { 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; } if (cancelChild) { if (predecessor == null) { mFirstTouchTarget = next; } else { predecessor.next = next; } target.recycle(); target = next; continue; } } predecessor = target; target = next; } } 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; } 

mFirstTouchTarget 是记录点击的一个链表
我们再看一下分发事件的方法 dispatchTransformedTouchEvent()

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { final boolean handled; final int oldAction = event.getAction(); //如果事件被取消了则往下分发取消的事件 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; } ... //继续放下分发 final MotionEvent transformedEvent; if (newPointerIdBits == oldPointerIdBits) { if (child == null || child.hasIdentityMatrix()) { if (child == null) { handled = super.dispatchTouchEvent(event); } else { ... handled = child.dispatchTouchEvent(event); event.offsetLocation(-offsetX, -offsetY); } return handled; } transformedEvent = MotionEvent.obtain(event); } else { transformedEvent = event.split(newPointerIdBits); } // Perform any necessary transformations and dispatch. if (child == null) { //如果没有子 View 则调用 View 的 dispatchTouchEvent() 方法,注意 View 和 ViewGroup 的 dispatchTouchEvent() 不一样,具体看下面 handled = super.dispatchTouchEvent(transformedEvent); } else { ... //往下分发,调用子 View/ViewGroup 的 dispatchTouchEvent() 方法 handled = child.dispatchTouchEvent(transformedEvent); } // Done. transformedEvent.recycle(); return handled; } 

欧克,到此为止 ViewGroup 的两个主要的方法就差不多分析完了,我感觉我的注释打的也很全,有人会问了为什么没有 onTouchEvent() 方法,因为 ViewGroup 没有这个方法,等会我们分析 View 会将这个方法。

2. 接下来我们再看一下 View 的 dispatchTouchEvent() 方法

public boolean dispatchTouchEvent(MotionEvent event) { ... final int actionMasked = event.getActionMasked(); if (actionMasked == MotionEvent.ACTION_DOWN) { // 如果开始 ACTION_DOWN 事件则取消当前的滑动 stopNestedScroll(); } if (onFilterTouchEventForSecurity(event)) { if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) { result = true; } //这个对象的作用就如他的名字,存储各种监听接口 ListenerInfo li = mListenerInfo; //首先 ListenerInfo 不为空(则里面有监听对象),并且有 TouchListener 不为空, //并且控件的状态是启用的, //并且 TouchListener 的 onTouch 返回 true 说明事件被外面的 TouchListener 处理了, //这就说明了外面设置监听接口优先级高于自己本身的 onTouchEvent() 方法, //这就是我们解决滑动冲突第一种方法的依据 if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } //如果上述没有处理事件,则调用自身的 onTouchEvent() 方法 if (!result && onTouchEvent(event)) { result = true; } } ... if (actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_CANCEL || (actionMasked == MotionEvent.ACTION_DOWN && !result)) { //如果手指抬起、时间取消、手指按下并由本 View 处理了事件,则停止 View 的滑动 stopNestedScroll(); } return result; } 

接下来我们再看一看我们心心念念的 onTouchEvent() 方法

public boolean onTouchEvent(MotionEvent event) { ... //判断事件点击 final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE; //如果当前 View 没有启用则不处理该事件 if ((viewFlags & ENABLED_MASK) == DISABLED) { if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); } mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; return clickable; } //代理对象 if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { return true; } } if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) { switch (action) { case MotionEvent.ACTION_UP: mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; if ((viewFlags & TOOLTIP) == TOOLTIP) { handleTooltipUp(); } if (!clickable) { removeTapCallback(); //取消长按点击的延时操作 removeLongPressCallback(); mInContextButtonPress = false; mHasPerformedLongPress = false; mIgnoreNextUpEvent = false; break; } boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0; if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) { boolean focusTaken = false; if (isFocusable() && isFocusableInTouchMode() && !isFocused()) { focusTaken = requestFocus(); } if (prepressed) { setPressed(true, x, y); } if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) { removeLongPressCallback(); if (!focusTaken) { 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)) { mUnsetPressedState.run(); } removeTapCallback(); } mIgnoreNextUpEvent = false; break; case MotionEvent.ACTION_DOWN: if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) { mPrivateFlags3 |= PFLAG3_FINGER_DOWN; } mHasPerformedLongPress = false; if (!clickable) { //处理长按点击,此处是用延时队列实现的,所有很多不符合的条件下要删除掉这个延时事件 checkForLongClick(0, x, y); break; } if (performButtonActionOnTouchDown(event)) { break; } //能否在容器内滚动 boolean isInScrollingContainer = isInScrollingContainer(); if (isInScrollingContainer) { mPrivateFlags |= PFLAG_PREPRESSED; if (mPendingCheckForTap == null) { mPendingCheckForTap = new CheckForTap(); } mPendingCheckForTap.x = event.getX(); mPendingCheckForTap.y = event.getY(); postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); } else { setPressed(true, x, y); checkForLongClick(0, x, y); } break; case MotionEvent.ACTION_CANCEL: ... //清除各种状态 break; case MotionEvent.ACTION_MOVE: if (clickable) { drawableHotspotChanged(x, y); } if (!pointInView(x, y, mTouchSlop)) { //如果手指移动到 View 的外部则停止长按等事件 removeTapCallback(); removeLongPressCallback(); if ((mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); } mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; } break; } return true; } return false; } 

我们事件分发的内容基本也就这么多了,其实光讲事件分发的话 onTouchEvent() 这个方法将的有点多余了,其实与我们当前内容也没有多大的关系。

img_e89fe89627c41ab8492884c09cb90b5d.jpe
欢迎关注我的公众号
原文链接:https://yq.aliyun.com/articles/637433
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章