您的位置:首页 > 移动开发 > Android开发

Android中ViewGroup、View事件分发机制源码分析总结(雷惊风)

2017-04-01 11:24 791 查看
1.概述

        很长时间没有回想Android中的事件分发机制了,打开目前的源码发现与两三年前的实现代码已经不一样了,5.0以后发生了变化,更加复杂了,但是万变不离其宗,实现原理还是一样的,在这里将5.0以前的时间分发机制做一下源码剖析及总结。会涉及到几个方法,dispatchTouchEvent()表示事件开始分发方法,在ViewGroup与View中都有,onInterCeptTouchEvent()表示是否拦截当前事件,比如ACTION_DOWN、ACTION_MOVE、ACTION_UP、ACTION_CANCLE,只在ViewGroup中存在,因为一个View的子类已经是最内层View,而ViewGroup的子类还会包含子View,所以它需要可以设置是否拦截当前事件传递到子View中,onToutchEvent()表示消费当前事件,OnToutchListener,OnClickListener,OnLongClickListener。

2.源码剖析

        我会将详细的代码注释加在源码中,首先说一下在Activity与Window中的分发过程。当我们点击屏幕上的一个View时,事件的分发就开始了,首先会调用当前Activity的dispatchTouchEvent(MotionEvent ev)方法,我们打开Activity类看一下它的实现:

public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
只挑主要的说,可以看到调用了getWindow().superDispatchTouchEvent()方法,如果这个方法返回true,则表示事件被消费,直接返回true,如果返回false,会执行自己的onTouchEvent()表明如果View树中都没有消费事件,当前Activity消费事件。我们知道在Phone上边PhoneWindow是Window的唯一子类,那我们就去PhoneWindow中找找相关方法:

//phoneWindow
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
在PhoneWindow中可以看到调用了mDocor的super***方法,大家应该都知道mDecor为window中的最顶级View类,他是PhoneWindow的内部类,继承了FrameLayout。我们进入mDecor的内部看看它的实现:

//DecorView
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
在DecorView中又调用了父类方法,我们最终在GroupView中找到了相关实现,这样我们就完成了一个点击事件从Activity到ViewGroup的过程。无论是ACTION_DOWN、ACTION_MOVE、ACTION_UP、ACTION_CANCLE任何一个都是如此的一直传递流程。

总结:点击--->Activity.dispatchTouchEvent(event)--->PhoneWindow.superDispatchTouchEvent(event)--->DecorView.superDispatchTouchEvent(event)--->ViewGroup.dispatchTouchevent(event);

说完了一个事件从Activity到ViewGroup的传递流程,下边说一下一个事件在ViewGroup中是如何分发的,看一下源码:

