事件分发大家应该都不陌生,陌生的请直接本站搜索关键词学习。
很多时候,我们都会说:
“事件分发是针对一次手势的过程,这个手势包含一次 ACTION_DOWN,多次 ACTION_MOVE,和一次 ACTION_UP”,在 ACTION_DOWN 的时候来决定 TargetView,即决定了这次事件分发的事件流向。
今天的问题是:
- 在一次事件分发中有可能有多次 ACTION_POINT_DOWN 吗?
- 在一次事件分发中有可能有多次 ACTION_DOWN 吗?
- 如果 2 成立,是在什么样的场景下会出现?
- 基于 3 的场景,我们可能在编写有交互的自定义 View的时候有哪些地方需要注意?
给个测试 Demo: FrameLayout 里面放两个自定义的 Button,复写事件分发相关方法,然后第一个手指触摸Button1,第一个手指不抬起的情况下,第二个手指触摸 Button2,观察日志输出。
更多问答 >>
-
每日一问 为什么 Dialog 默认弹出后 Activity 就无法响应用户事件了?
2020-01-15 19:24 -
每日一问 据说很多 app 在 2019 年最后一周都出现了日期上的 bug ?
2020-01-14 20:33 -
每日一问 系统帮我们做了网络安全校验,那么还需要我们手动去做吗?
2020-01-14 20:33 -
每日一问 “别慌,你 post 一个 Runnable ,在下一帧就可以拿到了。”这种说法对吗?
2020-01-20 01:05 -
每日一问 “你这个布局可以在进入这个页面之前预加载,这样页面渲染就快了”可能有什么潜在的问题吗?
2020-01-20 01:05 -
每日一问 ViewPager 嵌套,“老子”怎么就没拦住你?
2019-12-29 23:52 -
每日一问 Activity 都重建了,你 Fragment凭什么活着?
2019-12-23 23:19 -
2019-12-20 00:08
-
2019-12-15 23:55
-
每日一问 Android 签名机制 v1 v2 v3 , 卧槽都 v3 了?
2019-12-09 23:52

