前言

Android的事件分发机制也是老生常谈了,这篇文章并不是笼统的介绍这个机制,而是针对ACTION_DOWN这个事件探讨相关的细节。

dispatchTouchEvent

说到Android事件分发,一定绕不开dispatchTouchEvent函数,View和ViewGroup的该函数有很大的不同。

我们来看看ViewGroup的dispatchTouchEvent函数,它的部分源码如下:

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {...if (onFilterTouchEventForSecurity(ev)) {...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 downfinal int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex): TouchTarget.ALL_POINTER_IDS;removePointersFromTouchTargets(idBitsToAssign);final int childrenCount = mChildrenCount;if (newTouchTarget == null && childrenCount != 0) {...for (int i = childrenCount - 1; i >= 0; i--) {...if (!child.canReceivePointerEvents()|| !isTransformedTouchPointInView(x, y, child, null)) {ev.setTargetAccessibilityFocus(false);continue;}...if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {...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();}...}}// 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;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;}}...}if (!handled && mInputEventConsistencyVerifier != null) {mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);}return handled;
}

可以看到整个分发有几个关键因素:interceptedcanceledmFirstTouchTargetalreadyDispatchedToNewTouchTarget

intercepted、canceled比较好理解,重点来说说后面两个因素的是如何影响整个分发的。

ACTION_DOWN

一个完整的事件应该包含ACTION_DOWN、ACTION_MOVE、ACTION_UP。其中ACTION_DOWN是开始也是关键。

从上面dispatchTouchEvent源码中可以看到首先单独对ACTION_DOWN事件进行了处理,对所有child进行遍历,是从后向前遍历的,所以在处理上面的也就是最后添加的view会先得到事件

for (int i = childrenCount - 1; i >= 0; i--) {

对于每个child,会先判断事件是不是发生在它的区域内,不是则不处理:

if (!child.canReceivePointerEvents()|| !isTransformedTouchPointInView(x, y, child, null)) {ev.setTargetAccessibilityFocus(false);continue;
}

如果在区域内,则继续执行,下面dispatchTransformedTouchEvent这个函数就是下发事件的,我们来看下部分源码:

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,View child, int desiredPointerIdBits) {...if (newPointerIdBits == oldPointerIdBits) {if (child == null || child.hasIdentityMatrix()) {if (child == null) {handled = super.dispatchTouchEvent(event);} else {final float offsetX = mScrollX - child.mLeft;final float offsetY = mScrollY - child.mTop;event.offsetLocation(offsetX, offsetY);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) {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;
}

有不少逻辑在里面,但是仔细观察可以发现,不论那个条件,执行的代码都比较类似,如下:

if (child == null) {handled = super.dispatchTouchEvent(event);
} else {...handled = child.dispatchTouchEvent(event);...
}

当child不为null的时候,执行child的dispatchTouchEvent;为null时执行父类的dispatchTouchEvent,即View的dispatchTouchEvent函数,这个函数里会执行onTouchEvent等。所以在ViewGroup是没有onTouchEvent等函数的代码。

由于这时child不为null,所以执行了child的dispatchTouchEvent函数.

回到之前的ACTION_DOWN流程中,根据dispatchTransformedTouchEvent返回值进行不同的处理:

返回ture

如果返回true,即有一个child消费了ACTION_DOWN事件,可以看到后续执行了addTouchTarget函数,同时将alreadyDispatchedToNewTouchTarget置为true。

if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {...newTouchTarget = addTouchTarget(child, idBitsToAssign);alreadyDispatchedToNewTouchTarget = true;break;
}

addTouchTarget函数源码如下:

private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);target.next = mFirstTouchTarget;  //初始mFirstTouchTarget为null,所以这里next是nullmFirstTouchTarget = target;return target;
}

关键的一点是对mFirstTouchTarget进行了赋值。所以说true的处理是为mFirstTouchTarget赋值,将alreadyDispatchedToNewTouchTarget置为true
最后的break则跳出循环,不再遍历其他child。

返回false

如果返回false,即没有任何一个child消费ACTION_DOWN事件,直接跳过if代码,这样mFirstTouchTarget为null。

mFirstTouchTarget

那么mFirstTouchTarget、alreadyDispatchedToNewTouchTarget这两个属性在分发过程中的作用是什么?我们分别来说:

1、mFirstTouchTarget为null

mFirstTouchTarget为null,进入if语句执行dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS)

if (mFirstTouchTarget == null) {// No touch targets so treat this as an ordinary view.handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);
}

由于child是null,在dispatchTransformedTouchEvent代码中可以看到不再给任何child分发,而是调用了super.dispatchTouchEvent,即ViewGroup自己处理

这样ACTION_DOWN事件分发完了。其他事件分发时由于不再走ACTION_DOWN的处理过程,所以mFirstTouchTarget会一直为null,所以其他事件也不再向下分发了,直接ViewGroup自己处理

2、mFirstTouchTarget不为null

mFirstTouchTarget不为null,进入else语句中,会执行一个while循环

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

