随着手机App的发展,基础组件已经很难满足我们的需求,因此自定义view成为了我们常见的开发需求,而今天的主题事件的分发机制无疑是自定义view的一个基础。那么我们就从源码的角度来梳理一下,从手指触到屏幕的那一刻起到底发生了什么。
首先我们需要知道的是四种触摸事件类型,即:
- MotionEvent.ACTION_DOWN 手指刚接触屏幕的时刻
- MotionEvent.ACTION_UP 手指从屏幕离开的时刻
- MotionEvent.ACTION_MOVE 手指在屏幕上滑动
- MotionEvent.ACTION_CANCEL 非人为因素取消
以及三个事件分发对象,那为什么是他们三个?因为只有他们有dispatchTouchEvent()方法
- Activity
- ViewGroup
- View
我们都知道是Activity承载了我们能在屏幕上看到的一切,必然事件分发是从Activity开始,但事件分发等应该是view的特性,为何Activity也有此方法呢?我们就跟着源码细细看来吧,进入Activity的dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
此时我们可发现一些端倪,原来Activity调用的是Window的superDispatchTouchEvent(ev)方法,了解过view的加载流程的小伙伴都知道Activity的层级结构是这样的,而PhoneWindow是抽象类Window的唯一实现类
现在你是不是猜到了什么?没错进入PhoneWindow的superDispatchTouchEvent(ev)方法你会发现,他调用了DecorView的superDispatchTouchEvent(event)方法,而DecorView继承自FrameLayout,而FrameLayout则是一个ViewGroup,这就完成了Activity向ViewGroup的事件分发,由此真相大白于天下。
PhoneWindow.class
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
OK,我们先不往下看ViewGroup,先看下Activity中的onTouchEvent方法,这里没有再调用ViewGroup的方法,所以这里是个归口,
无论事件是否消费,到这里一次触摸操作就完成了。
Activity.class
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}
紧接着我们来到了ViewGroup的dispatchTouchEvent(MotionEvent ev)方法
ViewGroup.class
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
....
//返回值
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
//如果为down事件做了一些重置和标记清除
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// Check for interception. 检查是否拦截
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
//这个值比较重要,对应可查看
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) 这个方法就是子view请求viewGroup不做事件拦截的方法
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//看是否拦截,默认不拦截
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action);
} 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;
}
.....
if (!canceled && !intercepted) {
//不拦截
//对down事件做特殊处理
.....
if (newTouchTarget == null && childrenCount != 0) {
.....
//倒序遍历子view
for (int i = childrenCount - 1; i >= 0; i--) {
......
//判断子view能否接受到事件和点击位置是否在子view的区域内,若不能就跳过,判断下一个子view
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
.......
//查看dispatchTransformedTouchEvent方法,将事件分发传到了子view一层
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
//这里将mFirstTouchTarget指向了这个子view
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
}
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
//没有子view能消费这个事件,查看下面这个方法可知,他调用了ViewGroup的父类的dispatchTouchEvent方法,并间接调用了父类的onTouchEvent方法
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
//走到这里则说明viewGroup找到了能消费down及后续事件的子view
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
.........
}
// Update list of touch targets for pointer up or cancel, if needed.
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
......
}
}
.......
return handled;
}
内容比较多,包含了对各种条件的处理,但大致可表示为以下伪代码
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean consume = false;
//调用onInterceptTouchEvent判断是否拦截
if(onInterceptTouchEvent(ev)){
//拦截则调用自身的onTouchEvent
consume = onTouchEvent(ev);
}else{
//不拦截,将事件分发给子view
consume = child.dispatchTouchEvent(ev);
}
return consume;
}
这里需要说明的是ViewGroup并没有重写onTouchEvent()方法,所以我们重写时是重写了view的OnTouchEvent。
相对来说View的dispatchTouchEvent()方法就简单多了
View.class
public boolean dispatchTouchEvent(MotionEvent event) {
.....
boolean result = false;
if (onFilterTouchEventForSecurity(event)) {
......
//整个比较重要的在这,即在view中onTouch方法会优先于onTouchEvent方法调用
如果onTouch返回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;
}
最后我们来看下当事件传递到view后是怎么处理的,打开view的onTouchEvent方法
View.class
public boolean onTouchEvent(MotionEvent event) {
....
//记录这个view是否是可点击的
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
// 这里要说明的是即使view是DISABLE状态,他也是可点击的只是没有响应没有效果
return clickable;
}
......
//以下就是对各种事件的处理
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
......
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// mHasPerformedLongPress为false就移除长按回调
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state 执行onClick的回调
if (!focusTaken) {
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;
case MotionEvent.ACTION_DOWN:
//表示是否处理了长按事件,默认false
mHasPerformedLongPress = false;
// 是否在一个可滑动的容器内
boolean isInScrollingContainer = isInScrollingContainer();
// 在这无论if或else都走到了一个方法checkForLongClick(),间接调用CheckForLongPress()方法在此也能看到长按时间界限为500ms,在这个方法中对mHasPerformedLongPress付了值,即是否处理了长按事件
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
//在此做了一个延时操作
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
setPressed(true, x, y);
checkForLongClick(0, x, y);
}
break;
case MotionEvent.ACTION_CANCEL:
if (clickable) {
setPressed(false);
}
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
break;
case MotionEvent.ACTION_MOVE:
if (clickable) {
drawableHotspotChanged(x, y);
}
// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
// Remove any future long press/tap checks
removeTapCallback();
removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
break;
}
return true;
}
return false;
}
到此由Activity到ViewGroup到View的分发流程就基本梳理完了,最后附一张经典的流程图能让大家更清晰的理解整个事件分发及事件消费流程,图中根ViewGroup即DecorView(Activity->PhoneWindow->DecorView)