在一次完整的事件分发中,会有多次 ACTION_POINT_DOWN 吗?
肯定有,因为支持多指触控的View,就是通过监听ACTION_POINT_DOWN来对新手指按下作处理的。在一次完整的事件分发中,会不会有多次 ACTION_DOWN ?这个,其实站在不同的角度去看,得到的答案也会不同。就题目中给出的Demo,当第二根手指去按下Button2之后,确实会在Button2中收到ACTION_DOWN事件,这样加上Button1按下时收到的ACTION_DOWN,一共就两次了,这两次ACTION_DOWN都是在同一次事件分发中发生的。
但是,对于一个View自身来说,同一次事件分发中是不会收到两次ACTION_DOWN的(手动重写
dispatchTouchEvent并在里面改了Action的不算)。有同学可能会觉得:“这不是自相矛盾了嘛,前面说了会收到两次ACTION_DOWN,这里又说只有一次,那前面说的第二次ACTION_DOWN从哪来?”
其实是这样的:
比如现在这个布局,一个LinearLayout里面有一个id为
当两根手指分别在red的View,一个id为blue的View。red和blue中按下时,它们各自会收到ACTION_DOWN没错,但是,它们的父容器LinearLayout收到的事件却不是两次ACTION_DOWN,而是一次ACTION_DOWN + 一次ACTION_POINTER_DOWN(忽略子View不看,只看LinearLayout的话,也是很正常的一次多指按下)!也就是LinearLayout中收到的事件Action,和它的两个子View收到的事件Action不一定是一样的!当触摸事件落在LinearLayout上是ACTION_POINTER_DOWN,到了它的第二个子View时,就变成ACTION_DOWN 了,这就说明,ViewGroup在分派事件中,肯定还会对事件的Action进行更改的。那它是怎么更改的呢?根据什么来判断的呢?
源码分析明天再写,好困啊。。。先告诉大家源码的关键位置:dispatchTouchEvent方法);dispatchTouchEvent方法);dispatchTouchEvent方法);dispatchTransformedTouchEvent方法);dispatchTransformedTouchEvent方法);split方法);SDK版本为28。
太秀了
接着前天晚上的回答:
在ViewGroup中明明收到的是ACTION_POINTER_DOWN,为什么到了子View就变成了ACTION_DOWN了呢?来看下ViewGroup的dispatchTouchEvent方法,在分派事件给子View之前,有这么一段代码:如果当前Action是MotionEvent.ACTION_POINTER_DOWN,并且
可以看到split为true的话,就会进入这个if里面。split是根据mGroupFlags是否有FLAG_SPLIT_MOTION_EVENTS标识来决定的((Flags & MASK) != 0就是常见的位操作应用)。那在什么时候,mGroupFlags里面就会有FLAG_SPLIT_MOTION_EVENTS标识呢:这个
HONEYCOMB对应的版本号就是11(Android 3.0),也就是说,从Android 3.0开始,initViewGroup方法会在ViewGroup的构造方法中被调用。split就默认为true了,所以当ViewGroup中有第二根手指按下的时候,就会进入到那个if里面。if里面会做些什么呢:它会用当前按下的手指id(第一根手指是0,第二根是1,以此类推),作为1左移的位数,比如现在是第二根手指按下,那
idBitsToAssign的值就等于1 << 1,也就是2了。接着会把触摸事件、对应的子View连同idBitsToAssign一起扔进dispatchTransformedTouchEvent方法里:先看if的条件:如果
newPointerIdBits和oldPointerIdBits是一样的话,就会直接调用子View的dispatchTouchEvent方法进行分派事件,否则先调用MotionEvent的split方法,再把split方法返回的MotionEvent分派下去。oldPointerIdBits的值可以看到它是MotionEvent的getPointerIdBits方法返回的,看一下它的代码:emmm,就是把1每次左移后的结果都放进
这样说可能不太直观,举个例子,比如当前有3只手指按下了,那么idBits里面,左移的位数就是每一个手指的id值。idBits在每次赋值后是这样的:idBits的值为1idBits值为1,即001|010 = 011 = 3好,现在回到
dispatchTransformedTouchEvent方法中。newPointerIdBits的值是等于oldPointerIdBits&desiredPointerIdBits的,因为现在是假设第二根手指按下,所以oldPointerIdBits的值会是3,desiredPointerIdBits的值也就是在dispatchTouchEvent方法中的idBitsToAssign,第二根手指按下时是2(1<<1),那么它们的运算就是:
明晚先。newPointerIdBits的值就是2,那么,这一次是不满足if的条件了,所以会进入else,也就是会调用MotionEvent的split方法了,看一下里面做了什么:大佬,大佬
我艹,坐等更新!
昨晚的回答今天看上去有点蒙,删掉重新写了。
前天讲到,当新手指按下时,最终会调用到MotionEvent的
split方法,并把newPointerIdBits传了进去,来看下它的代码:先看那个for,它会循环
进入for循环后,首先会把1左移后的结果赋值给oldPointerCount次,oldPointerCount就是已按下的手指数量,按照昨晚的假设,它的值就是2(即当前有两根手指按下)。idBit,左移的位数就是手指的id值(像昨晚那样)。接着会判断idBit&idBits是否不为0,不为0的话,newPointerCount就会自增。来模拟一下每次idBit&idBits后的值:(前天分析到,在dispatchTransformedTouchEvent方法中那个newPointerIdBits的值是2,它又作为split方法的参数传了进来,所以idBits的值也是2,二进制就是010了)emmm,1左移1位后的值刚好等于
idBits, 2&2=2,满足里面那个if的条件,所以这时候newPointerCount就会 +1(其实这个操作就是为了找出新按下的手指)。接着往下看,它先会判断当前Action是否为ACTION_POINTER_DOWN,如果是,并且
别忘了现在我们是假设二根手指按下,所以当前Action会是ACTION_POINTER_DOWN,newPointerCount等于1的话,newAction就会变成ACTION_DOWN!newPointerCount也会是1!那么最终的newAction,也就是ACTION_DOWN了!在最后会调用
nativeInitialize方法来初始化事件(注意这时候有把newAction传进去的)。这里有nativeInitialize方法的相关解析。好了,来总结一下
split方法做了什么:这时候有同学会说:不对啊,新按下的手指肯定是一根一根的按下的,那么新手指数量不就每次都是1了?既然每次按下的时候都是1,在创建新事件时的Action也就变成ACTION_DOWN了,那子View怎么能收到ACTION_POINTER_DOWN的?
是的,确实是这样,只是我们前面在分析dispatchTouchEvent方法的时候,还有一段代码没有讲!现在把它找回来:可以看到,在for循环里调用
这说明了什么呢?也就是如果dispatchTransformedTouchEvent方法前,会先判断getTouchTarget方法返回的TouchTarget对象是否不为空,如果不为空的话,会把前面计算的1左移后的值追加到TouchTarget对象的pointerIdBits里面,然后break!getTouchTarget方法返回的对象不为空,就不会往下执行dispatchTransformedTouchEvent方法,那么MotionEvent的split方法也不会被调用了!不调用split方法,事件就不会被拆分,Action也不会从ACTION_POINTER_DOWN变成ACTION_DOWN了!!!那么,
看下它的代码:getTouchTarget方法何时不返回null呢?
也就是说,只要能在getTouchTarget方法它会遍历mFirstTouchTarget这个链表,并检测每一个节点的child是不是方法参数中的child(这个child就是即将要分派触摸事件子View),如果是的话就直接返回这个节点。mFirstTouchTarget中找到【保存了参数child实例】的节点,就不会返回null。好,现在,请看
它会调用dispatchTransformedTouchEvent方法返回true后做的事。addTouchTarget方法,并把child和idBitsToAssign传了进去:它会根据
也就是在第一次分派事件给子View之后,会把子View和child和idBitsToAssign创建一个新的TouchTarget对象,然后插到mFirstTouchTarget链表的头部!pointerIdBits(可以理解为当前已按下的手指数)都记录到mFirstTouchTarget中。当新手指按下时,会先从链表mFirstTouchTarget中去查找有没有【保存有对应的子View】的节点,如果有的话,就会更新这个节点的pointerIdBits(对应上面的newTouchTarget.pointerIdBits |= idBitsToAssign;这一句),也就是刷新当前手指按下的数量。如果没有找到的话,就说明这个子View当前还没有手指按下过!所以接着就会调用dispatchTransformedTouchEvent方法,在dispatchTransformedTouchEvent方法中,如果检测到是【多指按下】的话,就会把事件拆分(因为此前的手指触摸坐标都不是属于该子View的)!最后分派拆分(调用那个会把ACTION_POINTER_DOWN改成ACTION_DOWN的split方法)后的事件给子View!分派了事件之后,又会把这个子View和手指数量记录到链表中。。。刚刚有说到 "检测到是【多指按下】的话...",这个多指按下是怎么检测呢?其实也就是前晚在dispatchTransformedTouchEvent方法中的那句newPointerIdBits == oldPointerIdBits,因为第一根手指按下时的手指id为0,1左移0位等于1,所以前面这个条件肯定是成立的,这时候就不会走else(else里面是拆分),而是直接分派事件给子View。嗯,
看一下后面的代码:getTouchTarget方法返回不为空的话,它会直接break,但是现在事件还没分派给子View呢,接下来会做些什么呢?emmm,其实它break之后,会遍历
有同学可能会问:调用mFirstTouchTarget,并在里面调用dispatchTransformedTouchEvent方法进行分发的。dispatchTransformedTouchEvent方法不是会拆分事件的吗?会是会,但拆分事件也是有条件的啊:在拆分前会先检测参数
来模拟一下第二根手指按下后oldPointerIdBits和newPointerIdBits是否相等,如果相等的话就不会拆分事件,而是直接分派给子View的。getTouchTarget方法返回不为空的情形:当第二根手指按下时,
idBitsToAssign的值就等于 1 << 1 = 2;newTouchTarget不为空,会进入到if里,此时newTouchTarget的pointerIdBits值是1(第一根手指按下时:1 << 0 = 1),即pointerIdBits= 1 | 2(此时idBitsToAssign是2)= 3,break出去之后会遍历链表调用dispatchTransformedTouchEvent:可以看到现在是把
pointerIdBits(现在的值是3)传进去了,好,来到dispatchTransformedTouchEvent方法:
到下面的if,判断两值相等(此时oldPointerIdBits的值,前晚分析过,第二根手指按下时是3,那newPointerIdBits就是 3 & 3 = 3了。newPointerIdBits和oldPointerIdBits都是3),就会直接调用子View的dispatchTouchEvent分派事件!!!总结陈小缘大佬的话,第一根手指按下之后,父View、子View都出现ACTION_DOWN,第二根手指按下,父View出现ACTION_POINTER_DOWN,通过getTouchTarget方法判断第二根手指是否Touch之前Touch过的View,是的话,子View出现ACTION_POINTER_DOWN,不是的话,父View调用dispatchTransformedTouchEvent方法,把ACTION_POINTER_DOWN改为ACTION_DOWN传递给子View。请大佬们纠正。
嗯,收到ACTION_POINTER_DOWN时会先检查目标子View是不是正在触摸(`getTouchTarget(...)` != null),如果没有在触摸的话,最终会调用MotionEvent ...查看更多
嗯,收到ACTION_POINTER_DOWN时会先检查目标子View是不是正在触摸(`getTouchTarget(...)` != null),如果没有在触摸的话,最终会调用MotionEvent的`split`方法来拆分事件。 `dispatchTransformedTouchEvent`方法是无论子View有没有在触摸,都会调用的,它里面会判断是否要拆分。
谢谢大佬,“如果没有在触摸的话,最终会调用MotionEvent的`split`方法来拆分事件。”应该是有在触摸才会调用split。
目标View有在触摸的话,getTouchTarget方法就不会返回null了。返回null就表示目标View还没有手指在触摸