//ViewGroup中;
public boolean dispatchTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
final float xf = ev.getX();
final float yf = ev.getY();
final float scrolledXFloat = xf + mScrollX;
final float scrolledYFloat = yf + mScrollY;
final Rect frame = mTempRect;
boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//ViewGroup中按下事件处理;
if (action == MotionEvent.ACTION_DOWN) {
//如果mMotionTarget保存了View,因为是从新开始的DOWN,所以清空;
if (mMotionTarget != null) {
mMotionTarget = null;
}
//是否禁用拦截事件功能,如果禁用了拦截功能(不拦截)或者onInterceptTouchEvent()返回false,说明不拦截事件。
if (disallowIntercept || !onInterceptTouchEvent(ev)) {
ev.setAction(MotionEvent.ACTION_DOWN);
final int scrolledXInt = (int) scrolledXFloat;
final int scrolledYInt = (int) scrolledYFloat;
final View[] children = mChildren;
final int count = mChildrenCount;
//循环每一个子View;
for (int i = count - 1; i >= 0; i--) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null) {
child.getHitRect(frame);
//判断当前子View是否包含点击区域;
if (frame.contains(scrolledXInt, scrolledYInt)) {
final float xc = scrolledXFloat - child.mLeft;
final float yc = scrolledYFloat - child.mTop;
ev.setLocation(xc, yc);
child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
//调用子View的dispatchTouchEvent() ,向子事件分发事件;
//1.如果返回true,说明子类或者子类的某个子类中消费了事件,将这个child记录到mMotionTarget中,每一个ViewGroup都记录了
//消费了这个事件的子View,像链式,最后当前ViewGroup返回true,返回给调用这个ViewGroup的外层ViewGroup,一直到Activity中
//的dispatchTouchEvent()中,最后Activity中也返回true;
//2.如果返回false,说明子类或者子类的子类中没有消费这个事件,所以mMotionTarget不会被赋值,会继续向下执行代码;
if (child.dispatchTouchEvent(ev))  {
mMotionTarget = child;
return true;
}
}
}
}
}
}
boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
(action == MotionEvent.ACTION_CANCEL);
if (isUpOrCancel) {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
final View target = mMotionTarget;
//ACTION_DOWN事件时,被拦截,或者子类没有消费ACTION_DOWN,调用自己的onTouchEvent()执行onTouch、onClick()方法等;
//ACTION_UP时,子类没有消费ACTION_DOWN;
if (target == null) {
ev.setLocation(xf, yf);
if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
ev.setAction(MotionEvent.ACTION_CANCEL);
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
}
//调用super方法即调用到View中的dispatchTouchEvent()方法,还是调用的当前View,
//在View中会调用自己的onTouchEvent()执行onTouch、onClick()方法等处理;
//这里两种情况:1.自身没有处理返回false;2.自身处理了返回true,本身处理了,不会保存到mMotionTarget中,而它的外层ViewGroup会保存他;
//true/false都会一直返回到Activity中。
return super.dispatchTouchEvent(ev);
}
//ACTION_UP或者MOVE时,子类中消费了ACTION_DOWN事件,没有禁用拦截功能(可以拦截),并且拦截事件;
// 无论 target 是否为 null ,ACTION_DOWN事件的处理都不能走到这里,在之下都是ACTION_MOVE和ACTION_UP的逻辑
// 如果执行到这里,说明有响应ACTION_DOWN事件的view对象,这就看我们是否被允许拦截和要不要拦截了
// 如果允许拦截并且拦截了ACTION_MOVE和ACTION_UP事件,则将ACTION_CANCEL事件分发给target
// 然后直接返回true,表示已经响应了该次事件
if (!disallowIntercept && onInterceptTouchEvent(ev)) {
final float xc = scrolledXFloat - (float) target.mLeft;
final float yc = scrolledYFloat - (float) target.mTop;
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
ev.setAction(MotionEvent.ACTION_CANCEL);
ev.setLocation(xc, yc);
//将子类事件重置为ACTION_CANCEL;
if (!target.dispatchTouchEvent(ev)) {
}
mMotionTarget = null;
return true;
}
if (isUpOrCancel) {
mMotionTarget = null;
}
final float xc = scrolledXFloat - (float) target.mLeft;
final float yc = scrolledYFloat - (float) target.mTop;
ev.setLocation(xc, yc);
if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
ev.setAction(MotionEvent.ACTION_CANCEL);
target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
mMotionTarget = null;
}
// 如果没有拦截ACTION_MOVE和ACTION_UP事件,则直接派发给target
return target.dispatchTouchEvent(ev);
}
注释加了不少,看几遍就能明白。

事件分发机制在ViewGroup中的派发流程:

在ViewGroup的dispatchTouchEvent()中,

1.ACTION_DOWN:首先判断拦截相关设置,不拦截:获取每一个子类判断点击区域是否在当前子类上,

在的话调用其dispatchTouchEvent()方法,方法返回true说明当前View处理当前事件序列,记录到mMotionTarget中返回true,

表明找到了处理当前事件的View,停止事件分发。方法返回false,说明当前子View及其子孙View不处理当前事件,如果所有子View都不处理,

后续代码会调用当前ViewGroup的super.dispatchTouchEvent(ev),即执行自己的OnTouch(),OnClick()等(自己处理),如果返回了true,

