Android 事件分发机制---Down事件源码理解
来源:互联网 发布:猎上网怎么样知乎 编辑:程序博客网 时间:2024/05/18 02:00
背景:研究Android的事件分发机制是如何实现的。
一、事件分发用到的方法:
1、public boolean dispatchTouchEvent(MotionEvent ev): 用于TouchEvent事件的分发;
2、public boolean onInterceptTouchEvent(MotionEvent ev):事件拦截;
3、boolean onTouch(View v, MotionEvent event): 事件的回调;
4、public boolean onTouchEvent(MotionEvent event): 用来处理事件
二、点击事件分发流程:先基于MotionEvent.ACTION_DOWN来研究
1、首先,当一个手机屏幕上的点击事件产生后,事件会先传递给当前Activity的dispatchTouchEvent()方法
/**
* Called to process touch screen events. You can override this to
* intercept all touch screen events before they are dispatched to the
* window. Be sure to call this implementation for touch screen events
* that should be handled normally.
触摸屏幕事件的过程中被调用。可以通过复写该方法来拦截所有的触摸屏幕事件,在这些事件被传递给window之前。
为了这些被拦截的触摸屏幕事件能够被正常的处理,所以一定要调用该方法的实现方法。
*
* @param ev The touch screen event.
*
* @return boolean Return true if this event was consumed.
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();//-----001 与用户交互
}
if (getWindow().superDispatchTouchEvent(ev)) {//------002
return true;
}
return onTouchEvent(ev);
}
(001)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.
当一个key、touch、或者trackball事件被分发给Activity,该方法则会被调用。可以实现该方法,如果你想知道
用户与设备间以某种方式交互,当该Activity正在运行。这个回调和onUserLeaveHint()方法旨在帮助Activities
智能的管理状态栏通知;帮助Activity在适当的时间取消通知。
*
* <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.
所有Activity的onUserLeaveHint()回调方法的调用,都伴随着该方法的调用。这样做能够确保你的
Activity被告知相关的用户操作活动,例如拉下了通知栏面板并且点击一个item。
*
* <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.
注意,这个方法的回调只在触摸手势的Down操作,Move和Up操作不会被调用。
*
* @see #onUserLeaveHint()
*/
public void onUserInteraction() {
}
(002)getWindow().superDispatchTouchEvent(ev)源码解读:
1、getWindow():获取一个PhoneWindow的实现对象
/**
* 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.
PhoneWindow是该抽象类的唯一实现类
*/
public abstract class Window {}
2、查看PhoneWindow.superDispatchTouchEvent(ev)方法实现:调用了DecorView的superDispatchTouchEvent方法
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
3、查看DecorView方法实现:调用了super方法
public boolean superDispatchTrackballEvent(MotionEvent event) {
return super.dispatchTrackballEvent(event);
}
4、DecorView继承了FrameLayout,而FrameLayout继承了ViewGroup,最终调用了ViewGroup的dispatchTouchEvent方法。
总结:如果Activity复写了dispatchTouchEvent()方法,并且return true/false,则Activity自己消费了触摸事件,不再分发;
只有return super.diapatchTouchEvent()方法,才能使得Activity将事件进行自上而下的分发。
2、ViewGroup的dispatchTouchEvent()方法分段解析:
1.清空之前的指令:
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();//----每次的Down事件,都会将之前的mFirstTouchTarget值设置为null
}
2.校验是否拦截:
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
final booleandisallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;//-----003子控件是否设置拦截
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);//------004
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;
}
1.if语句进入的条件:Down事件、或者mFirstTouchTarget !=null,一般事件分发到ViewGroup时,mFirstTouchTarget==null;
2.boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;FLAG_DISALLOW_INTERCEPT是标记位,
一般是由ViewGroup的子控件通过复写requestDisallowInterceptTouchEvent(boolean)方法设置的。如果子控件设置true则为true,
如果没有设置则为false。
3.if语句则会执行onInterceptTouchEvent(),进行事件拦截;方法执行结果为intercepted = false
(003)boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;源码解读
1、ViewGroup实现了接口ViewParent
/**
* Called when a child does not want this parent and its ancestors to
* intercept touch events with
* {@link ViewGroup#onInterceptTouchEvent(MotionEvent)}.
当子控件不希望父控件及其祖父控件通过onInterceptTouchEvent()来拦截事件
*
* <p>This parent should pass this call onto its parents. This parent must obey
* this request for the duration of the touch (that is, only clear the flag
* after this parent has received an up or a cancel.</p>
父控件及其祖父空间必须遵守子控件的请求,知道调用了up、cancel时取消。
*
* @param disallowIntercept True if the child does not want the parent to
* intercept touch events.
*/
public voidrequestDisallowInterceptTouchEvent(boolean disallowIntercept);
小结:当子控件设置了requestDisallowInterceptTouchEvent(true),那么disallowIntercept=true,那么就不会进入if语句,也就不会执行onInterceptTouchEvent(ev);方法。如果子控件未设置requestDisallowInterceptTouchEvent(),那么disallowIntercept默认为false,则会执行onInterceptTouchEvent(ev);方法。
因此,dispatchTouchEvent()方法return super时默认会执行onInterceptTouchEvent()方法。
(004) onInterceptTouchEvent(ev);源码解读
/**
* Implement this method to intercept all touch screen motion events. This
* allows you to watch events as they are dispatched to your children, and
* take ownership of the current gesture at any point.
实现该方法可以拦截所有的触屏事件。
*
* <p>Using this function takes some care, as it has a fairly complicated
* interaction with {@link View#onTouchEvent(MotionEvent)
* View.onTouchEvent(MotionEvent)}, and using it requires implementing
* that method as well as this one in the correct way. Events will be
* received in the following order:
*
* <ol>
* <li> You will receive the down event here.
* <li> The down event will be handled either by a child of this view
* group, or given to your own onTouchEvent() method to handle; this means
* you should implement onTouchEvent() to return true, so you will
* continue to see the rest of the gesture (instead of looking for
* a parent view to handle it). Also, by returning true from
* onTouchEvent(), you will not receive any following
* events in onInterceptTouchEvent() and all touch processing must
* happen in onTouchEvent() like normal.
Down事件你可以交给自己的子类去处理,也可以交给自己的onTouchEvent()方法处理。当自己的onTouchEvent()方法return true时,
将会接受到接下来的move/up等事件,而不是交由父类来处理。同样的,onInterceptTouchEvent()方法将不会接收到move/up事件,
而是直接交给onTouchEvent()方法来处理。
* <li> For as long as you return false from this function, each following
* event (up to and including the final up) will be delivered first here
* and then to the target's onTouchEvent().
* <li> If you return true from here, you will not receive any
* following events: the target view will receive the same event but
* with the action {@link MotionEvent#ACTION_CANCEL}, and all further
* events will be delivered to your onTouchEvent() method and no longer
* appear here.
* </ol>
*
* @param ev The motion event being dispatched down the hierarchy.
* @return Return true to steal motion events from the children and have
* them dispatched to this ViewGroup through onTouchEvent().
* The current target will receive an ACTION_CANCEL event, and no further
* messages will be delivered here.
*/
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;
}
1.InputDevice.SOURCE_MOUSE:输入源是鼠标的定位设备;
2.MotionEvent.BUTTON_PRIMARY:主要按钮,鼠标左键;
3.isOnScrollbarThumb(ev.getX(), ev.getY()):在滚动滚动条;
默认该方法return false,不进行拦截。
小结:onInterceptTouchEvent()方法return true时,Down事件将被拦截后直接交给自己的onTouchEvent处理,onTouchEvent方法return true时,那么后续的move、up事件将由该级的ViewGroup直接分发给onTouchEvent方法处理,不再经过onInterceptTouchEvent方法。如果onTouchEvent()方法return false时,那么Down事件将会被向上传给父类的onTouchEvent()方法处理,那么后续的move、up事件将不会经过该级别的viewGroup的dispatchTouchEvent()方法啦。
2、结合源码分析,为什么子控件设置requestDisallowInterceptTouchEvent可以影响父控件的拦截事件
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
// We're already in this state, assume our ancestors are too
return;//--------A
}
if (disallowIntercept) {//-------B
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// Pass it up to our parent
if (mParent != null) {
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
1).假设(mGroupFlags & FLAG_DISALLOW_INTERCEPT)==0;
子控件设置了disallowIntercept = true拦截;则会执行到 B 代码处;
mGroupFlags |= FLAG_DISALLOW_INTERCEP ==> mGroupFlags = mGroupFlags|FLAG_DISALLOW_INTERCEP;在判断当前父控件
是否还有父控件,有的话则一直请求父控件放行除了Down事件。
再看:父控件执行dispatchTouchEvent()方法中的boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
此时的disallowIntercept = (mGroupFlags | FLAG_DISALLOW_INTERCEP & FLAG_DISALLOW_INTERCEPT)!=0;
最终简化成判断 disallowIntercept = FDI !=0 为true。
2).假设(mGroupFlags & FLAG_DISALLOW_INTERCEPT)!=0;
子控件设置了disallowIntercept = true拦截;则会执行到 A代码处;则直接返回,表明父控件及其祖父控件disallowIntercept值为true。
所以,最终的disallowIntercept值仍为 true.
3、再看父控件的dispatchTouchEvent()方法:
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;//--------C
}
由于子控件设置了requestDisallowInterceptTouchEvent(true),则disallowIntercept = true;
通过if语句执行后到 C代码处,intercepted = false;表明父控件不拦截,不调用onInterceptTouchEvent()方法。
2、事件分发
if (!canceled && !intercepted) {
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
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;
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {//----------A 倒序遍历
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)) {//---------D
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {//--------B
// 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();
newTouchTarget = addTouchTarget(child, idBitsToAssign);//-------C
alreadyDispatchedToNewTouchTarget = true;//记录Down事件被消费了
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;
}
}
}
1.A 代码处的for循环,是对所有的子View进行遍历,是从最上层到内层的遍历,自上而下的遍历过程,默认是不拦截事件的;
2.B 处代码是将事件分发给子类处理,dispatchTransformedTouchEvent()方法过长,可用如下伪代码讲解:也就是传入的child是否为null;
有子View,就由子View再进行事件分发;如果没有子View,在调用父类的事件分发方法。这里也只有包含触摸点的子View才能达到。
举例:如果一个ViewGroup下有View1和View2.当在View2上点击是,只有View2才能继续Down事件分发,View1会在D处被直接过滤掉。
if (child == null) {
handled = super.dispatchTouchEvent(event);//------006
} else {
handled = child.dispatchTouchEvent(event);//------005
}
return handled;
(005、006)handled = child.dispatchTouchEvent(event);代码讲解
下面对View.dispatchTouchEvent()代码进行缩减处理:
public boolean dispatchTouchEvent(MotionEvent event) {
boolean result = false;
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) { //-----1
result = true;
}
if (!result && onTouchEvent(event)) {//-----2
result = true;
}
return result;
}
包含触摸点的ViewGroup,会执行dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)方法,也就是上面的 B 处代码,会将Down事件交由子View处理,如果子View中复写了dispatchTouchEvent()方法,return true表明Down事件由自己消费,那么上面就会执行C处代码,找到了TargetView;return false则会继续执行for循环;return super的话,则会执行View.dispatchTouchEvent()方法,也就是上面的伪代码。可以看见在1、2处,如果在最底层的子View中设置了onTouchListen.onTouch()方法,并且返回值为true时,则不会执行onTouchEvent()方法,并且dispatchTouchEvent()方法返回true。
小结:如果包含触摸点的最底层View,retrun true自己消费Down事件,不会执行onTouchEvent()方法;return false也不会执行onTouchEvent()方法,并交给ViewGroup处理,也就是上面 B处代码;retun super时如果有设置onTouchListener.onTouch()方法,那么会优先执行OnTouch()方法,并且其返回值会影响是否执行onTouchEvent()方法。
子View在处理onTouchEvent()事件时,如果子View是Clickable默认为true的话,那么及时不做处理,也会返回true。也就是说,最底层的控件如果继承TextView的话,
那么默认的执行onTouchEvent()返回true,如果继承View的话,则默认返回false。
onTouchEvent()源码:
if (((viewFlags & CLICKABLE) == CLICKABLE ||(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
return true;
}
return false;
3.如果B 处代码返回false,则表示子View未进行消费,那么则继续for遍历其他子View直到结束。mFirstTouchTarget和newTouchTarget都为null。
如果返回true,那么表示有子View对Down事件消费了,到C代码;
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
4.C 处代码,主要是将mFirstTouchTarget指向了目标View。同时将newTouchTarget也指向了目标View,然后跳出了for循环。
5.D 处代码,用来判断ViewGroup下的所有子View,是否包含触摸点位置,如果没有则continue下一个View。也就是如果ViewGroup下的子View不包含触摸点,那么直接跳过,不交由它处理下发的事件。最终实现的,其实就是从上至下的一条直达目标的捷径路线,不会有多余的操作。
小结:事件分发总是从上而下的,默认是不拦截的;如果ViewGroup下还有多个ViewGroup,每个二级ViewGroup下有多个子View,那么此时事件分发:有顶级ViewGroup分发给二级ViewGroup,判断每一个二级ViewGroup是否包含触摸点,不包含直接跳过;直接找到包含触摸点的二级ViewGroup;同样的再下发给三级View的时候,也是只有包含触摸点的子View才有
机会继续下发Down事件,其它子View会被直接continue过滤掉。这是由 D 处代码带来的效果。
3、事件分发分段代码
Down事件继续解析:
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);//---------A 执行到这,表明没有子View消费
} else {
// Dispatch to touch targets, excluding the new touch target if we already
// dispatched to it. Cancel touch targets if necessary.
执行到这,表明有子View消费了Down事件
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {//------B
handled = true;
} else {
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;
}
}
return handled;
1.没有消费:当最低层的ViewGroup,将Down事件分发给包含触摸点的子View,并且子View没有消费Down事件,那么就会执行到 A 处代码。A 处代码里面,也就是上文的006处的代码,因为这个时候child传入的是null;就会执行super.dispatchTouchEvent()方法,child的super其实也就是自己,也就是上面的005、006处伪代码,也就会执行自己的onTouch()、onTouchEvent()方法。最底层的ViewGroup如果设置了onTouchListener.onTouch()结果也会影响onTouchEvent()方法;如果onTouchEvent() return true则自己消费;return false则继续交由上一层ViewGroup处理;return super方法,那么也会父类的dispatchTouchEvent()方法。也就是向上执行onTouchEvent()方法。
2、被消费了:那么就会执行到B处,handled =true,最后就会将true返回。
总结:
完整的Down事件分发流程:
Activity将Down事件,return super最终交给顶层的ViewGroup处理,顶层ViewGroup通过dispatchTouchEvent()方法,遍历查找到包含触摸点的最底层ViewGroup、及View,将Down事件一级级下发。
如果有某一级的ViewGroup的dispatchTouchEvent()方法return true不再向下分发,则由这一级ViewGroup消费了;return false不再向下分发,交由上一级ViewGroup执行onTouchEvent()方法;return super方法才会继续向下分发。
最底层的View在执行dispatchTouchEvent()方法时,return true则自己消费,return false则交由上一级ViewGroup处理,这2中情况都不会执行自己的onTouchEvent()方法;return super时才会执行自己的onTouchEvent()方法,如果结果还是自己不消费,那么则会向上逐级的调用ViewGroup的onTouchEvent()方法。
最终又会交由Activity的onTouchEvent()处理。向上逐级执行onTouchEvent()时,只要某一级return true,那么Down事件也就停止向上执行了。
借用http://www.jianshu.com/p/e99b5e8bd67b一张图总结下。
阅读全文
0 0
- Android 事件分发机制---Down事件源码理解
- Android 事件分发机制---move、up事件源码理解
- 深入源码理解Android Touch事件分发机制(上篇)
- 深入源码理解Android Touch事件分发机制(下篇)
- 理解Android中的TouchEvent事件分发机制
- android 快速理解事件分发机制
- 关于Android事件分发机制的理解
- 深入理解Android事件分发机制
- Android中事件分发机制理解
- Android 事件分发机制 理解杂谈
- Android事件分发机制------------>验证+理解
- 深入理解Android事件分发机制
- Android事件分发机制的理解
- 一步步理解Android事件分发机制
- Android View事件分发机制理解
- Android事件分发机制简单理解
- android 事件分发机制 概念理解
- 完全理解android事件分发机制
- Android 源码编译——以及遇到的问题记录
- [转]VS2015编译ForestDB
- 命令行工具tshark使用小记
- 最新城市码(txt文件转xml)
- Hibernate3原生SQL查询返回自定义类型时Integer类型的转换问题
- Android 事件分发机制---Down事件源码理解
- git commit -m 与 git commit -am 的区别
- 安全编程
- sql
- Curator典型使用场景之事件监听。
- BDTC 2017丨探索大数据在医疗行业的应用实践
- caffe的一些经典网络的实现
- php+ajax+jquery 实现无刷新分页以及js缓存
- Android 事件分发机制---move、up事件源码理解