这时由于alreadyDispatchedToNewTouchTarget为true,所以直接给handled赋值true并不做任何处理。因为之前代码中child对ACTION_DOWN事件已经响应,所以这里的alreadyDispatchedToNewTouchTarget是为了防止重复分发ACTION_DOWN事件。

这样ACTION_DOWN事件分发完成后,分发其他事件时,alreadyDispatchedToNewTouchTarget被重新赋值false,由于不再走ACTION_DOWN的处理过程,所以alreadyDispatchedToNewTouchTarget就一直是false了,而mFirstTouchTarget会一直保持不变。在这个while循环中则会执行else语句,通过执行dispatchTransformedTouchEvent将事件直接分发给mFirstTouchTarget对应的child,即之前消费ACTION_DOWN事件的child。

总结

这样我们得到几个结论:

Android事件分发机制之ACTION_DOWN相关推荐

  1. android触摸事件分发,Android 事件分发机制

    Android 事件分发机制一直让人头痛,之前也是面向 GitHub 编程得过且过.今天下定决心了解一下,以便后面自己定制 View 效果.Android 触摸事件有三个基本类型:ACTION_DOW ...

  2. 【转】Android事件分发机制完全解析,带你从源码的角度彻底理解(下)

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/9153761 记得在前面的文章中,我带大家一起从源码的角度分析了Android中Vi ...

  3. 浅谈Android事件分发机制

    在Android实际开发过程中经常会遇到View之间的滑动冲突,如ScrollView与Listview.RecyclerView之间的嵌套使用.在很好的解决此类问题之前,我们应深入的了解Androi ...

  4. Android 事件分发机制

    Android 事件分发机制  demo验证:  https://blog.csdn.net/hty1053240123/article/details/77866302 目录 1.基础认知 2.事件 ...

  5. Android事件分发机制:基础篇:最全面、最易懂

    如何提升安卓水平?安卓开发者必须了解的事件分发机制. 最全面.最易懂的形式来讲解Android事件分发机制. 0. 前言 鉴于安卓分发机制较为复杂,故分为多个层次进行讲解,分别为基础篇.实践篇与高级篇 ...

  6. Android 系统(199)---Android事件分发机制详解

    Android事件分发机制详解 前言 Android事件分发机制是Android开发者必须了解的基础 网上有大量关于Android事件分发机制的文章,但存在一些问题:内容不全.思路不清晰.无源码分析. ...

  7. android系统(8)---android事件分发机制

    前言 Android事件分发机制是每个Android开发者必须了解的基础知识 网上有大量关于Android事件分发机制的文章,但存在一些问题:内容不全.思路不清晰.无源码分析.简单问题复杂化等等 今天 ...

  8. 一篇文章彻底搞懂Android事件分发机制

    本文讲的是一篇文章彻底搞懂Android事件分发机制,在android开发中会经常遇到滑动冲突(比如ScrollView或是SliddingMenu与ListView的嵌套)的问题,需要我们深入的了解 ...

  9. 【Android 事件分发】MotionEvent.ACTION_DOWN 按下事件分发流程( Activity | ViewGroup | View )

    Android 事件分发 系列文章目录 [Android 事件分发]事件分发源码分析 ( 驱动层通过中断传递事件 | WindowManagerService 向 View 层传递事件 ) [Andr ...

最新文章

  1. Sentry--错误日志收集框架
  2. C和C++语言编程里面常用函数或者编程技巧总结(不断更新)
  3. iOS开发学习-nonatomic和atomic的区别
  4. 【bzoj4444】[Scoi2015]国旗计划 倍增
  5. 【Python CheckiO 题解】Fizz Buzz
  6. html实现图片加载动画效果,HTML5+javascript实现图片加载进度动画效果
  7. 格式化字符串,不足补零
  8. golang笔记——数据类型
  9. SQL SERVER--单回话下的死锁
  10. java显示一个钟表_java实现时钟效果
  11. 手机12306买卧铺下铺技巧_手机上买火车票怎么买下铺
  12. 什么是静态代理,什么是动态代理
  13. jemalloc 内存管理
  14. 线性代数 计算机 视频教程,哈工大:线性代数教学视频
  15. item_get - 获得搜好货商品详情
  16. 超简单的java短信验证码,神级之作
  17. cmake详细教程(经验版)
  18. 使用TCPDF插件生成pdf以及pdf的中文处理
  19. 01.软件测试基础知识整合
  20. Windows10安装开源Mujoco

热门文章

  1. C#方法重载(overload)方法重写(override)隐藏(new)
  2. iBATIS.NET
  3. CPU : Intel CPU命名规则
  4. BI-LSTM and CRF using Keras
  5. 解决每次从cmd进入sqlplus,都得重新设置pagesize、linesize的问题
  6. Intellij IDEA + Maven + Cucumber 项目 (三):简单解释RunCukesTest.java
  7. 申请邓白氏编码和公司开发者账号需要的东西
  8. Redis自定义动态字符串(sds)模块(二)
  9. java中多条件与不定条件查询
  10. Altera之VIP TPG学习笔记