表明自己处理当前事件,如果返回false,自己不处理当前事件。无论返回true还是false最终都是返回到Activity中。

2.ACTION_MOVE:判断拦截相关:不拦截,调用target.dispatchTouchEvent(ev)进行事件分发;拦截:将target的MotionEvent置为Cancel,

mMotionTarget赋值为null,返回true,表明自己消费了事件。这次不会调用自己的super.dispatchTouchEvent(ev)。

再次MOVE分发时由于mMotionTarget为空了,所以会调用自己的super.dispatchTouchEvent(ev),即自身处理。

3.ACTION_CANCEL或ACTION_UP:回复状态默认值,判断是否拦截,拦截返回true;不拦截向下派发事件。

下边看一下在Viewgroup的dispatchTouchEvent方法中继续向下分发事件代码if(child.dispatchTouchEvent(ev))执行后如果child也是一个ViewGroup子类那么跟上边的逻辑是一样的,那么如果child是一个View的子类,比如Button、TextView、ImageView等时,是如何分发事件的,即事件在View类中的传递流程,接着看一下源码:

//View中的dispatchTouchEvent;
public boolean dispatchTouchEvent(MotionEvent event) {
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}
重要源码就这些,在这里可以看到首先判断我们有没有给当前View设置OnTouchListener事件,并且当前View是enabled状态的,如果前两个条件都满足,则会调用我们OnTouchListener中的onTouch()方法,如果上边三个条件都为true,则直接返回true,表明当前View消费当前事件,如果我们在onTouch()方法中返回false,那么就会跳过判断执行自己的onTouchEvent()方法,这里先提前说一下,调用onTouchEvent()方法也有继续判断当前View是否消费当前事件的作用,在其内部会根据我们点击在控件上停留时间判断是否执行OnClick,OnLongClick事件。下边看一下onTouchEvent()源码:

//View中onTouchEvent();
public boolean onTouchEvent(MotionEvent event) {
final int viewFlags = mViewFlags;
//当前View是Disabled状态且是可点击则会消费掉事件
if ((viewFlags & ENABLED_MASK) == DISABLED) {
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
//将事件交给代理者处理,直接return true
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
//View可以点击或者长按,只要进入判断,一定反回true,即消费事件;
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
//100ms内或者以后都会进入代码块;
if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
//如果是在0到500ms之间或者500秒之后执行了LongClick事件但返回的false(不拦截事件);
if (!mHasPerformedLongPress) {
// This is a tap, so remove the longpress check
//取消长按事件任务监听;
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
//在pressed状态,只执行Click事件;
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)) {
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
mPrivateFlags |= PRESSED;
refreshDrawableState();
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
break;
case MotionEvent.ACTION_DOWN:
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
//设置被点击状态;
mPrivateFlags |= PREPRESSED;
//没有触发长按事件;
mHasPerformedLongPress = false;
//发送100毫秒延时消息,执行CheckForTap类中方法。
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
break;
case MotionEvent.ACTION_CANCEL:
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
removeTapCallback();
break;
case MotionEvent.ACTION_MOVE:
final int x = (int) event.getX();
final int y = (int) event.getY();
// Be lenient about moving outside of buttons
int slop = mTouchSlop;
if ((x < 0 - slop) || (x >= getWidth() + slop) ||
(y < 0 - slop) || (y >= getHeight() + slop)) {
// Outside button
//手指滑出了View范围,取消100ms计时任务;
removeTapCallback();
//判断是否包含PRESSED标识,如果包含,移除长按的检查任务;
if ((mPrivateFlags & PRESSED) != 0) {
// Remove any future long press/tap checks
removeLongPressCallback();
// Need to switch from pressed to not pressed
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
}
}
break;
}
return true;
}
return false;
}
有些相对来说重要的类或者代码我会在下边贴出来,帮助大家更好地理解,在这里代码就不详细的讲了,在最后我还会总结,相关代码:

