基础了解
MotionEvent
所谓点击事件分发,其实就是对MotionEvent分发。当一个MotionEvent产生了以后,系统需要把这个事件传递给一个具体的View,而这个传递的过程就是分发过程。
三种主要事件Action
package android.view;
public final class MotionEvent extends InputEvent implements Parcelable {
...
/**
* Constant for {@link #getActionMasked}: A pressed gesture has started, the
* motion contains the initial starting location.
* <p>
* This is also a good time to check the button state to distinguish
* secondary and tertiary button clicks and handle them appropriately.
* Use {@link #getButtonState} to retrieve the button state.
* </p>
* 手指刚接触屏幕
*/
public static final int ACTION_DOWN = 0;
/**
* Constant for {@link #getActionMasked}: A pressed gesture has finished, the
* motion contains the final release location as well as any intermediate
* points since the last down or move event.
* 手指在屏幕上移动
*/
public static final int ACTION_UP = 1;
/**
* Constant for {@link #getActionMasked}: A change has happened during a
* press gesture (between {@link #ACTION_DOWN} and {@link #ACTION_UP}).
* The motion contains the most recent point, as well as any intermediate
* points since the last down or move event.
* 手指从屏幕上松开的一瞬间
*/
public static final int ACTION_MOVE = 2;
...
}
- 当手指点击屏幕后松开,事件序列为DOWN->UP
- 当手指点击屏幕,滑动一会,再抬起手指离开屏幕,时间序列为DOWN->MOVE->..MOVE->UP
getX/getY | getRawX/getRawY
- getX/getY 返回的是相当于当前View左上角的x和y坐标
- getRawX/getRawY 返回的是相对于手机屏幕左上角的x和y坐标
三个方法了解一下
android.view.View # public boolean dispatchTouchEvent(MotionEvent event)
用来进行事件的分发。如果事件能够传递给当前View,那么此方法一定会被调用。
android.view.ViewGroup # public boolean onInterceptTouchEvent(MotionEvent ev)(MotionEvent ev)
在dispatchTouchEvent内部调用,用来判断是否拦截某个事件,如果当前View拦截了某个事件,那么同一个事件序列当中,此方法不会被再次调用,返回结果表示是否拦截当前事件(true表示拦截,false表示继续向下分发给它的子元素)。
android.view.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的onInterceptTouchEvent执行后返回结果,并根据这个结果进行不同方式的处理:
a.若返回结果为为true,则表示拦截,这时候根据代码显示就是终止对事件的分发并且自身调用onTouchEvent对本此事件进行处理。
b.若返回结果为false,根据代码显示,则会去调用child.dispatchTouchEvent(ev),也就是继续向下分发给自己的子View元素,再往后就是子元素去进行处理直到这个事件结束。 _
一些结论
(1)同一个事件序列是指从手指接触屏幕的那一刻起,到手指离开屏幕的那一刻结束,在这个过程中所产生的一系列事件,这个事件序列以down事件开始,中间含有数量不定的move事件,最终以up事件结束。
(2)正常情况下,一个事件序列只能被一个View拦截且消耗。这一条的原因可以参考(3),因为一旦一个元素拦截了某此事件,那么同一个事件序列内的所有事件都会直接交给它处理,因此同一个事件序列中的事件不能分别由两个View同时处理,但是通过特殊手段可以做到,比如一个View将本该自己处理的事件通过onTouchEvent强行传递给其他View处理。
(3)某个View一旦决定拦截,那么这一个事件序列都只能由它来处理(如果事件序列能够传递给它的话),并且它的onInterceptTouchEvent不会再被调用。这条也很好理解,就是说当一个View决定拦截一个事件后,那么系统会把同一个事件序列内的其他方法都直接交给它来处理,因此就不用再调用这个View的onInterceptTouchEvent去询问它是否要拦截了。
(4)某个View一旦开始处理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent返回了false),那么同一事件序列中的其他事件都不会再交给它来处理,并且事件将重新交由它的父元素去处理,即父元素的onTouchEvent会被调用。意思就是事件一旦交给一个View处理,那么它就必须消耗掉,否则同一事件序列中剩下的事件就不再交给它来处理了,这就好比上级交给程序员一件事,如果这件事没有处理好,短期内上级就不敢再把事情交给这个程序员做了,二者是类似的道理。
(5)如果View不消耗除ACTION_DOWN以外的其他事件,那么这个点击事件会消失,此时父元素的onTouchEvent并不会被调用,并且当前View可以持续收到后续的事件,最终这些消失的点击事件会传递给Activity处理。
(6)ViewGroup默认不拦截任何事件。Android源码中ViewGroup的onInterceptTouch-Event方法默认返回false。
(7)View没有onInterceptTouchEvent方法,一旦有点击事件传递给它,那么它的onTouchEvent方法就会被调用。
(8)View的onTouchEvent默认都会消耗事件(返回true),除非它是不可点击的(clickable 和longClickable同时为false)。View的longClickable属性默认都为false,clickable属性要分情况,比如Button的clickable属性默认为true,而TextView的clickable属性默认为false。
(9)View的enable属性不影响onTouchEvent的默认返回值。哪怕一个View是disable状态的,只要它的clickable或者longClickable有一个为true,那么它的onTouchEvent就返回true。
(10)onClick会发生的前提是当前View是可点击的,并且它收到了down和up的事件。
(11)事件传递过程是由外向内的,即事件总是先传递给父元素,然后再由父元素分发给子View,通过requestDisallowInterceptTouchEvent方法可以在子元素中干预父元素的事件分发过程,但是ACTION_DOWN事件除外。
深入了解
事件分发流程梳理1—从Activity到顶级ViewGroup(View)
点击事件最先传递给Activity,由Activity的dispatchTouchEvent来进行事件派发,具体工作是由Window来完成。先知道这么多,我们就以Activity作为入口,然后就边看代码变分析吧。
Activity#dispatchTouchEvent
package android.app;
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory2,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2,
Window.OnWindowDismissedCallback, WindowControllerCallback,
AutofillManager.AutofillClient {
......
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
......
}
这个方法其实跟本文主题无关,但看见了还是简单了解一下,一个判断,当事件DOWN来袭的时候。调用onUserInteraction()
,首先它是一个空实现方法,这种方法一般是系统留给我们复写的接口方法。然后它跟另外一个方法有关系onUserLeaveHint
,这俩都是在特定情境下可以复写的接口方法。
Activity#onUserInteraction()
activity在分发各种事件的时候会调用该方法,注意:启动另一个activity,Activity#onUserInteraction()会被调用两次,一次是activity捕获到事件,另一次是调用Activity#onUserLeaveHint()之前会调用Activity#onUserInteraction()。Activity#onUserLeaveHint()
用户手动离开当前activity,会调用该方法,比如用户主动切换任务,短按home进入桌面等。系统自动切换activity不会调用此方法,如来电,灭屏等。
然后我们看到这一行:
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
getWindow()返回一个Window,然后看Window下的superDispatchTouchEvent。
Window#superDispatchTouchEvent
package android.view;
/**
* Abstract base class for a top-level window look and behavior policy. An
* instance of this class should be used as the top-level view added to the
* window manager. It provides standard UI policies such as a background, title
* area, default key processing, etc.
*
* <p>The only existing implementation of this abstract class is
* android.view.PhoneWindow, which you should instantiate when needing a
* Window.
*/
public abstract class 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);
......
}
一个抽象方法,我们去找他的实现类。看顶上的注释:
The only existing implementation of this abstract class is android.view.PhoneWindow
PhoneWindow#superDispatchTouchEvent
package com.android.internal.policy
public class PhoneWindow extends Window implements MenuBuilder.Callback {
// 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);
}
......
}
科普一波:
DecorView是啥?它一般是当前界面最底层的容器(即setContentView之后产生的View的父容器)。可以通过代码Activity$View decorView = getWindow().getDecorView(); 获得
好了,来嘛,继续看嘛,mDecor.superDispatchTouchEvent(event);
,看看DecorView嘛。
PhoneWindow#superDispatchTouchEvent
package com.android.internal.policy
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
......
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
......
}
它调用了super.dispatchTouchEvent(event)
,看它的父类,这里DecorView extends FrameLayout
,FrameLayout是个ViewGroup,本质上也是个View,所以到这儿,事件就从Activity传递到了ViewGroup层。
事件分发流程梳理2-ViewGroup层的事件下发流程
分发逻辑回顾
点击事件达到顶级View(一般是一个ViewGroup)以后,会调用ViewGroup的dispatchTouchEvent方法,然后的逻辑是这样的:
如果顶级ViewGroup拦截事件即onInterceptTouchEvent返回true,则事件由ViewGroup处理
- 这时如果ViewGroup的mOnTouchListener被设置,则onTouch会被调用。
- 否则onTouchEvent会被调用。在onTouchEvent中,如果设置了mOnClickListener,则onClick会被调用。
- 如果都提供的话,onTouch会屏蔽掉onTouchEvent。
如果顶级ViewGroup不拦截事件,返回false
- 则事件会传递给它所在的点击事件链上的子View,这时子View的dispatchTouchEvent会被调用。到此为止,事件已经从顶级View传递给了下一层View,接下来的传递过程和顶级View是一致的,如此循环,完成整个事件的分发。
ViewGroup—onInterceptTouchEvent调用流程分析(是否拦截事件)
在ViewGroup内搜到方法dispatchTouchEvent
,然后ctrl+f // Check for interception.
// Check for interception.
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); // 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;
}
- actionMasked == MotionEvent.ACTION_DOWN
当前事件是按下事件。 - mFirstTouchTarget != null
当事件由ViewGroup的子元素处理成功时,mFirstTouchTarget会被赋值指向子元素,于是(ViewGroup没拦截时)mFirstTouchTarget!=null,当ViewGroup拦截时,则mFirstTouchTarget==null - if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) 这层if的意思是:当摁下ACTION_DWON时,就不去考虑之前有没有拦截过该事件,都具有拦截的资格(能够调用onInterceptTouchEvent),当当前事件时UP或者MOVE时,则需要考虑之前有没有拦截过事件,若没拦截就还有机会去拦截该事件,若之前拦截过了,则不再考虑再次调用onInterceptTouchEvent方法,本次后续的时间序列都拦截
- 当onInterceptTouchEvent不再考虑被调用时,那么走到else里边,
intercepted = true
,那么当这个intercepted为true时,看他注释:
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
我们主要看后一句,this view group continues to intercept touches
,该ViewGroup继续拦截触摸事件,意思就是后续的事件序列继续拦截。也就是说intercepted=true
表示当前事件序列拦截。
- 假设当前具备调用
OnInterceptTouchEvent
方法的资格时,还有一层判断:final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
,这层判断是啥意思呢?它产出一个值disallowIntercept
,用于判断:
ViewGroup#dispatchTouchEvent
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
- 这个标记位
FLAG_DISALLOW_INTERCEPT
是通过方法requestDisallowInterceptTouchEvent(boolean disallowIntercept)
来设置的,当requestDisallowInterceptTouchEvent(true)
时,if (!disallowIntercept)
为false,那么表示不拦截intercepted = false
,这也是这个方法名字的由来,”请求不拦截触摸事件”。但是,有时候我们子View调用这个方法会失效,为什么?因为,当面对事件ACTION_DOWN
的时候这个标记会被重置,也就是resetTouchState
这个方法,那么requestDisallowInterceptTouchEvent(boolean disallowIntercept)
这个方法岂不是很鸡肋?这里先TODO一下,等看到滑动冲突的时候再来看如何利用这个方法,源码:
重置FLAG_DISALLOW_INTERCEPT
为false
ViewGroup#dispatchTouchEvent
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
cancelAndClearTouchTargets(ev);
resetTouchState();
}
/**
* Resets all touch state in preparation for a new cycle.
*/
private void resetTouchState() {
clearTouchTargets();
resetCancelNextUpFlag(this);
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
mNestedScrollAxes = SCROLL_AXIS_NONE;
}
写FLAG_DISALLOW_INTERCEPT
ViewGroup#requestDisallowInterceptTouchEvent
@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
// We're already in this state, assume our ancestors are too
return;
}
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// Pass it up to our parent
if (mParent != null) {
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
ViewGroup—DispatchTouchEvent调用流程分析(分发到View)
- 遍历childCount,ViewGroup的所有子元素。
- 判断子元素是否能够接收到事件 a.坐标是否在区域内 b.是否在实行动画
这个判断是个||的关系,这俩个条件有一个不成立时,则忽略接下来的逻辑直接进入下一次子元素循环(continue)
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
- 若当前事件的坐标在区域内,并且没有在执行动画,那么就去调用dispatch
- formedTouchEvent(实际上就是调用的子元素的dispatchTouchEvent)注意这里很重要,这里的handled = child.dispatchTouchEvent(event);实际上就已经过渡到View的事件分发流程了,But,我们这里暂时不分析,下一节进行分析
/**
* Transforms a motion event into the coordinate space of a particular child view,
* filters out irrelevant pointer ids, and overrides its action if necessary.
* If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
*/
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);//实际上就是调用的子元素的dispatchTouchEvent
}
event.setAction(oldAction);
return handled;
}
......
return handled;
}
- 如果子元素的dispatchTouchEvent方法返回true,那么mFirstTouchTarget就会被赋值同时跳出(break)for循环,赋值过程真实发生在addTouchTarget, 注意这里很重要,mFirstTouchTarget的赋值直接影响事件拦截逻辑,并且ViewGroup向View的事件分发的过渡点也在这个mFirstTouchTarget
/**
* Adds a touch target for specified child to the beginning of the list.
* Assumes the target child is not already present.
*/
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
如果子元素的dispatchTouchEvent返回false,ViewGroup就会将事件分发给下一个子元素。(如果还有下一个子元素的话)
总结一波:
* 这个dispatchTransformedTouchEvent()
比想象的地位更重要,它作为ViewGroup向View事件过渡的一个点,无论是事件从ViewGroup正常分发到子元素Viewchild.dispatchTouchEvent()
,或者是ViewGroup自己处理事件,都要过渡到View,因为ViewGroup这个类并没有onTouchEvent
这个方法,并且根据代码显示,它也确实是调用的super.dispatchTouchEvent()
*最后附上ViewGroup的DispatchTouchEvnetn的代码整体概况,已做好注释:
ViewGroup#dispatchTouchEvent
if (!canceled && !intercepted) {
......
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.
......
final View[] children = mChildren;
//遍历childCount,ViewGroup的所有子元素
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;
}
//判断子元素是否能够接收到事件 a.坐标是否在区域内 b.是否在实行动画
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget = getTouchTarget(child);
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实际上就是调用的子元素的dispatchTouchEvent方法
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();
//如果子元素的dispatchTouchEvent方法返回true,那么mFirstTouchTarget就会被赋值同时跳出for循环,赋值过程真实发生在addTouchTarget
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
//如果子元素的dispatchTouchEvent返回false,ViewGroup就会将事件分发给下一个子元素。(如果还有下一个子元素的话)
// 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();
}
......
}
}
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
//No touch targets so treat this as an ordinary view.
//mFirstTouchTarget == null,表示上面那一波遍历并没有子View正常的处理了分发下去的事件
//a.包含俩种情况,ViewGroup没有子View
//b.子元素了处理了事件,但子View的DispatchTouchEvent返回了false(一般是由于子View的OnTouchEvent返回了false)
//可以看到这里第三个参数是null,当着参数为null的时候,该方法内回去调handled = super.dispatchTouchEvent(event);
//由于View是ViewGroup的父类,所以就转跳到了View的事件分发流程里去了
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.
......
target = next;
}
}
事件分发流程梳理3-View事件分发和处理流程
到这里,事件已经到达View这个类了,可能是父元素分发给子元素,也可能是父元素自己处理,总之,已经抵达到View这个类里边了。
View—DispatchTouchEvent
- View#onFilterTouchEventForSecurity
最外层的一个大判断。当为false时会放弃接下来所有的分发逻辑。那么这个判断是什么意思?
(event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0
是判断当前事件的窗体是否被遮盖(OBSCURED是遮盖的意思),这里!=0
意思就是真
,整条判断语句的意思就是,如果当前窗体被遮盖了则返回false,再看下边他那个注释True if the event should be dispatched, false if the event should be dropped.
该事件需要被分发,则返回true。该事件需要被丢弃,则返回false。最终我们知道onFilterTouchEventForSecurity
这个方法实际上是谷歌提供给我们的一个分发事件的安全过滤策略,判断事件窗体是否有在顶层(窗体遮盖与否),因为真实的场景是我们玩儿手机点点点,肯定都是点的最顶层的View。
/**
* Filter the touch event to apply security policies.
*
* 依据安全策略,过滤触摸事件。
* @param event The motion event to be filtered. 需要被过滤的触摸事件。
* @return True if the event should be dispatched, false if the event should be dropped.
* 该事件需要被分发,则返回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 is obscured, drop this touch.
return false;
}
return true;
}
另外值得一提的是,在ViewGroup的源碼内也进行了这个事件的过滤。
- OnTouchListener
判断是否有mOnTouchListener,如果有(非空),则调用它的onTouch。如果onTouch返回true,那么result = true
,这个result的值决定onTouchEvent()这个方法是否调用,这就说明了OnTouchListener.onTouch的优先级高于onTouchEvent
。
//noinspection SimplifiableIfStatement
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;
}
- View#onTouchEvent()在DISABLE时的事件处理情况
注释写的很明白,A disabled view that is clickable still consumes the touch events, it just doesn't respond to them.
当View处于DISABLED的时候同样会消耗事件,只是不响应罢了。
......
if ((viewFlags & ENABLED_MASK) == DISABLED) {
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;
}
- View#onTouchEvent()消费一个事件及OnclickListener的回调
1.首先看到LONG_CLICKABLE
和CLICKABLE
有一个为true都会让clickable为true,那么它就会消费这个事件,即onTouchEvent返回true,无论它是不是DISABLED。
2.在Up事件内,会触发performClick,如果View设置了onClickListener,那么会触发它的onClick方法.
......
//首先看到```LONG_CLICKABLE```和```CLICKABLE```有一个为true都会让clickable为true,
//那么它就会消费这个事件,即onTouchEvent返回true。
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
......
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
...
performClick()
...
break;
......
}
return true;
}
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);
notifyEnterOrExitForAutoFillIfNeeded(true);
return result;
}
总结
刚才我们梳理完了View的事件分发流程,这里来总结一波,从最初开始,我们从Activity
的dispatch调到了ViewGroup
的dispatch,然后从ViewGroup的dispatch调到View
的dispatch,当View
的dispatch返回时这时候实际上是返回到了ViewGroup
的dispatchTransformedTouchEvent,那么我们再来回顾一下ViewGroup的dispatchTransformedTouchEvent和dispatchTouchEvent
ViewGroup的dispatchTouchEvent
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
......
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
.....
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
.....
}
.....
return handled;
}
那么再追溯到Activity的dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
这就是事件从Activity一层层分发到View,然后又从View一层层冒泡返回到了Activity这里.下一篇准备分析下从Activity开始之前的事件分发,也就涉及到整个Android底层架构里的IMS系统,ok,本文就到这里,有什么问题请直接指出.