前言:上一篇我们已经从事件分发执行流程入手,一起来了解并分析了事件分发的经过,大家应该从分析中能对事件分发的有个总体的认识,并且我相信应该也能自己分析出事件会如何执行,其实就那么点东西,弄明白了就不难了,但是今天我们还是要来看看activity,viewgroup,view的相关源码来学习一下他们的工作原理,那就开始吧!
首先来结合我们上一篇的工程情况来看看:
再次强调,1个点击事件发生后,事件先传到Activity、再传到ViewGroup、最终再传到 View,那么我们分别从Activity对点击事件的分发机制、ViewGroup对点击事件的分发机制、View对点击事件的分发机制来分析源码。
一、Activity对点击事件的分发机制
从我们上一天的分析可以看到,当一个新的点击事件发生时,首先是会进入到Activity的dispatchTouchEvent()方法内,那么我们先来看看Activity的dispatchTouchEvent()的源码:
public boolean dispatchTouchEvent(MotionEvent ev) {
//由于每次点击事件第一个肯定是ACTION_DOWN,所以这里肯定是true
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
//如果getWindow().superDispatchTouchEvent(ev)是true的话
//那么dispatchTouchEvent()返回的就是true,事件被消费,传递结束
return true;
}
//否则就会执行onTouchEvent方法
return onTouchEvent(ev);
}
- activity的onUserInteraction方法:
/**
* Called whenever a key, touch, or trackball event is dispatched to the
* activity. Implement this method if you wish to know that the user has
* interacted with the device in some way while your activity is running.
* This callback and {@link #onUserLeaveHint} are intended to help
* activities manage status bar notifications intelligently; specifically,
* for helping activities determine the proper time to cancel a notfication.
*
* <p>All calls to your activity's {@link #onUserLeaveHint} callback will
* be accompanied by calls to {@link #onUserInteraction}. This
* ensures that your activity will be told of relevant user activity such
* as pulling down the notification pane and touching an item there.
*
* <p>Note that this callback will be invoked for the touch down action
* that begins a touch gesture, but may not be invoked for the touch-moved
* and touch-up actions that follow.
*
* @see #onUserLeaveHint()
*/
public void onUserInteraction() {
}
这里返回的是一个空方法,当我们不知道这个方法是干啥用的时候,官方的注释就是最好的答案,也许你英语不好,但是我们有在线翻译不是么?这个方法主要是给用户提供告知和手机的一些交互的情况,要是用得到的话就实现它然后重写代码,这里我们用不到,不做了解。
- activity的getWindow().superDispatchTouchEvent(ev)
通过鼠标点进去后发现,这是Window的抽象类
/**
* Used by custom windows, such as Dialog, to pass the touch screen event
* further down the view hierarchy. Application developers should
* not need to implement or call this.
*
*/
public abstract boolean superDispatchTouchEvent(MotionEvent event);
我们只能看看Window的实现类了
可以看到Window唯一的实现类只有PhoneWindow,那么我们进到PhoneWindow中查找superDispatchTouchEvent方法,代码如下:
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
接着点进mDecor中去看看,会发现进入了DecorView中:
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
继续往下找,发现进入了ViewGroup中:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
...具体的到ViewGroup中讲解
}
现在再来审视一下这张图
发现没有,事件通过getWindow().superDispatchTouchEvent(ev)往下一直传递到ViewGroup中,如果ViewGroup的dispatchTouchEvent返回了false,那么我们这里就会执行onTouchEvent方法,交由我们自己实现的onTouchEvent方法处理,如果ViewGroup的dispatchTouchEvent返回了true,那么我们这return true,事件就会往下执行,符合我们上一篇首图的逻辑。那么接下来我们顺着往下走来看看onTouchEvent方法。
- activity的onTouchEvent()方法(系统默认的,如果未重写执行此流程)
来看看源码:
/**
* Called when a touch screen event was not handled by any of the views
* under it. This is most useful to process touch events that happen
* outside of your window bounds, where there is no view to receive it.
*
* @param event The touch screen event being processed.
*
* @return Return true if you have consumed the event, false if you haven't.
* The default implementation always returns false.
*/
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}
这段代码表示:onTouchEvent方法在触摸屏幕事件没有被任何视图处理时调用,即没有任何视图(ViewGroup、View)消费事件时,Activity的onTouchEvent方法会执行。如果你已经消费了这个事件,返回true,如果你没有,返回false,默认的实现总是返回false。即只有在点击事件在Window边界外才会返回true,一般情况都返回false。
下面我们来看看mWindow.shouldCloseOnTouch(this, event)方法。
- Window的shouldCloseOnTouch()方法
public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN
&& isOutOfBounds(context, event) && peekDecorView() != null) {
return true;
}
return false;
}
这个方法主要用来判断是否是边界外点击事件+是否是DOWN事件+event的坐标是否在边界内。如果是在边界外并且是DOWN事件,那么就意味着返回true,那么就是消费事件,如果不是则返回false,不消费事件,到这里Activity的事件分发已经执行完毕了。
因为我们在MainActivity中已经重写了onTouchEvent方法,所以上述过程当getWindow().superDispatchTouchEvent(ev)返回false的时候,事件就会交由我们MainActivity中的onTouchEvent方法处理,所以看看我们上一篇的测试3
并且运行结果也和我们上述分析一致:
二、ViewGroup对点击事件的分发机制
由于我的api较新,所以这里的代码是Android7.0的代码,并且由于ViewGroup中的dispatchTouchEvent方法较长,所以咱们这里只择出部分关键代码来分析如何运作的。
// Check for interception.
//定义一个变量是否拦截
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//如果是down事件并且传入对象不为空则进入
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 {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
- ViewGroup的onInterceptTouchEvent方法
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;
}
实现此方法拦截所有触摸屏运动事件,这允许我们观看事件,因为它们被分派给它的孩子,并在任何时候对当前的手势拥有所有权。使用这个函数需要一些注意,因为它是相当复杂的,您将在这里收到down事件,down事件将由这个viewgroup的子节点处理,或者由您自己的onTouchEvent()方法处理。这意味着您应该实现onTouchEvent()以返回true。同样,通过从onTouchEvent()返回true,您将不会在onInterceptTouchEvent()中接收任何后续事件,并且所有的触摸处理都必须在onTouchEvent()中发生,就像normal一样。只要您从这个函数返回false,每个后续事件(包括最终的up)将首先在这里传递,然后传递到目标的onTouchEvent()。
当前的目标将接收ACTION_CANCEL事件,并且不会在这里传递更多的消息。
盗取网上一篇博客的其中的一张图结合分析的结论来看看
到这里相信大家对ViewGroup如何分发事件有了自己的理解了,下面接着来看看View是处理事件的。
三、View对点击事件的分发机制
鉴于Android7.0的代码关于View这块比较复杂,咱们还是看看Android2.0的代码吧,虽然代码简单,但是工作流程肯定是一样的,只不过后期做了优化,咱们了解原理就好了。
public boolean dispatchTouchEvent(MotionEvent event) {
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}
- mOnTouchListener
关于onTouchListener的赋值在这个方法里:
public void setOnTouchListener(OnTouchListener l) {
mOnTouchListener = l;
}
可以看到,只要我们给view设置了setOnTouchListener,那么mOnTouchListener一定是有值的。
- (mViewFlags & ENABLED_MASK) == ENABLED
这个条件是判断当前点击的控件是否enable,因为我们既然要给这个view设置点击事件的话,那么我们肯定会给它setOnClickListener,所以这个条件应该是成立的。
- mOnTouchListener.onTouch(this, event)
这个事件需要我们注册了onTouchListener后进行回调返回结果,举个例子,在咱们上一篇中咱们也进行了注册。
view.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
System.out.println("执行了onTouch(), 动作是:" + event.getAction());
return false;
}
});
我们已经分析过:
return false;那么上述的if条件不成立,那么就会执行onTouchEvent方法,和我们上一篇验证的结果一样。
return true;上述条件成立,事件传递结束,直接被消费掉,和我们验证的结果也一样。
接下来我们来看看onTouchEvent方法。
- View.onTouchEvent()方法
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;
}
}
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
//如果view是可点击的,包括长按,那么就进入switch条件中
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
if ((mPrivateFlags & PRESSED) != 0) {
// 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
if (mPendingCheckForLongPress != null) {
removeCallbacks(mPendingCheckForLongPress);
}
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
//执行这个方法,见详解
performClick();
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
}
break;
case MotionEvent.ACTION_DOWN:
mPrivateFlags |= PRESSED;
refreshDrawableState();
if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
postCheckForLongClick();
}
break;
case MotionEvent.ACTION_CANCEL:
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
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 = ViewConfiguration.get(mContext).getScaledTouchSlop();
if ((x < 0 - slop) || (x >= getWidth() + slop) ||
(y < 0 - slop) || (y >= getHeight() + slop)) {
// Outside button
if ((mPrivateFlags & PRESSED) != 0) {
// Remove any future long press checks
if (mPendingCheckForLongPress != null) {
removeCallbacks(mPendingCheckForLongPress);
}
// Need to switch from pressed to not pressed
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
}
} else {
// Inside button
if ((mPrivateFlags & PRESSED) == 0) {
// Need to switch from not pressed to pressed
mPrivateFlags |= PRESSED;
refreshDrawableState();
}
}
break;
}
//如果该控件可点击的话,那么一定是return true;
return true;
}
//如果该控件不可点击,一定return false;
return false;
}
- 详解performClick()
public boolean performClick() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
if (mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
mOnClickListener.onClick(this);
return true;
}
return false;
}
如果我们给view.setOnClickListener(new View.OnClickListener() {});那么这里mOnClickListener一定不为null,所以return true;如果没设置click监听,那么就return false;然后事件根据返回结果进行传递或者消费。
总结:
通过上一篇各种情况下执行流程和本篇源码分析,我们应该能够清楚的了解了Android事件分发到底是个什么东西了,最好的学习方法还是写个demo,然后有不明白的地方点进去看看源码,不要求全部看懂,找找我们关心的重点,能大概分析清楚是怎么工作的即可,祝大家学习愉快~