//点击状态变为PRESSED类;
private final class CheckForTap implements Runnable {
public void run() {
mPrivateFlags &= ~PREPRESSED;
//设置PRESSED标识
mPrivateFlags |= PRESSED;
//刷新背景
refreshDrawableState();
//如果View支持长按事件,调用postCheckForLongClick()方法;
if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
postCheckForLongClick(ViewConfiguration.getTapTimeout());
}
}
}
//ViewConfiguration.getLongPressTimeout() 为500ms;再次发送500-100=400ms延时消息
private void postCheckForLongClick(int delayOffset) {
mHasPerformedLongPress = false;

if (mPendingCheckForLongPress == null) {
mPendingCheckForLongPress = new CheckForLongPress();
}
mPendingCheckForLongPress.rememberWindowAttachCount();
postDelayed(mPendingCheckForLongPress,
ViewConfiguration.getLongPressTimeout() - delayOffset);
}
//长按事件发生处理器;
class CheckForLongPress implements Runnable {

private int mOriginalWindowAttachCount;

public void run() {
if (isPressed() && (mParent != null)
&& mOriginalWindowAttachCount == mWindowAttachCount) {
//执行LongClick监听;
if (performLongClick()) {
mHasPerformedLongPress = true;
}
}
}
}
总结:也就是说如果用户设置了LongClickListener,从用户触发ACTION_DOWN开始500ms才能触发事件,如果达不到500ms,视为点击事件;

如果longClickListener中返回true,mHasPerformedLongPress就是true;
//还在100ms内,取消监听;
private void removeTapCallback() {
if (mPendingCheckForTap != null) {
mPrivateFlags &= ~PREPRESSED;
removeCallbacks(mPendingCheckForTap);
}
}

//取消长按事件任务监听;
private void removeLongPressCallback() {
if (mPendingCheckForLongPress != null) {
removeCallbacks(mPendingCheckForLongPress);
}
}
总结:主要就是判断是否移出当前View,如果移出,根据时间移出相关计时任务;
private final class UnsetPressedState implements Runnable {
public void run() {
setPressed(false);
}
}
public void setPressed(boolean pressed) {
if (pressed) {
mPrivateFlags |= PRESSED;
} else {
mPrivateFlags &= ~PRESSED;
}
refreshDrawableState();
dispatchSetPressed(pressed);
}
清除PRESSED,刷新背景,向下传递pressed;


事件分发在View中的流程总结:

调用View的dispatchTouchEvent(),判断是否设置了OnTouchListener并且View为Enable的,如果都满足,调用OnTouchListener.onTouch(this, event),如果返回true,

直接返回true,表明当前View处理当前事件,不会调用自己的onTouchEvent(),如果onTouth()方法返回false,则调用onTouchEvent()。

在onTouchEvent()中如果当前View为可点击或者可长按则一定返回true,否则返回false。

1.ACTION_DOWN:初始化状态为PREPRESSED,设置mHasPerformedLongPress为flase,发送100ms任务,如果100ms到后,还没有UP或者移出当前View,

则将标志置为PRESSED,如果支持长按事件,发送另一个400ms计时任务,如果到时间后依然没有UP,LongClickListener不为空,调用执行,返回true,将

mHasPerformedLongPress设置为true。

2.ACTION_MOVE:检测用户有没有移出View,并移除相关时间任务。

3.ACTION_UP:100ms内仍然是PREPRESSED状态,不会触发Click;在100到500ms之间,取消仍然运行的计时任务,执行 performClick去执行Onclick;

如果在500ms以后,没有设置onLongClickListener或者返回false,仍然会执行performClick;返回true,onClick不执行;LongClick在Click前执行。

       在事件分发过程中OnTouchListener优先OnClickListener与OnLongClickListener执行,如果返回false,OnClickListener与OnLongClickListener才有可能执行到,返回true不执行。在500ms内抬起手指不会执行OnLongClickListener,会执行OnClickListener,在500ms以后如果OnLongClickListener返回false,OnClickListener会执行,返回true不执行。这就是他们几个的执行顺序及规则,当然这是建立在我们注册了这三个监听。今天的总结就到这里,希望对初学者有帮助,如果哪里不对,欢迎大神拍砖,谢谢!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: