1.Android中事件分发
事件分发顺序:Activity(Window) -> ViewGroup -> View
1.1Activity的dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
//getWindow()获取的就是PhoneWindow对象
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
//该方法为空方法
//当此activity在栈顶时,触屏点击按home,back,menu键等都会触发此方法
//所以onUserInteraction()主要用于屏保
public void onUserInteraction() {
}
1.2 PhoneWindow的superDispatchTouchEvent(ev)
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
//mDecor是DecorView的实例
//DecorView是视图的顶层view,继承自FrameLayout,是所有界面的父类
}
1.3 DecorView的superDispatchTouchEvent
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
//DecorView继承自FrameLayout
//那么它的父类就是ViewGroup
而super.dispatchTouchEvent(event)方法,其实就应该是ViewGroup的dispatchTouchEvent()
}
1.4 汇总;
当一个点击事件发生时,调用顺序如下:
- 事件最先传到Activity的dispatchTouchEvent()进行事件分发
- 调用Window类实现类PhoneWindow的superDispatchTouchEvent()
- 调用DecorView的superDispatchTouchEvent()
- 最终调用DecorView父类的dispatchTouchEvent(),即ViewGroup的
dispatchTouchEvent()
1.5 回头看Activity的dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
小结:
当一个点击事件发生时,事件最先传到Activity的dispatchTouchEvent()进行事件分发,最终是调用了ViewGroup的dispatchTouchEvent()方法 ,判断ViewGroup的dispatchTouchEvent()返回值是否为true,返回true就不执行Activity的onTouchEvent()方法;返回false,就执行。
2).View事件分发源码分析
2.1 View的dispatchTouchEvent()方法
//mOnTouchListener是在View类下setOnTouchListener方法里赋值的
//mViewFlags & ENABLED_MASK) == ENABLED即当前点击的控件是否enable
//mOnTouchListener的onTouch(this, event)方法的返回值是否为true
public boolean dispatchTouchEvent(MotionEvent event) {
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
public void setOnTouchListener(OnTouchListener l) {
//即只要我们给控件注册了Touch事件,mOnTouchListener就一定被赋值(不为空)
mOnTouchListener = l;
}
}
第一个条件:mOnTouchListener != null;
第二个条件:(mViewFlags & ENABLED_MASK) == ENABLED;
第三个条件:mOnTouchListener.onTouch(this, event);
小结:
- 只有这三个条件都为true,dispatchTouchEvent返回true,不执行onTouchEvent(event),否则执行否则执行onTouchEvent(event)方法。
- onTouch()方法先于onTouchEvent(event)执行
2.2 onTouchEvent(event)
public boolean onTouchEvent(MotionEvent event) {
final int viewFlags = mViewFlags;
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));
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
//如果该控件enable且可以点击的就会进入到下两行的switch判断中去;
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
//如果当前的事件是抬起手指,则会进入到MotionEvent.ACTION_UP这个case当中。
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
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();
}
if (!mHasPerformedLongPress) {
// 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)) {
//请往下看performClick()的源码分析
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;
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
removeTapCallback();
if ((mPrivateFlags & PRESSED) != 0) {
// Remove any future long press/tap checks
removeLongPressCallback();
// Need to switch from pressed to not pressed
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
}
}
break;
}
//如果该控件是可以点击的,就一定会返回true
return true;
}
//如果该控件是不可以点击的,就一定会返回false
return false;
}
由上可知
- 在View为disable情况下,如果view可点击则onTouchEvent()返回true,否则onTouchEvent()返回false。
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));
}
- 在View为enable情况下,如果view可点击则onTouchEvent()返回true,如果view不可点击,onTouchEvent()返回false.
public boolean onTouchEvent(MotionEvent event) {
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
//..........省略
//如果该控件enable且可以点击的,就一定会返回false
return true;
}
//如果该控件enable但不可以点击的,就一定会返回false
return false;
}
结合以上2点可知:
只要view可点击则onTouchEvent()一定返回true,view不可点击,onTouchEvent()一定返回false.
2.3 View的点击事件在哪执行?
一次点击事件必不可少的行为MotionEvent.ACTION_DOWN和MotionEvent.ACTION_UP。那么点击事件一定是在MotionEvent.ACTION_UP中执行,看源码:
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
//.......省略......
if (!mHasPerformedLongPress) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
//performClick翻译一下,执行点击? 呵呵
performClick();
}
}
}
//.......省略......
break;
}
//.......省略......
}
2.3.1 performClick()源码
public boolean performClick() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
//只要mOnClickListener不为null,就会去调用mOnClickListener的onClick方法
if (mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
mOnClickListener.onClick(this);
return true;
}
return false;
}
public void setOnClickListener(OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
mOnClickListener = l;
}
总结:
- 执行顺序:onTouch()–>onTouchEvent()–>onClick()
- view为disable时,onTouch()不会执行,onTouchEvent()执行,onClick()不执行
- 如果view为enable且onTouch()返回true,就会让dispatchTouchEvent方法返回true,那么将不会执行onTouchEvent(),onClick()也不会执行。
- 如果view为enable且onTouch()返回false,那么就会执行onTouchEvent();执行onClick()。
- 只要view可点击则onTouchEvent()一定返回true,view不可点击,onTouchEvent()一定返回false.
3.ViewGroup事件分发源码分析
3.1 ViewGroup的dispatchTouchEvent()
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;
if (action == MotionEvent.ACTION_DOWN) {
if (mMotionTarget != null) {
mMotionTarget = null;
}
//如果我们在onInterceptTouchEvent()中返回false,就会让第二个值为true,从而进入到条件判断的内部
//如果我们在onInterceptTouchEvent()中返回true,就会让第二个值为false,从而跳出了这个条件判断。
//关于onInterceptTouchEvent()请看下面分析(关注点1)
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;
//通过for循环,遍历了当前ViewGroup下的所有子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是不是正在点击的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()方法(具体请看下面的View事件分发机制)
//实现了点击事件从ViewGroup到View的传递
if (child.dispatchTouchEvent(ev)) {
//调用子View的dispatchTouchEvent后是有返回值的
//如果这个控件是可点击的话,那么点击该控件时,dispatchTouchEvent的返回值必定是true
//因此会导致条件判断成立
mMotionTarget = child;
//于是给ViewGroup的dispatchTouchEvent方法直接返回了true,这样就导致后面的代码无法执行,直接跳出
//即把ViewGroup的touch事件拦截掉
return true;
}
}
}
}
}
}
boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
(action == MotionEvent.ACTION_CANCEL);
if (isUpOrCancel) {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
final View target = mMotionTarget;
//没有任何View接收事件的情况,即点击空白处情况
if (target == null) {
ev.setLocation(xf, yf);
if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
ev.setAction(MotionEvent.ACTION_CANCEL);
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
}
//调用ViewGroup的父类View的dispatchTouchEvent()
//因此会执行ViewGroup的onTouch()、onTouchEvent()
//实现了点击事件从ViewGroup到View的传递
return super.dispatchTouchEvent(ev);
}
//之后的代码在一般情况下是走不到的了,我们也就不再继续往下分析。
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);
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;
}
return target.dispatchTouchEvent(ev);
}
3.1.1 disallowIntercept
disallowIntercept:是否禁用事件拦截的功能(默认是false),可以通过调用requestDisallowInterceptTouchEvent方法对这个值进行修改。
3.1.2 onInterceptTouchEvent(ev) 是否拦截事件(默认是false)。
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}
3.2 MotionEvent.ACTION_DOWN时,做了些什么??
if (action == MotionEvent.ACTION_DOWN) {
if (mMotionTarget != null) {
mMotionTarget = null;
}
//onInterceptTouchEvent()返回false或disallowIntercept==true进入if语句
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;
//通过for循环,遍历了当前ViewGroup下的所有子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是不是正在点击的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()方法
if (child.dispatchTouchEvent(ev)) {
//如果这个控件是可点击的话,那么点击该控件时,dispatchTouchEvent的返回值必定是true
//因此会导致条件判断成立
mMotionTarget = child;
//于是给ViewGroup的dispatchTouchEvent方法直接返回了true,这样就导致后面的代码无法执行,直接跳出
//即把ViewGroup的touch事件拦截掉
return true;
}
}
}
}
}
}
首先判断(disallowIntercept || !onInterceptTouchEvent(ev))是否为true,true则进入if语句,通过for循环,遍历了当前ViewGroup下的所有子View,查找点击的View,
- 找到则调用该View的dispatchTouchEvent()方法,返回值为true,则该View消耗了DOWN事件,ViewGroup的dispatchTouchEvent()结束,后续的MOVE等事件都由该View消费。
- 没找到或找到但View的dispatchTouchEvent()返回值为false,则DOWN事件未被任何View消耗。走流程3.3。
3.3 没有任何View消耗事件的情况或onInterceptTouchEvent()为true
//DOWN事件未被任何View消耗则mMotionTarget==null;
final View target = mMotionTarget;
//没有任何View接收事件的情况,即点击空白处情况
if (target == null) {
ev.setLocation(xf, yf);
if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
ev.setAction(MotionEvent.ACTION_CANCEL);
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
}
//调用ViewGroup的父类View的dispatchTouchEvent()
//因此会执行ViewGroup的onTouch()、onTouchEvent()
//实现了点击事件从ViewGroup到View的传递
return super.dispatchTouchEvent(ev);
}
由上可知:
没有任何View消耗DOWN事件或onInterceptTouchEvent()为true情况下,target == null,会调用ViewGroup父类View的dispatchTouchEvent(),会执行ViewGroup的onTouch()、onTouchEvent()。
- super.dispatchTouchEvent(ev)为true,消耗事件。
- super.dispatchTouchEvent(ev)为false,事件向上传递。
3.4 拦截子View的DOWN事件
onInterceptTouchEvent()设置为true,走流程3.3,如果DOWN事件被ViewGroup消费了,该事件列的其他事件(Move、Up)将直接传递给ViewGroup的onTouch()或onTouchEvent()。
3.5 不拦截子View的DOWN事件,拦截子View的其他事件
子View消费了DOWN事件,在ViewGroup的onInterceptTouchEvent方法返回true拦截该MOVE事件,这个MOVE事件将会被系统变成一个CANCEL事件传递给子View的dispatchTouchEvent()方法,后续又来了一个MOVE事件,该MOVE事件会直接传递给ViewGroup的onTouch()或onTouchEvent().
//子View消费了DOWN事件且onInterceptTouchEvent()==true&&disallowIntercept ==false
if (!disallowIntercept && onInterceptTouchEvent(ev)) {
final float xc = scrolledXFloat - (float) target.mLeft;
final float yc = scrolledYFloat - (float) target.mTop;
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
//设置MotionEvent.ACTION_CANCEL事件
ev.setAction(MotionEvent.ACTION_CANCEL);
ev.setLocation(xc, yc);
//调用子view的dispatchTouchEvent()并设置mMotionTarget==null
//下个事件来,走流程3.3
if (!target.dispatchTouchEvent(ev)) {
}
mMotionTarget = null;
//MotionEvent.ACTION_CANCEL事件被消费
return true;
}
4.Touch事件的后续事件(MOVE、UP)层级传递
- 当(Activity、ViewGroup、View)的dispatchTouchEvent在进行事件分发的时候,只有前一个事件返回true,才会收到后一个事件。
- 如果在某个对象(Activity、ViewGroup、View)的dispatchTouchEvent()收到ACTION_DOWN的函数并返回true,那么也能收到ACTION_MOVE和ACTION_UP 。
黑线:ACTION_DOWN事件传递方向
红线:ACTION_MOVE和ACTION_UP事件传递方向
- 如果在某个对象(Activity、ViewGroup、View)的onTouchEvent()消费了DOWN事件,那么ACTION_MOVE和ACTION_UP的事件从上往下传到这个View后就不再往下传递了,而直接传给这个View的onTouchEvent()并结束本次事件传递过程。
参考