从我刚进实验室的时候,学长学姐就说View的事件分发机制是Android里面一个很重要的内容,要我们好好学。
但是随着自己对Android了解的深入,越发觉得这个东西很有必要了解下,正好Android艺术开发探索也看到了View这块,也看了郭霖大神的博客和另一位大神的博客,所以就好好学习了一番,并写了此博客。
1. MotionEvent
在开始讲View事件分发之前,我们先来了解下MotionEvent。
这个就是手指解除到屏幕后所产生的一系列事件,主要为一下三个典型事件:
ACTION_DOWM——手指刚接触屏幕ACTION_MOVE——手指在屏幕上移动ACTION_UP——手指从屏幕上松开ACTION_CANCEL——结束事件(非人为)
正常情况下,一次手指触摸屏幕然后离开可能触发一下两种情况:
点击屏幕然后松开:ACTION_DOWN -> ACTION_UP点击屏幕然后滑动再松开:ACTION_DOWN -> ACTION_MOVE -> ACTION_UP
下面一个图片来概括下:
2. 事件分发传递规则
众所周知,AndroidUI是由Activity、ViewGroup、View及其派生类组成的。
大致示意图如下:
其中:
Activity:控制生命周期或者处理事件ViewGroup:一组View或者多个View的集合。也是布局Layout的基类。但是特别的是,他也集成自View。View:所有UI组件的基类
从上图我们就可以看出来,事件分发的顺序就是:Activity -> ViewGroup -> View。也就是说一个点击事件产生,先交由Activity,再传到ViewGroup,再传到View。这个过程中只要有一个部分说要拦截,就不会再继续往下传递。
3. 事件分发的核心方法
其实时间分发的核心方法很简单,就由三个方法组成:
public boolean dispatchTouchEvent(MotionEvent ev) 用来进行事件的分发。如果事件能够传递给当前View,那么此方法一定会被调用,返回结果受当前View的onTouchEvent和下级View的dispatchTouchEvent方法的影响,表示是否消耗当前事件。
public boolean onInterceptTouchEvent(MotionEvent event) 在dispatchTouchEvent方法内部调用,用来判断是否拦截某个事件,如果当前View拦截了某个事件,那么在同一事件序列当中,此方法不会再次被调用,返回结果表示是否拦截当前事件。(只有ViewGroup中才有此方法,View中没有)
public boolean onTouchEvent(MotionEvent event) 在dispatchTouchEvent方法中调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列中,当前View无法再次接收到事件。
三个方法间的关系可以按下面一段伪代码表示:(参考的代码)
public boolean dispatchTouchEvent(MotionEvent ev
) {
boolean consume
= false;
if (onInterceptTouchEvent(ev
)) {
consume
= onTouchEvent
(ev
) ;
} else {
consume
= child
.dispatchTouchEvent
(ev
) ;
}
return consume
;
}
对于一个根ViewGroup,点击事件产生后,就会传递给他,这时他的dispatchTouchEvent就会被调用,然后就开始判断他是否拦截。如果拦截,那么点击事件就会给ViewGroup去处理,如果不拦截,就调用child.dispatchTouchEvent (ev) 传给子控件的dispatchTouchEvent方法。然后继续循环,直到到最底层view,也就是没有child的时候,或者直到事件被拦截。
4. 源码分析
4.1 Activity事件分发
首先先来看dispatchTouchEvent方法:
源码
public boolean dispatchTouchEvent(MotionEvent ev
) {
if (ev
.getAction() == MotionEvent
.ACTION_DOWN
) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev
)) {
return true;
}
return onTouchEvent(ev
);
}
public void onUserInteraction() {
}
public abstract boolean superDispatchTouchEvent(MotionEvent event
);
@Override
public boolean superDispatchTouchEvent(MotionEvent event
) {
return mDecor
.superDispatchTouchEvent(event
);
}
public boolean superDispatchTouchEvent(MotionEvent event
) {
return super.dispatchTouchEvent(event
);
}
public boolean onTouchEvent(MotionEvent event
) {
if (mWindow
.shouldCloseOnTouch(this, event
)) {
finish();
return true;
}
return false;
}
public boolean shouldCloseOnTouch(Context context
, MotionEvent event
) {
final boolean isOutside
=
event
.getAction() == MotionEvent
.ACTION_DOWN
&& isOutOfBounds(context
, event
)
|| event
.getAction() == MotionEvent
.ACTION_OUTSIDE
;
if (mCloseOnTouchOutside
&& peekDecorView() != null
&& isOutside
) {
return true;
}
return false;
}
流程
4.2 ViewGroup事件分发
源码
由于此部分代码过长,我们将代码拆分成两部分:
part1
@Override
public boolean dispatchTouchEvent(MotionEvent ev
) {
...
boolean handled
= false;
if (onFilterTouchEventForSecurity(ev
)) {
final int action
= ev
.getAction();
final int actionMasked
= action
& MotionEvent
.ACTION_MASK
;
if (actionMasked
== MotionEvent
.ACTION_DOWN
) {
cancelAndClearTouchTargets(ev
);
resetTouchState();
}
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
);
} else {
intercepted
= false;
}
} else {
intercepted
= true;
}
if (actionMasked
== MotionEvent
.ACTION_DOWN
) {
cancelAndClearTouchTargets(ev
);
resetTouchState();
}
part2
...
if (!canceled
&& !intercepted
) {
...
final View
[] children
= mChildren
;
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;
}
if (!canViewReceivePointerEvents(child
)
|| !isTransformedTouchPointInView(x
, y
, child
, null
)) {
ev
.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget
= getTouchTarget(child
);
if (newTouchTarget
!= null
) {
newTouchTarget
.pointerIdBits
|= idBitsToAssign
;
break;
}
resetCancelNextUpFlag(child
);
if (dispatchTransformedTouchEvent(ev
, false, child
, idBitsToAssign
)) {
mLastTouchDownTime
= ev
.getDownTime();
if (preorderedList
!= null
) {
for (int j
= 0; j
< childrenCount
; j
++) {
if (chifcldren
[childIndex
] == mChildren
[j
]) {
mLastTouchDownIndex
= j
;
break;
}
}
} else {
mLastTouchDownIndex
= childIndex
;
}
mLastTouchDownX
= ev
.getX();
mLastTouchDownY
= ev
.getY();
newTouchTarget
= addTouchTarget(child
, idBitsToAssign
);
alreadyDispatchedToNewTouchTarget
= true;
break;
}
ev
.setTargetAccessibilityFocus(false);
}
...
}
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
;
}
...
}
private TouchTarget
addTouchTarget(@NonNull View child
, int pointerIdBits
) {
final TouchTarget target
= TouchTarget
.obtain(child
, pointerIdBits
);
target
.next
= mFirstTouchTarget
;
mFirstTouchTarget
= target
;
return target
;
}
part3
if (mFirstTouchTarget
== null
) {
handled
= dispatchTransformedTouchEvent(ev
, canceled
, null
,
TouchTarget
.ALL_POINTER_IDS
);
} else {
...
}
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
);
}
}
...
}
private void resetTouchState() {
clearTouchTargets();
resetCancelNextUpFlag(this);
mGroupFlags
&= ~FLAG_DISALLOW_INTERCEPT
;
mNestedScrollAxes
= SCROLL_AXIS_NONE
;
}
流程
此图搬自Carson_Ho大佬的博客
4.3 View事件分发
源码
dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent event
) {
...
boolean result
= false;
...
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;
}
}
...
return result
;
}
从上面可以看出,onTouch的优先级高于onTouchEvent。
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();
final boolean clickable
= ((viewFlags
& CLICKABLE
) == CLICKABLE
|| (viewFlags
& LONG_CLICKABLE
) == LONG_CLICKABLE
)
|| (viewFlags
& CONTEXT_CLICKABLE
) == CONTEXT_CLICKABLE
;
if ((viewFlags
& ENABLED_MASK
) == DISABLED
) {
if (action
== MotionEvent
.ACTION_UP
&& (mPrivateFlags
& PFLAG_PRESSED
) != 0) {
setPressed(false);
}
mPrivateFlags3
&= ~PFLAG3_FINGER_DOWN
;
return clickable
;
}
if (mTouchDelegate
!= null
) {
if (mTouchDelegate
.onTouchEvent(event
)) {
return true;
}
}
if (clickable
|| (viewFlags
& TOOLTIP
) == TOOLTIP
) {
switch (action
) {
case MotionEvent
.ACTION_UP
:
...
if (!mHasPerformedLongPress
&& !mIgnoreNextUpEvent
) {
removeLongPressCallback();
if (!focusTaken
) {
if (mPerformClick
== null
) {
mPerformClick
= new PerformClick();
}
if (!post(mPerformClick
)) {
performClickInternal();
}
}
}
...
}
mIgnoreNextUpEvent
= false;
break;
...
}
return true;
}
return false;
}
public boolean performClick() {
if (mOnClickListener
!= null
) {
playSoundEffect(SoundEffectConstants
.CLICK
);
mOnClickListener
.onClick(this);
return true;
}
return false;
}
流程