Android事件分发是指触摸屏幕的事件分发,在手指触摸屏幕后所产生的一系列事件中,典型的事件类型有如下几种:
- MotionEvent.ACTION_DOWN ——手指刚接触屏幕
- MotionEvent.ACTION_MOVE——手指在屏幕上面滑动
- MotionEvent.ACTION_UP——手指从屏幕上松开的一瞬间
点击事件的分发过程由三个很重要的方法来共同完成:
public boolean dispatchTouchEvent(MotionEvent ev)
用来进行事件的分发。如果事件能够传递给当前View,那么此方法一定会被调用,返回结果受当前View的onTouchEvent(MotionEvent event)和下级View的dispatchTouchEvent方法的影响,表示是否消耗当前事件。
public boolean onInterceptTouchEvent(MotionEvent ev)
在上述方法内部调用,ViewGroup独有方法,用来判断是否拦截某个事件,如果当前View拦截了某个事件,那么在同一个事件序列当中,此方法不会被再次调用,返回结果表示是否拦截当前事件。
public boolean onTouchEvent(MotionEvent event)
在dispatchTouchEvent方法中调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列中,当前View无法再次接收到事件。
事件分发是按照Activity -> ViewGroup->View进行的
代码演示
class MainActivity : ComponentActivity() {
companion object{
private const val TAG = "MainActivity"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_layout)
}
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
when(ev?.action){
MotionEvent.ACTION_DOWN -> Log.d(TAG, "dispatchTouchEvent: DOWN")
MotionEvent.ACTION_UP -> Log.d(TAG, "dispatchTouchEvent: UP")
}
return super.dispatchTouchEvent(ev)
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
when(event?.action){
MotionEvent.ACTION_DOWN -> Log.d(TAG, "onTouchEvent: DOWN")
MotionEvent.ACTION_UP -> Log.d(TAG, "onTouchEvent: UP")
}
return super.onTouchEvent(event)
}
}
private const val TAG = "DispatchViewGroup"
class DispatchViewGroup : LinearLayout {
constructor(context : Context) : this(context ,null)
constructor(context : Context,attributeSet: AttributeSet?) : this(context,attributeSet,0)
constructor(context : Context,attributeSet: AttributeSet?,defStyleAttr: Int):super(context,attributeSet,defStyleAttr)
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
when(ev?.action){
MotionEvent.ACTION_DOWN -> Log.d(TAG, "dispatchTouchEvent: DOWN")
MotionEvent.ACTION_UP -> Log.d(TAG, "dispatchTouchEvent: UP")
}
return super.dispatchTouchEvent(ev)
}
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
when(ev?.action){
MotionEvent.ACTION_DOWN -> Log.d(TAG, "onInterceptTouchEvent: DOWN")
MotionEvent.ACTION_UP -> Log.d(TAG, "onInterceptTouchEvent: UP")
}
return super.onInterceptTouchEvent(ev)
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
when(event?.action){
MotionEvent.ACTION_DOWN -> Log.d(TAG, "onTouchEvent: DOWN")
MotionEvent.ACTION_UP -> Log.d(TAG, "onTouchEvent: UP")
}
return super.onTouchEvent(event)
}
}
class DispatchView : TextView {
constructor(context : Context) : super(context)
constructor(context : Context, attributeSet: AttributeSet?) : super(context,attributeSet)
constructor(context : Context, attributeSet: AttributeSet?, defStyleAttr: Int):super(context,attributeSet,defStyleAttr)
override fun dispatchTouchEvent(event: MotionEvent?): Boolean {
when(event?.action){
MotionEvent.ACTION_DOWN -> Log.d(TAG, "dispatchTouchEvent: down")
MotionEvent.ACTION_UP -> Log.d(TAG, "dispatchTouchEvent: up")
}
return super.dispatchTouchEvent(event)
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
when(event?.action){
MotionEvent.ACTION_DOWN -> Log.d(TAG, "onTouchEvent: down")
MotionEvent.ACTION_UP -> Log.d(TAG, "onTouchEvent: up")
}
return super.onTouchEvent(event)
}
}
点击Button,打印的log是:
2023-08-23 20:01:41.150 14174-14174 MIUIInput com.example.dispathceventdemo D [MotionEvent] ViewRootImpl windowName 'com.example.dispathceventdemo/com.example.dispathceventdemo.MainActivity', { action=ACTION_DOWN, id[0]=0, pointerCount=1, eventTime=144660345, downTime=144660345, phoneEventTime=20:01:41.140 } moveCount:0
2023-08-23 20:01:41.151 14174-14174 MainActivity com.example.dispathceventdemo D dispatchTouchEvent: DOWN
2023-08-23 20:01:41.151 14174-14174 DispatchViewGroup com.example.dispathceventdemo D dispatchTouchEvent: DOWN
2023-08-23 20:01:41.151 14174-14174 DispatchViewGroup com.example.dispathceventdemo D onInterceptTouchEvent: DOWN
2023-08-23 20:01:41.151 14174-14174 DispatchView com.example.dispathceventdemo D dispatchTouchEvent: down
2023-08-23 20:01:41.151 14174-14174 DispatchView com.example.dispathceventdemo D onTouchEvent: down
2023-08-23 20:01:41.152 14174-14174 DispatchViewGroup com.example.dispathceventdemo D onTouchEvent: DOWN
2023-08-23 20:01:41.152 14174-14174 MainActivity com.example.dispathceventdemo D onTouchEvent: DOWN
2023-08-23 20:01:41.379 14174-14174 MIUIInput com.example.dispathceventdemo D [MotionEvent] ViewRootImpl windowName 'com.example.dispathceventdemo/com.example.dispathceventdemo.MainActivity', { action=ACTION_UP, id[0]=0, pointerCount=1, eventTime=144660578, downTime=144660345, phoneEventTime=20:01:41.373 } moveCount:1
2023-08-23 20:01:41.379 14174-14174 MainActivity com.example.dispathceventdemo D dispatchTouchEvent: UP
2023-08-23 20:01:41.379 14174-14174 MainActivity com.example.dispathceventdemo D onTouchEvent: UP
当点击事件没有处理时,可以看到时间处理的流程大致如下:
源码分析
Activity中dispatchTouchEvent
/**
* 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.
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
1.根据描述,dispatchTouchEvent被调用来处理屏幕触摸事件,可以在Window接收到触摸事件之前拦截触摸事件。
2.ACTION_DOWN时,会调用onUserInteraction,表示用户在和屏幕交互,onUserInteraction是一个空方法,可以重写该方法,可以在此实现与用户的交互功能。
3.getWindow().superDispatchTouchEvent 是调用Window中的superDispatchTouchEvent,Window是一个抽象类,唯一实现类是PhoneWindow。
4.superDispatchTouchEvent返回true,dispatchTouchEvent就结束,不会执行Activity中的onTouchEvent方法。
/**
*PhoneWindow
**/
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
PhoneWindow中调用了DecorView中 superDispatchTouchEvent(MotionEvent event)
//DecorView
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
这里的super.dispatchTouchEvent调用的是ViewGroup中的dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) {
//mInputEventConsistencyVerifier是用来调试使用的,正式版本中为null
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
// If the event targets the accessibility focused view and this is it, start
// normal event dispatch. Maybe a descendant is what will handle the click.
//处理辅助功能,在设置里面开启辅助功能才能生效
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
ev.setTargetAccessibilityFocus(false);
}
//申明事件是否被消费
boolean handled = false;
//onFilterTouchEventForSecurity进行安全检查,为了防范恶意软件误导用户
//主要过滤的是当前Window被遮挡的情况下的触摸事件。
if (onFilterTouchEventForSecurity(ev)) {
//Android是用一个32位的Int值表示一次TouchEvent,低8位表示具体动作,比如按下,抬起,滑动
//这里涉及到多点触控
//第一根手指按下产生事件:ACTION_DOWN
//第二根手指按下产生事件:ACTION_POINTER_DOWN
//此时抬起一根手指产生事件:ACTION_POINTER_UP
//再抬起另一根手指产生事件:ACTION_UP
//通过 ev.getAction() 得到的值包含了 动作(低8位)、触控点索引(9-16位)等,而不单单是上述的几种行为动作
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// 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.
//设置状态和初始化,为一个新的TouchEvent做准备。会调用cancelAndClearTouchTargets()和resetTouchState()
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// Check for interception.
//ViewGroup拦截结果
final boolean intercepted;
//刚刚按下屏幕时,mFirstTouchTarget是为null,cancelAndClearTouchTargets中清除上一次点击事件时,会将mFirstTouchTarget置空
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//disallowIntercept = 是否禁用事件拦截的功能(默认为false),可通过调用requestDisallowInterceptTouchEvent() 修改
//用来确定ViewGroup是否禁止拦截事件
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//默认onInterceptTouchEvent返回false,不拦截,如果想拦截,应该重写onInterceptTouchEvent这个方法
//如果ACTION_DOWN 时候返回true,子View怎么都无法响应触摸事件
intercepted = onInterceptTouchEvent(ev);
//防止onInterceptTouchEvent 中对action 修改
//所以重新设置一遍
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;
}
// If intercepted, start normal event dispatch. Also if there is already
// a view that is handling the gesture, do normal event dispatch.
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}
// Check for cancelation.
//canceled = true条件
//1.此View 被ViewGroup或Window移除
//2. actionMasked == MotionEvent.ACTION_CANCEL
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;
//split 作用是判断可以把事件分发到多个子View , 多点触摸开关
//这个同样在ViewGroup中提供了public的方法: setMotionEventSplittingEnabled ( boolean split)来设置.
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0
&& !isMouseEvent;
<!-- view处理触摸事件的关键代码 -->
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
//如果是辅助功能事件,会寻找他的targetview来接收这个事件
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
//ACTION_POINTER_DOWN :代表用户又使用一个手指触摸到屏幕上,也就是说,在已经有一个触摸点的情况下,有新出现了一个触摸点。
//ACTION_HOVER_MOVE : 鼠标事件 , 指针在窗口或者View区域移动,但没有按下。
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
// 多点触控会有不同的索引,获取索引号
//该索引位于MotionEvent中的一个数组没索引值就是数组的下标值
//只有up或者down事件才会携带索引值
final int actionIndex = ev.getActionIndex(); // always 0 for down
// 这个整型变量记录了TouchTarget中view所对应的触控点id
// 触控点id的范围是0-31,整型变量中一个二进制为1,则对应绑定该id的触控点
//这里根据是否需要分离,对触控点id进行记录
// 而如果不需要分离,则默认接受所有触控点的事件
// ALL_POINTER_IDS = -1; (-1 二进制 = 11111111111111111111111111111111)
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
//清空这个手指idBitsToAssign对应的TouchTarget链表。
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
//获取触摸坐标
final float x =
isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex);
final float y =
isMouseEvent ? ev.getYCursorPosition() : ev.getY(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;
//倒序遍历子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;
}
//1.触摸坐标(x,y)在child的可视范围内
//2.child可接受触摸事件,是指child的是可见的(VISIBLE);或者虽然不可见,但是位于动画状态。
if (!child.canReceivePointerEvents()
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
// 从缓存中获取newTouchTarget
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
//newTouchTarget != null 表示之前处理过
// 重新设置手指id 跳出循环
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
// 调用dispatchTransformedTouchEvent()将触摸事件分发给child。
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 index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
//如果child.dispatchTouchEvent(event) = true
//子view包装成TouchTarge , 头插法插入到mFirstTouchTarget 的单链表中
//把 mFirstTouchTarget 指向了child,同时把newTouchTarget也指向了child
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();
}
if (newTouchTarget == null && mFirstTouchTarget != null) {
//这种情况 , mFirstTouchTarget!=null ,newTouchTarget == null
//表示之前能消费事件 , 但是现在不行了(手指不在之前能消费那个view范围内)
//但是newTouchTarget = mFirstTouchTarget , 事件还是交给之前能消费那个view处理
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// 没有能消费的子view . 自己super.dispatchTouchEvent处理
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) {
//如果子view消费了事件,handled = true
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
//如果cancelChild =true , 给子view发送ACTION_CANCEL事件
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
//如果cancelChild =true , 将子view从mFirstTouchTarget 链表移除
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
//遍历mFirstTouchTarget链表
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);
}
}
if (!handled && mInputEventConsistencyVerifier != null) {
//移除idBitsToRemove 对应的TouchTarget
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}
viewGroup中处理事件流程比较长。主要处理步骤如下:
1.接收到ACTION_DOWN时,将之前保存的触摸状态全部清空
2.检查disallowIntercept 是否禁用事件拦截的功能(默认为false),可通过调用requestDisallowInterceptTouchEvent() 修改,如果是disallowIntercept = true,禁止调用onInterceptTouchEvent方法
3.当!canceled && !intercepted即没有被取消和ViewGroup拦截,会进入到childView的接受流程
4.通过dispatchTransformedTouchEvent来处理子view是否接收点击事件,
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;
}
//oldPointerIdBits表示现在所有的触控id
// desiredPointerIdBits来自于该view所在的touchTarget,表示该view感兴趣的触控点id
// 因为desiredPointerIdBits有可能全是1,所以需要和oldPointerIdBits进行位与
// 得到真正可接受的触控点信息
final int oldPointerIdBits = event.getPointerIdBits();
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
// If for some reason we ended up in an inconsistent state where it looks like we
// might produce a motion event with no pointers in it, then drop the event.
// 控件处于不一致的状态。正在接受事件序列却没有一个触控点id符合
if (newPointerIdBits == 0) {
return false;
}
// 来自原始MotionEvent 的新的MotionEvent,只包含目标感兴趣的触控点
// 最终派发的是这个MotionEvent
final MotionEvent transformedEvent;
//两者相等,表示该view接受所有的触控点的事件
//这个时候transfromedEvent相当于原始MotionEvent的复制
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
// 当目标控件不存在通过setScaleX()等方法进行的变换时
// 为了效率会将原市事件简单地进行控件位置与棍定量变换之后
// 发送给目标的dispatchTouchEvent()方法返回
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.
// 对Motion Event的坐标系,转换为目标控件的坐标系并进行分发
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());
}
// 调用子view的方法进行分发
handled = child.dispatchTouchEvent(transformedEvent);
}
// 分发完毕,回收MotionEvent
transformedEvent.recycle();
return handled;
}
View中的dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent event) {
// If the event should be handled by accessibility focus first.
//首先检查是否有焦点,没有焦点时是无法处理点击事件
if (event.isTargetAccessibilityFocus()) {
// We don't have focus or no virtual descendant has it, do not handle the event.
//没有焦点
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
// We have focus and got the event, then use normal event dispatch.
event.setTargetAccessibilityFocus(false);
}
boolean result = false;
//系统调试的代码,在ViewGroup的dispatchTouchEvent中也有
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
//停止滚动
stopNestedScroll();
}
//安全检查,过滤掉一些不合法的事件,比如当前的View的窗口被遮挡了。
if (onFilterTouchEventForSecurity(event)) {
//handleScrollBarDragging 处理鼠标拖拽时的滚动,手指触摸时,返回false
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//ListenerInfo是View的内部类,里面有各种各样的listener,例如OnClickListener,OnLongClickListener,OnTouchListener等等
ListenerInfo li = mListenerInfo;
//1.设置了mOnTouchListener
//2.View可点击
//3.执行OnTouchListener.onTouch方法
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
//执行了OnTouchListener.onTouch,表示该View处理了当前触摸事件
result = true;
}
//1.没有设置setOnTouchListener
//2.执行onTouchEvent
if (!result && onTouchEvent(event)) {
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
// Clean up after nested scrolls if this is the end of a gesture;
// also cancel it if we tried an ACTION_DOWN but we didn't want the rest
// of the gesture.
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
View中dispatchTouchEvent大致流程如下:
1.View中dispatchTouchEvent默认返回false。
2.OnTouchListener.onTouch是先于onTouchEvent执行的
3. 当OnTouchListener.onTouch 返回true时,onTouchEvent无法执行。
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
//判断是否可点击,长按和上下文点击(上下文菜单,鼠标右键)
//mViewFlags设置了CLICKABLE、LONG_CLICKABLE、CONTEXT_CLICKABLE中任一一个flags
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
//view 是DISABLED,通过setEnable设置
//PFLAG4_ALLOW_CLICK_WHEN_DISABLED 标志位被设置为 0,即不允许 View 在 disable 状态下被点击
//PFLAG4_ALLOW_CLICK_WHEN_DISABLED 是在Android12时引入,用来设置是否允许 View 在 disable 状态下被点击
if ((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;
}
//如果控件设置了触摸代理,需要通过代理判断是否消耗了触摸事件
//mTouchDelegate是通过setTouchDelegate()设置
//TouchDelegate可以用来扩大触摸面积
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
//View是clickable is true
//调用 setTooltipText()给 View 设置了提示文本,当用户将鼠标悬停在视图上或长按视图时,Android 会显示 tooltipText 属性的值
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
//抬手后 tooltip延迟一会消失
if ((viewFlags & TOOLTIP) == TOOLTIP) {
handleTooltipUp();
}
if (!clickable) {
removeTapCallback();
removeLongPressCallback();
//触控笔按键被按下或者鼠标右键按下的时候为true,在按键释放或触控笔提起来的时候为false
mInContextButtonPress = false;
mHasPerformedLongPress = false;
//下一个ACTION_UP类型事件应该被忽略,按键释放或触控笔提起来的时候被设置为true
mIgnoreNextUpEvent = false;
break;
}
//PFLAG_PREPRESSED是在ACTION_DOWN事件发生时,在可滚动容器内设置延迟
//如果PFLAG_PRESSED标识存在,说明控件处于按压状态
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.
//变量focusTaken默认为false,影响点击事件是否执行
boolean focusTaken = false;
//isFocusable 是否可以去获取焦点
//isFocusableInTouchMode 判断该控件在触摸模式下是否可以获取焦点
//isFocused 是判断当前是否已经获取到焦点
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();//获取焦点
}
//prepressed 代表CheckForTap类型事件消息还没有执行,就触发了ACTION_UP类型事件
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);
}
//mHasPerformedLongPress 变量代表已经执行了长按事件,如果执行了长按事件,就不会执行点击事件了
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform 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)) {
//执行点击事件
performClickInternal();
}
}
}
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();
}
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_DOWN:
//如果触摸源是触摸屏
//将会在变量mPrivateFlags3上设置PFLAG3_FINGER_DOWN标志
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
}
//将变量mHasPerformedLongPress的值设为false
//该变量是为了在一次长按过程中执行过长按之后,避免执行点击事件
mHasPerformedLongPress = false;
//clickable = false,会进入mHasPerformedLongPress
if (!clickable) {
//长按处理
checkForLongClick(
ViewConfiguration.getLongPressTimeout(),
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
break;
}
//判断鼠标右键点击了,会调用showContextMenu()方法,显示上下文菜单
if (performButtonActionOnTouchDown(event)) {
break;
}
// Walk up the hierarchy to determine if we're inside a scrolling container.
//isInScrollingContainer 判断当前控件是否处于一个可以滚动的容器
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 {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
checkForLongClick(
ViewConfiguration.getLongPressTimeout(),
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
}
break;
case MotionEvent.ACTION_CANCEL:
if (clickable) {
setPressed(false);
}
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
break;
case MotionEvent.ACTION_MOVE:
if (clickable) {
//设置RippleDrawable的控件使用
drawableHotspotChanged(x, y);
}
//getClassification得到手势事件的类别,返回有三种类型
//CLASSIFICATION_NONE,没有额外的信息事件
//CLASSIFICATION_AMBIGUOUS_GESTURE 分类常量:模糊手势。用户对当前事件流的意图尚未确定。手势动作(如滚动)应被禁止,直到分类解析为另一个值或事件流结束。
//CLASSIFICATION_DEEP_PRESS 用户有意使劲压在屏幕上,这种类型的事件可以应用于加速长按事件的发生
final int motionClassification = event.getClassification();
final boolean ambiguousGesture =
motionClassification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE;
int touchSlop = mTouchSlop;//最小滑动距离
//hasPendingLongPressCallback 检查消息队列中是否有待执行的长按事件消息
if (ambiguousGesture && hasPendingLongPressCallback()) {
//pointInView 检查触摸点是否已经超出了控件的范围
if (!pointInView(x, y, touchSlop)) {
//取消掉长按事件
removeLongPressCallback();
long delay = (long) (ViewConfiguration.getLongPressTimeout()
* mAmbiguousGestureMultiplier);
// Subtract the time already spent
delay -= event.getEventTime() - event.getDownTime();
checkForLongClick(
delay,
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
}
touchSlop *= mAmbiguousGestureMultiplier;
}
// Be lenient about moving outside of buttons
if (!pointInView(x, y, touchSlop)) {
// Outside button
// Remove any future long press/tap checks
//移除延时单击动作
removeTapCallback();
//移除长按动作
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 immediately
removeLongPressCallback();
checkForLongClick(
0 /* send immediately */,
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS);
}
break;
}
return true;
}
return false;
}
/**
* Entry point for {@link #performClick()} - other methods on View should call it instead of
* {@code performClick()} directly to make sure the autofill manager is notified when
* necessary (as subclasses could extend {@code performClick()} without calling the parent's
* method).
*/
private boolean performClickInternal() {
//是为了通知自动填充服务该控件执行了点击事件,然后接着执行performClick()方法。
notifyAutofillManagerOnClick();
return performClick();
}
//主要为了执行注册的点击事件
public boolean performClick() {
notifyAutofillManagerOnClick();
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
//先播放点击音效
playSoundEffect(SoundEffectConstants.CLICK);
//执行setOnClickListener时注册的回调
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
notifyEnterOrExitForAutoFillIfNeeded(true);
return result;
}
从OnToucEvent代码中可以看出:
1.检查是否设置了TouchDelegate,设置了就调用mTouchDelegate.onTouchEvent,返回true就直接返回true
2.长按事件是在ACTION_DOWN时处理的
3.短按事件是在ACTION_UP中处理,调用performClickInternal
3.OnClickListener.onClick 和 playSoundEffect点击音效都是在performClickInternal中执行
常见问题
1.activity viewgroup和view都不消费action_down,那么action_up事件是怎么传递的?
当ViewGroup和View都不处理ACTION_DOWN时,ACTION_MOVE和ACTION_UP事件就不会传递到ViewGroup和View的dispathTouchEvent。
2.为什么子 View 不消费 ACTION_DOWN,之后的所有事件都不会向下传递了?
主要是因为mFirstTouchTarget,mFirstTouchTarget是一个链表,存储了TouchTarget对象,TouchTarget的作用场景在事件派发流程中,用于记录派发目标,即消费了事件的子View。当View或ViewGroup没有处理DOWN事件时,DecorView中的mFirstTouchTarget == null
dispatchTouchEvent(MotionEvent ev){
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;
}
}
可以看到intercepted = true,默认拦截。
3. 同时对父 View 和子 View 设置点击方法,优先响应哪个?
优先响应View,ViewGroup在dispatchTocuEvent中,如果先响应父 view,那么子 view 将永远无法响应,父 view 要优先响应事件,必须先调用 onInterceptTouchEvent 对事件进行拦截,那么事件不会再往下传递,直接交给父 view 的 onTouchEvent 处理。
4.View中OnTouchEvent,OnClickListener和OnTouchListeners,OnLongClickListener三者的优先级是什么样的?
OnTouchListeners 》OnTouchEvent 》OnLongClickListener 》OnClickListener
OnTouchListeners 和 OnTouchEvent都是在dispatchTouchEvent中执行的,OnTouchListeners比OnTouchEvent执行顺序早,
如果OnTouchListeners中onTouch方法返回true,后面的OnTouchEvent不会执行,会导致OnTouchListeners,OnLongClickListener也不会被执行。OnLongClickListener在OnTouchEvent中的DOWN事件就会被执行,OnClickListener会在UP时被调用,而且OnLongClickListener返回true,OnTouchEvent就不会被执行。
参考
https://blog.csdn.net/q1165328963/article/details/120773934