APP事件分发流程

Post on Feb 12, 2023 by Wei Lin

事件分发概述

常用View

(1) 常用组件

用户界面的基本组件为View类及其子类, Android里面预定义很多基本的界面组件,比如Button、CheckBox、TextView等,它们一般称作组件(components)或是部件(widgets)。


(2) ViewGroup

一个View也可以由多个其他的View组件构成,即android.view.ViewGroup类及其子类,它本身也是继承自View类,它可以充当一个父视图(parent view),容纳其他的子视图(child views)。


(3) Layout

另外一种常用的ViewGroup就是布局,是一种容器视图,比如RelativeLayout,TableLayout等。

事件分发过程1.png

监听器

在监听器中写执行代码,当触发某类事件时就会执行,相当于回调。

常用监听器 响应事件
onClickListener 用于响应点击事件
onLongClickListener 用于响应长按事件
onTouchListener 用于响应触摸事件,比如多手势
onFocusChangeListener 用语响应焦点转移事件
onKeyListener 用于响应键盘按键事件

事件处理基本规则

同一个事件序列是指从手指接触屏幕起,到离开屏幕结束,以down开始,包含数量不定的move事件,以up结束;

  • 正常情况下,一个事件序列只能被一个View拦截且消耗;
  • 某个View决定拦截,那么这一个事件序列都只能由它来处理;
  • 某个View一旦开始处理事件,如果不消耗ACTION_DOWN事件(onTouchEvent方法返回false),那么同一事件序列中的其他事件都不会再交给他来处理;
  • 如果View不消除ACTION_DOWN以外的事件,那么这个点击事件会消失;
  • ViewGroup默认不拦截任何事件,onInterceptTouchEvent方法默认返回false;
  • View没有onInterceptTouchEvent方法,一旦有点击事件传递给它,onTouchEvent方法会被调用;
  • View的onTouchEvent方法默认返回true,除非是不可点击的;
  • View的enable属性不影响onTouchEvent方法的默认返回值;
  • onClick会发生的前提是View是可点击的,并且收到了down和up事件;
  • 事件传递过程是由外向内的,即总是先传递给父元素,然后由父元素分发给子View。

APP事件分发过程

View的事件分发概述

(1) 窗口层级

一个界面的窗口层级如下, 事件传递从 Activity 逐层向下传递的 View 组件上:

事件分发过程2.png


(2) 事件分发方法

MotionEvent即点击事件,当一个MotionEvent产生后,系统需要把这个事件传递给一个具体的View,这个过程就是分发过程。

点击事件的分发过程由dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent三个方法共同完成。

方法 说明
public boolean dispatchTouchEvent(MotionEvent event) 用来进行事件分发,如果事件能传递给当前View,那么此方法被调用
public boolean onInterceptTouchEvent(MotionEvent event) 是ViewGroup提供的方法,在dispatchTouchEvent()方法内部调用,用来判断是否拦截某个事件。 默认返回false,返回true表示拦截,于是不再将事件分发给View。 View中不提供onInterceptTouchEvent()。view中默认返回true,表示消费了这个事件
public boolean onTouchEvent(MotionEvent event) 在dispatchTouchEvent方法内部调用,用来处理点击事件。返回结果为true表示消耗当前事件

注:ViewGroup是容纳这些View组件的容器,如LinearLayout、RelativeLayout等 (extends ViewGroup),其本身也是从View派生出来的,处理事件机制与View一样。


(3) 分发大致流程

当一个点击事件产生以后,它的传递顺序为:Activity —> Window —> ViewGroup —> View。

即一个事件总是先传递给Activity,Activity再传递给Window,最后Window再传递给顶级View(即ViewGroup),顶级View接收到事件后会选择是否分发事件给View。


如果一个View的onTouchEvent方法返回false,那么它的父容器的onTouchEvent方法会被调用,即当所有元素都不处理这个事件,那么这个事件最终传递给Activity处理。

事件分发过程3.png


以ViewGroup为例,其分发事件的伪代码如下:

//当一个事件产生以后,会传递给根ViewGroup,并调用根ViewGroup的dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent ev) {
	boolean consume = false;
	//如果这个ViewGroup要拦截当前事件,那么调用onInterceptTouchEvent后返回true
	//并调用onTouchEvent方法
	if (onInterceptTouchEvent(ev)) {
		consume = onTouchEvent(ev);
	} else {
	//如果这个ViewGroup不拦截当前事件,那么调用onInterceptTouchEvent后返回false
	//并将当前事件传递给它的子元素,调用子元素的dispatchTouchEvent方法,如此反复
		consume = child.dispatchTouchEvent(ev);
	}
	//返回false表示该事件未被消费,
	return consume;
}

Activity

Activity.java

//首先事件交给Activity.dispatchTouchEvent()进行分发
public boolean dispatchTouchEvent(MotionEvent ev) {
	//onUserInteraction()是空方法,用户可重载。有事件分发到当前Activity就会被调用
	if (ev.getAction() == MotionEvent.ACTION_DOWN) {
		onUserInteraction();
	}
	//事件开始交给Activity所拥有的Window进行分发,向下传递
	//superDispatchTouchEvent()返回true表示事件循环结束
	//返回false意味着事件没人处理
	if (getWindow().superDispatchTouchEvent(ev)) {
		return true;
	}
	//如果所有View的onTouchEvent都返回了false,那么Activity的onTouchEvent就会被调用
	return onTouchEvent(ev);
}

PhoneWindow

PhoneWindow.java

Window是个抽象类,而Window的superDispatchTouchEvent方法也是个抽象方法,其唯一的实现类是PhoneWindow。

@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
	//PhoneWindow将事件传递给了DecorView
	return mDecor.superDispatchTouchEvent(event);
}

//DecorView将事件传递给其父类FrameLayout,FrameLayout没有重写dispatchTouchEvent,
//所以直接调用到其父类ViewGroup.dispatchTouchEvent(event)
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
	public boolean superDispatchTouchEvent(MotionEvent event) {
		return super.dispatchTouchEvent(event);
	}
}

ViewGroup

ViewGroup作为一个parent是可以截获传向它的child的touch事件的。

如果一个ViewGroup的onInterceptTouchEvent()方法返回true,说明Touch事件被截获,子View不再接收到Touch事件,而是转向本ViewGroup的 onTouchEvent()方法处理。

从Down开始,之后的Move,Up都会直接在onTouchEvent()方法中处理。先前还在处理touch event的child view将会接收到一个 ACTION_CANCEL。如果onInterceptTouchEvent()返回false,则事件会交给child view处理。


其dispatchTouchEvent()中代码较多,简化为伪代码进行分析。

//当一个事件产生以后,会传递给根ViewGroup,并调用根ViewGroup的dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent ev) {
	if (mInputEventConsistencyVerifier != null) {
		mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
	}
	//标识一个消息是否被处理(消费)
	boolean handled = false;

	// Check for interception.
	final boolean intercepted;


	//标识是否拦截事件
	final boolean intercepted;


	//当事件类型为ACTION_DOWN或者mFirstTouchTarget != null,不拦截事件
	//当ViewGroup的子元素成功处理事件时,mFirstTouchTarget会被赋值并指向子元素
	//当ViewGroup不拦截事件并将事件交由子元素处理时mFirstTouchTarget != null

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


public boolean dispatchTouchEvent(MotionEvent ev) {
	//标识一个消息是否被处理(消费)
	boolean handled = false;

	//如果这个ViewGroup要拦截当前事件,那么调用onInterceptTouchEvent后返回true
	//并调用onTouchEvent方法
	if (onInterceptTouchEvent(ev)) {
		handled = onTouchEvent(ev);
	} else {
		//如果这个ViewGroup不拦截当前事件,那么调用onInterceptTouchEvent后返回false
		//并将当前事件传递给它的子元素,调用子元素的dispatchTouchEvent方法,如此反复
		handled = child.dispatchTouchEvent(ev);
	}
	//返回false表示该事件未被消费,
	return handled;
}

View

如果ACTION_DOWN事件发生在某个View的范围之内,则后续的ACTION_MOVE,ACTION_UP和ACTION_CANCEL等事件都将被发往该View,即使事件已经出界了。

若Down事件一直未被消费,则该View不会执行move和up事件。


View.java

public boolean dispatchTouchEvent(MotionEvent event) {

	ListenerInfo li = mListenerInfo;
	if (li != null
	        && li.mOnTouchListener != null  //用户设置了OnTouchListener则为true
	        && (mViewFlags & ENABLED_MASK) == ENABLED  //View是否可用
	        //调用自定义的OnTouchListener.onTouch(),返回true,表示消费事件
	        && li.mOnTouchListener.onTouch(this, event)) {
		result = true;
	}
	//返回true标识在执行完onTouch里面的代码之后,onTouch事件并没有结束
	
	//result=true,表示消费事件
	//false则执行onTouchEvent()
	if (!result && onTouchEvent(event)) {
		result = true;
	}
	//...
	return result;
}

onTouch()是View提供给用户,让用户自己处理触摸事件的接口。而onTouchEvent()是Android系统自己实现的接口。(02),onTouch()的优先级比onTouchEvent()的优先级更高。dispatchTouchEvent()中分发事件的时候,会先将事件分配给onTouch()进行处理,然后才分配给onTouchEvent()进行处理。 如果onTouch()对触摸事件进行了处理,并且返回true;那么,该触摸事件就不会分配在分配给onTouchEvent()进行处理了。只有当onTouch()没有处理,或者处理了但返回false时,才会分配给onTouchEvent()进行处理。


然后再看一下View.onTouchEvent()

只要View的CLICKABLE和LONG_CLICKABLE有一个为true,那么它就会消耗这个事件。