我想不管是在开发过程中还是在面试过程中都会碰到View事件分发机制的相关问题,今天我们就由浅入深、全面刨析一下事件分发的机制,请耐心看完,如果看完后还没有理解就请你打我。
事件分发的原理
事件分发,其实就是责任链模式的一种(责任链模式分为纯责任链与不纯责任链,此处应为纯责任链模式。责任链模式的定义:避免将一个请求的发送者与接收者耦合在一起,让多个对象都有机会处理请求。将接收请求的对象连接成一条链,并且沿着这条链传递请求,直到有一个对象能够处理它为止)
在Android中,当Activity收到触摸事件之后,会一级一级地往下传,在每一级View中,它们各自都有权利去处理(也就是拦截)这个事件,如果这次的事件传到了最底层的View,也没能处理的话,就会从这个最底层的View一级一级地向上传回去,最终会调用Activity的onTouchEvent方法。
如果是在面试过程中我们仅回答上面的答案还是远远不够的,我们还需要深挖一下以下几个问题:
- 处理事件分发的对象有哪些?
- 事件是如何处理(分发)的?
处理事件分发的对象有哪些?
Activity、ViewGroup、View
事件传递的顺序为:Activity -> ViewGroup -> View
事件是如何处理(分发)的?
这里我们首先看一下事件分发中所涉及到的3个核心方法:
- dispatchTouchEvent()
- onTouchEvent()
- onInterceptTouchEvent()
它们的作用如下:
下面,我们继续结合源码来看一下具体的分发流程。
事件从Activity开始分发,我们先看一下Activity的dispatchTouchEvent()源码:
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
//只有Down事件才会调用此方法
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;//true则说明事件被消费,并停止事件传递,若返回false,则将事件交给Activity的onTouchEvent()处理
}
return onTouchEvent(ev);//没有View可以处理,调用Activity的onTouchEvent方法
}
/***
*每当Key,Touch,Trackball事件分发到当前Activity就会被调用。如果你想当你的Activity在运行的时候,能够得知用户正在与你的设备交互,你可以*override该方法。
*
*这个回调方法和onUserLeaveHint是为了帮助Activities智能的管理状态栏Notification;特别是为了帮助Activities在恰当的时间取消Notification。
*
*所有Activity的onUserLeaveHint 回调都会伴随着onUserInteraction。这保证当用户相关的的操作都会被通知到,例如下拉下通知栏并点击其中的条目。
*
*注意在Touch事件分发过程中,只有Touch Down 即Touch事件的开始会触发该回调,不会在move 和 up 分发时触发
*/
public void onUserInteraction() {
}
//处理事件的方法,若没有view处理触摸事件最后会回调此方法
public boolean onTouchEvent(MotionEvent event) {
//处理发生在Window边界外的触摸事件
// 返回true:说明事件在边界外,即 消费事件
// 返回false:未消费(默认)
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}
从上面的dispatchTouchEvent()中可以看出,Activity首先将事件分发给了Window进行处理,接下来那我看一下Window的源码:
public abstract class Window {
....... 此处省略无关代码
public abstract boolean superDispatchTouchEvent(MotionEvent event);
......
}
我们可以看出Window是个抽象类,而且源码中有说明:The only existing implementation of this abstract class is android.view.PhoneWindow(Window的唯一实现类是PhoneWindow),那我们就看一下PhoneWindow的源码:
public class PhoneWindow extends Window implements MenuBuilder.Callback {
// This is the top-level view of the window, containing the window decor.
//这是window的顶级view,用于window的装饰。activity中的setContentView方法就是给DecorView设置布局
private DecorView mDecor;
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
}
PhoneWindow将事件转交给DecorView进行处理,那我们继续看一下DecorView的源码:
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
public boolean superDispatchTouchEvent(MotionEvent event) {
// 调用ViewGroup的dispatchTouchEvent(),FrameLayout的父类是ViewGroup
return super.dispatchTouchEvent(event);
}
}
DecorView调用了它父类的dispatchTouchEvent方法进行处理,跳转进去你会发现了直接跳转到了ViewGroup的dispatchTouchEvent方法,因为FrameLayout的父类就是ViewGroup。接下来我们看一下ViewGroup中的源码:
插一句,如果有面试官问“事件分发是如何从Activity传递到ViewGroup的?”我想你应该知道怎么回答了吧。
ViewGroup:
@UiThread
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
//辅助功能(朗读、放大镜等辅助功能)事件判断,带有accessibility的基本都是辅助功能相关,可以不去关注
// If the event targets the accessibility focused view and this is it, start
// normal event dispatch. Maybe a descendant is what will handle the click.
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
ev.setTargetAccessibilityFocus(false);
}
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// 如果为down事件,则重置所有状态。一个完整的事件序列是以DOWN开始,以UP结束,
//所以如果是DOWN事件,那么说明是一个新的事件序列,所以需要初始化之前的状态
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// 是否拦截事件
final boolean intercepted;
//如果ViewGroup里面的子元素view能够处理事件的话,那么这个mFirstTouchTarget就会指向这个子元素view
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//判断是否不允许拦截事件,子View在非down事件中调用requestDisallowInterceptTouchEvent来设置是否允许父View拦截事件,
//false表示可以拦截,true表示不可以拦截
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//调用onInterceptTouchEvent方法判断是否需要拦截
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
//没有子元素可以处理事件,并且不是新的触摸事件则默认拦截
intercepted = true;
}
// If intercepted, start normal event dispatch. Also if there is already
// a view that is handling the gesture, do normal event dispatch.
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}
// 事件是否取消
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
//split是表示是否可以分发给多个孩子
// Update list of touch targets for pointer down, if needed.
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
//是否消耗此事件
boolean alreadyDispatchedToNewTouchTarget = false;
//没有取消事件并且也不拦截事件
if (!canceled && !intercepted) {
// If the event is targeting accessibility focus we give it to the
// view that has accessibility focus and if it does not handle it
// we clear the flag and dispatch the event to all children as usual.
// We are looking up the accessibility focused host to avoid keeping
// state since these events are very rare.
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
//ACTION_POINTER_DOWN表示有非主要的手指按下(即按下之前已经有手指在屏幕上了,多指触摸事件)
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex(); // always 0 for down
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
// Clean up earlier touch targets for this pointer id in case they
// have become out of sync.
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
final ArrayList<View> preorderedList = buildTouchDispatchChildList();//将子view排序后返回
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
//对子View进行遍历
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
// If there is a view that has accessibility focus we want it
// to get the event first and if not handled we will perform a
// normal dispatch. We may do a double iteration but this is
// safer given the timeframe.
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
// 如果子View能否接受事件,或者点击事件的坐标落在View范围内,如果2个条件有一个不满足则直接continue进入下一次循环
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
// 当前子View能接收事件,为子View创建TouchTarget
newTouchTarget = getTouchTarget(child);
//如果有子View处理即newTouchTarget 不为null,则跳出循环
if (newTouchTarget != null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
// 调用dispatchTransformedTouchEvent把事件分配给子View,后面会具体分析该方法
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original 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();
//当child处理了点击事件,那么会设置mFirstTouchTarget指向该view, 在addTouchTarget被赋值
newTouchTarget = addTouchTarget(child, idBitsToAssign);
//事件已消耗
alreadyDispatchedToNewTouchTarget = true;
//跳出循环,结束分发
break;
}
// The accessibility focus didn't handle the event, so clear
// the flag and do a normal dispatch to all children.
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
if (newTouchTarget == null && mFirstTouchTarget != null) {
// Did not find a child to receive the event.
// Assign the pointer to the least recently added target.
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
//没有子View可以处理事件或者此控件将事件拦截,因此把自身当作一个view控件(注意,自身原本是ViewGroup类型)来处理事件,进入//dispatchTransformedTouchEvent方法后你后看到child为null时,会调用View.dispatchTouchEvent()来处理事件
//后面会分析dispatchTransformedTouchEvent方法
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// Dispatch to touch targets, excluding the new touch target if we already
// dispatched to it. Cancel touch targets if necessary.
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
//这里就是区分了ACTION_DOWN事件和别的事件,因为在在上面我们知道,如果子View消耗了ACTION_DOWN事件,那么//alreadyDispatchedToNewTouchTarget和newTouchTarget已经有值了,所以就直接置handled为true并返回
handled = true;
} else {//非down事件
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
//分发事件
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
// 处理CANCEL和UP事件的情况
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);
}
}
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}
//如果没有重写此方法的话,一般情况下都会返回false,不拦截
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
&& ev.getAction() == MotionEvent.ACTION_DOWN
&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
&& isOnScrollbarThumb(ev.getX(), ev.getY())) {
return true;
}
return false;
}
/**
*
* 如果child!=null则调用child.dispatchTouchEvent方法(即ViewGroup的dispatchTouchEvent方法),
* 否则调用父类的dispatchTouchEvent方法,即View.dispatchTouchEvent方法(因为child为null,说明此控件不包含子控件或者此控件将事件拦截,因 *此把它当作view来处理,因此会走view的dispatchTouchEvent方法),
* 因此会执行View的onTouch() ->> onTouchEvent() ->> performClick() ->> onClick(),即自己处理该事件,事件不会往下传递(具体请参考View *事件的分发机制中的View.dispatchTouchEvent())
*/
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// Canceling motions is a special case. We don't need to perform any transformations
// or filtering. The important part is the action, not the contents.
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;
}
// Calculate the number of pointers to deliver.
final int oldPointerIdBits = event.getPointerIdBits();
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
// If for some reason we ended up in an inconsistent state where it looks like we
// might produce a motion event with no pointers in it, then drop the event.
if (newPointerIdBits == 0) {
return false;
}
// If the number of pointers is the same and we don't need to perform any fancy
// irreversible transformations, then we can reuse the motion event for this
// dispatch as long as we are careful to revert any changes we make.
// Otherwise we need to make a copy.
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
transformedEvent = event.split(newPointerIdBits);
}
// Perform any necessary transformations and dispatch.
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
handled = child.dispatchTouchEvent(transformedEvent);
}
// Done.
transformedEvent.recycle();
return handled;
}
}
上面的代码比较多,可以借助以下流程图进行梳理:
另外经过分析后我们应该也明白了为什么子View可以通过requestDisallowInterceptTouchEvent方法干预父View的事件分发过程,但ACTION_DOWN事件除外,因为Down事件会重置所有状态,会让子View设置的FLAG_DISALLOW_INTERCEPT标志位失效。
从上面我们可以看到如果没有子view处理事件的话ViewGroup会调用View.dispatchTouchEvent方法,下面我们看一下该源码:
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {
public boolean dispatchTouchEvent(MotionEvent event) {
//此if代码块就是判断当前事件是否能获得焦点,如果不能获得焦点或者不存在一个View,那我们就直接返回False跳出循环
// If the event should be handled by accessibility focus first.
if (event.isTargetAccessibilityFocus()) {
// We don't have focus or no virtual descendant has it, do not handle the event.
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
// We have focus and got the event, then use normal event dispatch.
event.setTargetAccessibilityFocus(false);
}
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// 当我们手指按到View上时,其他的依赖滑动都要先停下
stopNestedScroll();
}
//过滤掉一些不合法的事件,比如当前的View的窗口被遮挡了。
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//ListenerInfo 是view的一个内部类 里面有各种各样的listener,例如OnClickListener,OnTouchListener等等
ListenerInfo li = mListenerInfo;
//首先判断如果监听li对象!=null, 且是否有实现OnTouchListener,当前的view状态是不是ENABLED,OnTouchListener的onTouch中返//回值是否为true
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
//如果满足这些条件那么返回true,这个事件就在此处理
result = true;
}
//如果上一段判断的条件没有满足,就判断View自身的onTouchEvent方法有没有处理,没有处理最后返回false,处理了返回true;
//从这里也可以看出如果上面的onTouch方法返回true的话onTouchEvent方法就不会被执行
if (!result && onTouchEvent(event)) {
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
// Clean up after nested scrolls if this is the end of a gesture;
// also cancel it if we tried an ACTION_DOWN but we didn't want the rest
// of the gesture.
//如果这是手势的结尾,则在嵌套滚动后清理
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
/**
* 应用安全策略过滤触摸事件,当窗口被遮蔽时事件将会被过滤,即意味着不会执行事件分发。
*
* @param event 即将被过滤的事件对象
* @return 当事件允许被分发时返回 TRUE,否则为FALSE。
*
* @see #getFilterTouchesWhenObscured
*/
public boolean onFilterTouchEventForSecurity(MotionEvent event) {
//noinspection RedundantIfStatement
if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0 && (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
// 当 Window 被遮蔽时,则事件将会被丢弃。
return false;
}
return true;
}
public boolean onTouchEvent(MotionEvent event) {
// 获取动作点击屏幕的位置坐标
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
//如果当前View是一个DISABLED状态,且当前View是一个可点击或者是可长按的状态 则clickable返回true。表示当前事件在此消耗且不做处理
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
//如果当前View状态为DISABLED
if ((viewFlags & ENABLED_MASK) == DISABLED) {
//如果View的状态是被按压过,且当抬起事件产生,重置View状态为未按压,刷新Drawable的状态
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return clickable;
}
// 如果设置了触摸代理
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
//就交给mTouchDelegate.onTouchEvent处理,如果返回true,则事件被处理了,则不会向下传递
return true;
}
}
//如果当前View的状态是可点击或者是可长按的,就对事件流进行细节处理
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
if ((viewFlags & TOOLTIP) == TOOLTIP) {
handleTooltipUp();
}
//清除各种状态
if (!clickable) {
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
}
//prepressed指的是,如果view包裹在一个scrolling View中,可能会进行滑动处理,所以设置了一个prePress的状态
//大致是等待一定时间,然后没有被父类拦截了事件,则认为是点击到了当前的view,从而显示点击态
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
//如果是pressed状态或者是prepressed状态,才进行处理
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
//如果设定了获取焦点,那么调用requestFocus获得焦点
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
//在释放之前给用户显示View的prepressed的状态,状态需要改变为PRESSED,并且需要将背景变为按下的状态为了让用户感知到
if (prepressed) {
// The button is being released before we actually
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
setPressed(true, x, y);
}
// 是否处理过长按操作了,如果是,则直接返回
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
//如果不是长按的话,仅仅是一个Tap,所以移除长按的回调
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.
//UI子线程去执行click,为了让click事件开始的时候其他视觉发生变化不影响。
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
//如果post消息失败,直接调用处理click事件
if (!post(mPerformClick)) {
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
//ViewConfiguration.getPressedStateDuration() 获得的是按下效果显示的时间,由PRESSED_STATE_DURATION常量指定,在2.2中为125毫秒,也就是隔了125毫秒按钮的状态重置为未点击之前的状态。目的是让用户感知到click的效果
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
//如果通过post(Runnable runnable)方式调用失败,则直接调用
// If the post failed, unpress right now
mUnsetPressedState.run();
}
//移除Tap的回调 重置View的状态
removeTapCallback();
}
mIgnoreNextUpEvent = false;
break;
//如果是按下的手势
case MotionEvent.ACTION_DOWN:
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
}
//在触摸事件中执行按钮相关的动作,如果返回true则表示已经消耗了down
mHasPerformedLongPress = false;
if (!clickable) {
//仅在View支持长按时执行有效,否则直接退出方法
checkForLongClick(0, x, y);
break;
}
//这个performButtonActionOnTouchDown(event) 一般的设备都是返回false.因为目前的实现中,它是处理如鼠标的右键的.(如果此View响应或者其父View响应右键菜单,那么就此事件就被消耗掉了.)
if (performButtonActionOnTouchDown(event)) {
break;
}
// 判断当前view是否是在滚动器当中
// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean isInScrollingContainer = isInScrollingContainer();
// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
//将view的状态变为PREPRESSED,检测是Tap还是长按事件
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
//如果是在滚动器当中,在滚动器当中的话延迟返回事件,延迟时间为 ViewConfiguration.getTapTimeout()=100毫秒
//在给定的tapTimeout时间之内,用户的触摸没有移动,就当作用户是想点击,而不是滑动.
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
checkForLongClick(0, x, y);
}
break;
//接收到系统发出的ACTION_CANCLE事件时,重置状态, 将所有的状态设置为最初始
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
// 判断当前滑动事件是否还在当前view当中,不是就执行下面的方法
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
// Remove any future long press/tap checks
// 移除PREPRESSED状态和对应回调
removeTapCallback();
removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
// 是PRESSED就移除长按检测,并移除PRESSED状态
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
break;
}
return true;
}
return false;
}
//如果View设置了OnClickListener,那么会回调onClick方法
public boolean performClick() {
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);
return result;
}
}
此处也提供一张流程图帮助您梳理一下逻辑:
好了,至此已经全部分析完了,最后再提供一张事件分发流程图:
参考链接:
https://www.jianshu.com/p/8527dba23512
https://www.jianshu.com/p/238d1b753e64
https://www.jianshu.com/p/38015afcdb58