1.事件分发的总结
- 事件分发顺序:Activity(Window) -> ViewGroup -> View
- 事件分发过程由dispatchTouchEvent()、onInterceptTouchEvent()和onTouchEvent()三个方法协助完成。
三个方法之间的关系,伪代码如下:
//点击事件产生后,会直接调用dispatchTouchEvent()方法
public boolean dispatchTouchEvent(MotionEvent event){
//代表是否消耗事件
boolean consume = false;
if (onInterceptTouchEvent(event)){
//如果onInterceptTouchEvent()返回为true,则代表当前View拦截的点击事件
//事件会被当前View进行处理
//即调用onTouchEvent()方法去处理点击事件
consume = onTouchEvent(event);
}else{
//不拦截,交由子元素处理,重复上述过程
//直到点击事件被处理为止。
consume = child.dispatchTouchEvent(event);
}
return consume;
}
2.事件分发的源码分析
事件分发顺序:Activity(Window) -> ViewGroup -> View
1. Activity中的事件分发
当一个点击事件发生时,事件最先传递到Activity中的dispatchTouchEvent()进行事件分发
具体是由Activity的window来完成。
Activity中的dispatchTouchEvent源码:
public boolean dispatchTouchEvent(MotionEvent ev) {
//一般事件最开始都是down,所以这里返回true
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
//注意1
onUserInteraction();
}
//注意2
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
- 注意1:onUserInteraction(),方法为空方法,当该Activity处于栈顶的时候,触屏点击home,back,menu键等都会触发此方法。所以onUserInteraction()方法主要用于屏保
- 注意2:Window类是抽象类,且PhoneWindow是其唯一实现类。
superDispatchTouchEvent()是也是抽象方法。
PhoneWindow类中的superDispatchTouchEvent()方法:
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
//mDecor是DecorView的实例
//DecorView是视图的顶层view, 继承自FrameLayout, 是所有界面的父类
}
mDecor.superDispatchTouchEvent(event)方法:
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
//DecorView继承自FrameLayout
//那么它的父类就是ViewGroup
//而super.dispatchTouchEvent(event)方法, 其实就应该是
//ViewGroup的dispatchTouchEvent()
}
综上:执行getWindow().superDispatchTouchEvent(ev)实际上是执行了ViewGroup.superDispatchTouchEvent(ev)。
activity中的superDispatchTouchEvent实际上就是在执行ViewGroup.superDispatchTouchEvent()方法。这样,事件就从Activity传递到了ViewGroup。
总结:
当一个点击事件发生时,调用顺序如下,
- 事件最先传递到Activity的dispatchTouchEvent进行分发。
- 调用Window的实现类PhoneWindow的superDisPatchTouchEvent(),
- 调用DecorView的superDispatchTouchEvent()。即父类ViewGroup的dispatchTouchEvent()方法。
2. ViewGroup中的事件分发
ViewGroup的dispatchTouchEvent()方法:
...
// Check for interception.主要是拦截器的处理
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//是否拦截
//可以在子View通过调用
//requestDisallowInterceptTouchEvent方法对这个值进行修改, 不让该View拦截事件
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 {
// 当没有触摸targets, 且不是down事件时, 开始持续拦截触摸。
intercepted = true;
}
...
该代码主要是判断是否拦截。
a.如果当前事件的motionEvent.action_down,进入判断,
b.如果mFirstTouchTarget != null,即已经发生过motionEvent.action_down,并且该事件已经有ViewGroup的子view来处理了,那么也进行判断。调用viewgroup.onInterceptTouchEvent(),判断是否拦截。如果不是以上两种情况,即已经是move或者up事件,并且之前的事件没有对象处理,则设置为true,开始拦截接下来所有的事件。这也就解释了如果view的onTuchEvent()方法返回false,那么接下来的的一系列事件都不会交由它处理。如果ViewGroup的onInterceptTouchEvent(),第一次执行为true,则mFirstTouchTarget = null,则也会使得接下来不会调用onInterceptTouchEvent(),直接将拦截设置为true。
当viewgroup不拦截事件的时候,事件会向下分发交由它的子View或者ViewGroup进行处理。
/**从底层的父视图开始遍历,寻找newTouchTarget,即上面的mFirstTouchTarget ,如果已经存在寻找的newTouchTarget,说明正在接收触摸事件,则跳出循环。
*/
...
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
//如果当前视图无法获取焦点,则跳过本次循环
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
//如果view不可见或者触摸的焦点不在view的范围内,跳过本次循环
if (!child.canReceivePointerEvents()
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget = getTouchTarget(child);
//视图已经接收触摸事件,退出整个循环
if (newTouchTarget != null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
//重置取消或抬起标志位
//如果触摸位置在child的区域内,则把事件分发给view或者ViewGroup
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// 获取TouchDown的时间点
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
//获取touchDown的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();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
- Android事件分发是先传递到ViewGroup,再由ViewGroup传递到view
- 在ViewGroup中通过onInterceptTouchEvent()
1.onInterceptTouchEvent方法返回true代表拦截事件,即不允许事件继续向子View传递;
2.返回false代表不拦截事件,即允许事件继续向子View传递;(默认返回false)
3.子View中如果将传递的事件消费掉,ViewGroup中将无法接收任何事件。
3. View中的事件分发
View.dispatchTouchEvent源码分析:
//过滤触摸事件以应用安全策略。
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
- 条件 li != null ,直接实例化,不为空
- 条件 li.mOnTouchListener != null
//mOnTouchListener是在View类下setOnTouchListener方法里赋值的
public void setOnTouchListener(OnTouchListener l) {
// getListenerInfo() 返回的是静态类ListenerInfo
//即只要我们给控件注册了Touch事件, mOnTouchListener就一定
//被赋值( 不为空)
getListenerInfo().mOnTouchListener = l;
}
- 条件 (mViewFlags & ENABLED_MASK) == ENABLED
- 该条件是判断当前点击的控件是否enable
- 由于很多view默认是enable的,因此该条件恒定为true。
- 条件 li.mOnTouchListener.onTouch(this, event)
- 回调控件注册touch事件时的onTouch方法
view.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { return false; } });
- 如果在onTouch方法返回true,就会让条件成立,整个方法返回为true
- 如果在onTouch方法返回false,就会继续后面的判断,执行onTouchEvent(event)方法。
onTouchEvent(event):
public boolean onTouchEvent(MotionEvent event) {
...
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
if ((viewFlags & TOOLTIP) == TOOLTIP) {
handleTooltipUp();
}
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
if (!focusTaken) {
// 注意: 经历了种种判断进入performClickInternal()方法
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;
注意:当手指抬起的时候触发点击事件,经过一系列的判断,调用 performClickInternal(),该方法里面又调用了performClick().
public boolean performClick() {
// We still need to call this method to handle the cases where performClick() was called
// externally, instead of through performClickInternal()
notifyAutofillManagerOnClick();
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
notifyEnterOrExitForAutoFillIfNeeded(true);
return result;
}
-
只要li.mOnClickListener 不为空,就会调用onclick方法。
public void setOnClickListener(@Nullable OnClickListener l) { if (!isClickable()) { setClickable(true); } getListenerInfo().mOnClickListener = l; }
mOnClickListener 是在View类下setOnClickListener方法里赋值的,而我们设置按钮点击的事件的时候,会调用该方法,自动赋值,最后回调onClick()方法。
结论:
onTouch() 的执行高于onClick()。
-
每当控件被点击的时候:
- 如果在回调onTouch()里面返回false,就会让dispatchTouchEvent()返回false,那么就会执行onTouchEvent()方法;如果回调了setOnclickListener()来给控件注册点击事件的话,最后会在通过performClickInternal(),调用performCik()方法里面调用onClick()方法。
onTouch()返回false(该事件没有被onTouch()消费掉) = 执行onTouchEvent() = 执行Onclick()。
- 如果在回调onTouch()里返回为true,就会让dispatchTouchEvent方法返回true,那么将不会执行onTouchEvent(),即onClick()也不会执行。
onTouch()返回true(该事件被onTouch()消费掉) =dispatchTouchEvent()返回true(不会再继续向下传递) = 不会执行onTouchEvent() = 不会执行onClick().