android 事件分发机制源码解析

来源:互联网 发布:物理补课软件 编辑:程序博客网 时间:2024/05/14 18:45
首先我重新写了这了几个控件类,只是加了点打印日志,来观察里面的事件分发机制.

然后写了个布局,如图.



MainActivity有dispatchTouchEvent, onTouchEvent方法

MyRelativieLayout有dispatchTouchEvent, onInterceptTouchEvent, onTouchEvent方法

TextView和Button有dispatchTouchEvent, onTouchEvent方法.

这里拿MyRelativeLayout1的代码来举例,如下

    @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        Log.e(TAG, "MyRelativeLayout1 dispatchTouchEvent begin >> "                + (ev.getAction() == MotionEvent.ACTION_DOWN ? "down" : ev.getAction() == MotionEvent.ACTION_UP ? "up" : ev.getAction() == MotionEvent.ACTION_MOVE ? "move" : "other"));        boolean flag = super.dispatchTouchEvent(ev);        Log.e(TAG, "MyRelativeLayout1 dispatchTouchEvent end " + flag + " >> "                + (ev.getAction() == MotionEvent.ACTION_DOWN ? "down" : ev.getAction() == MotionEvent.ACTION_UP ? "up" : ev.getAction() == MotionEvent.ACTION_MOVE ? "move" : "other"));        return flag;    }    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        Log.e(TAG, "MyRelativeLayout1 onInterceptTouchEvent "                + (ev.getAction() == MotionEvent.ACTION_DOWN ? "down" : ev.getAction() == MotionEvent.ACTION_UP ? "up" : ev.getAction() == MotionEvent.ACTION_MOVE ? "move" : "other"));        boolean flag = super.onInterceptTouchEvent(ev);        Log.e(TAG, "MyRelativeLayout1 onInterceptTouchEvent end " + flag + " >> "                + (ev.getAction() == MotionEvent.ACTION_DOWN ? "down" : ev.getAction() == MotionEvent.ACTION_UP ? "up" : ev.getAction() == MotionEvent.ACTION_MOVE ? "move" : "other"));        return flag;    }    @Override    public boolean onTouchEvent(MotionEvent ev) {        Log.e(TAG, "MyRelativeLayout1 onTouchEvent "                + (ev.getAction() == MotionEvent.ACTION_DOWN ? "down" : ev.getAction() == MotionEvent.ACTION_UP ? "up" : ev.getAction() == MotionEvent.ACTION_MOVE ? "move" : "other"));        boolean flag = super.onTouchEvent(ev);        Log.e(TAG, "MyRelativeLayout1 onTouchEvent end " + flag + " >> "                + (ev.getAction() == MotionEvent.ACTION_DOWN ? "down" : ev.getAction() == MotionEvent.ACTION_UP ? "up" : ev.getAction() == MotionEvent.ACTION_MOVE ? "move" : "other"));        return flag;    }


然后点击MyRelativeLayout2区域时打印出

MainActivity: MainActivity dispatchTouchEvent begin >> down
MyRelativeLayout1: MyRelativeLayout1 dispatchTouchEvent begin >> down
MyRelativeLayout1: MyRelativeLayout1 onInterceptTouchEvent begin >> down
MyRelativeLayout1: MyRelativeLayout1 onInterceptTouchEvent end false >> down
MyRelativeLayout2: MyRelativeLayout2 dispatchTouchEvent begin >> down
MyRelativeLayout2: MyRelativeLayout2 onInterceptTouchEvent begin >> down
MyRelativeLayout2: MyRelativeLayout2 onInterceptTouchEvent end false >> down
MyRelativeLayout2: MyRelativeLayout2 onTouchEvent begin >> down
MyRelativeLayout2: MyRelativeLayout2 onTouchEvent end false >> down
MyRelativeLayout2: MyRelativeLayout2 dispatchTouchEvent end false >> down
MyRelativeLayout1: MyRelativeLayout1 onTouchEvent begin >> down
MyRelativeLayout1: MyRelativeLayout1 onTouchEvent end false >> down
MyRelativeLayout1: MyRelativeLayout1 dispatchTouchEvent end false >> down
MainActivity: MainActivity onTouchEvent begin >> down
MainActivity: MainActivity onTouchEvent end false >> down
MainActivity: MainActivity dispatchTouchEvent end false >> down
MainActivity: MainActivity dispatchTouchEvent begin >> up
MainActivity: MainActivity onTouchEvent begin >> up
MainActivity: MainActivity onTouchEvent end false >> up
MainActivity: MainActivity dispatchTouchEvent end false >> up

这里能看到. down事件走了这么多步,up事件才走了4步.先来张图展示一下流程.


为什么ACTION_UP的流程才走了这么一点?下面看源码回答


点击左边的MyTextView

MainActivity: MainActivity dispatchTouchEvent begin >> down
MyRelativeLayout1: MyRelativeLayout1 dispatchTouchEvent begin >> down
MyRelativeLayout1: MyRelativeLayout1 onInterceptTouchEvent begin >> down
MyRelativeLayout1: MyRelativeLayout1 onInterceptTouchEvent end false >> down
MyTextView: MyTextView dispatchTouchEvent begin >> down
MyTextView: MyTextView onTouchEvent begin >> down
MyTextView: MyTextView onTouchEvent end false >> down
MyTextView: MyTextView dispatchTouchEvent end false >> down
MyRelativeLayout1: MyRelativeLayout1 onTouchEvent begin >> down
MyRelativeLayout1: MyRelativeLayout1 onTouchEvent end false >> down
MyRelativeLayout1: MyRelativeLayout1 dispatchTouchEvent end false >> down
MainActivity: MainActivity onTouchEvent begin >> down
MainActivity: MainActivity onTouchEvent end false >> down
MainActivity: MainActivity dispatchTouchEvent end false >> down
MainActivity: MainActivity dispatchTouchEvent begin >> up
MainActivity: MainActivity onTouchEvent begin >> up
MainActivity: MainActivity onTouchEvent end false >> up
MainActivity: MainActivity dispatchTouchEvent end false >> up

图和上面的差不多,差别是TextView不是ViewGroup.没有onInterceptTouchEvent事件




点击右边的MyButton

MainActivity: MainActivity dispatchTouchEvent begin >> down
MyRelativeLayout1: MyRelativeLayout1 dispatchTouchEvent begin >> down
MyRelativeLayout1: MyRelativeLayout1 onInterceptTouchEvent begin >> down
MyRelativeLayout1: MyRelativeLayout1 onInterceptTouchEvent end false >> down
MyButton: MyButton dispatchTouchEvent begin >> down
MyButton: MyButton onTouchEvent begin >> down
MyButton: MyButton onTouchEvent end true >> down
MyButton: MyButton dispatchTouchEvent end true >> down
MyRelativeLayout1: MyRelativeLayout1 dispatchTouchEvent end true >> down
MainActivity: MainActivity dispatchTouchEvent end true >> down
MainActivity: MainActivity dispatchTouchEvent begin >> up
MyRelativeLayout1: MyRelativeLayout1 dispatchTouchEvent begin >> up
MyRelativeLayout1: MyRelativeLayout1 onInterceptTouchEvent begin >> up
MyRelativeLayout1: MyRelativeLayout1 onInterceptTouchEvent end false >> up
MyButton: MyButton dispatchTouchEvent begin >> up
MyButton: MyButton onTouchEvent begin >> up
MyButton: MyButton onTouchEvent end true >> up
MyButton: MyButton dispatchTouchEvent end true >> up
MyRelativeLayout1: MyRelativeLayout1 dispatchTouchEvent end true >> up
MainActivity: MainActivity dispatchTouchEvent end true >> up


当点击了Button这种clickable控件,就会在onTouchEvent中返回true.

之后的代码其实用处就不大了,因为返回true后会绕过后面MyRelativeLayout1和Activity的onTouchEvent.

意思是拦截了当前事件的继续派发,自己处理和消化.

所以down流程走完了,up流程会跟down流程走同样的方法路径.






看了上面log和图,接着来分析下源码.(源码基于android-23)


先看Activity的dispatchTouchEvent方法

    public boolean dispatchTouchEvent(MotionEvent ev) {        if (ev.getAction() == MotionEvent.ACTION_DOWN) {            onUserInteraction();        }        if (getWindow().superDispatchTouchEvent(ev)) {            return true;        }        return onTouchEvent(ev);    }
很简单.getWindow().superDispatchTouchEvent(ev)意思就是调用子View的dispatchTouchEvent方法.

如果子View没有拦截返回false,就执行Activity的onTouchEvent方法.拦截了就不执行.

getWindow().superDispatchTouchEvent(ev)会把事件传给第一个View——DecorView



然后来看看看ViewGroup的dispatchTouchEvent.几乎所有原生的安卓布局都没有重写这个方法,而是用的他们父类ViewGroup的.

整个diapatchTouchEvent太长了,以下截取部分重要代码...

public boolean dispatchTouchEvent(MotionEvent ev) {final boolean intercepted;//这是2103行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;}//这是2117行if (!canceled && !intercepted) {//这是2133行...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 indexfor (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);alreadyDispatchedToNewTouchTarget = true;break;}...}//这是2235行}



点击非clickable控件流程

1.down流程

如果是点击的非clickable控件,那么down事件会执行intercepted = onInterceptTouchEvent(ev);

intercepted人如其名,表示是否拦截掉事件交给,交给自己的onTouchEvent处理. 不拦截就交给子View处理.

现在讨论的是正常流程,所以默认布局的onInterceptTouchEvent会返回false, intercepted 为false.

然后2133行的 !canceled && !intercepted 判断为真(canceled不在本文讨论范围)

然后会执行里面最重要的依据dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign), 这个就是把事件派发给子View,交由子View处理的方法.

假设子View都没有处理,就会返回false.跳过了里面的逻辑.

2.up流程

up事件因为actionMasked不等于ACTION_DOWN.所以跑了else的部分,使的intercepted = true;

就不会跑2133那里的if代码段,也就不会派发事件给子View.

所以就解释了上面的问题"为什么ACTION_UP的流程才走了这么一点?"

解释一下设计原理就是,我down事件都派发过给子View了,但是儿子们都没处理,所以后续的up事件(包括中间如果有的move事件)都不会再派发到子类了.

省时省力!

所以小总结一句,当1.自己重写onInterceptTouchEvent拦截,或者2.不是down事件并且子View又没一个给力的能兜住事件, 这两种情况就会自己消费了.


点击clickable控件流程

1.down流程

如果是点击的是clickable控件,流程像上面那样,走到了dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)

然后clickable控件返回了true.执行了里面的addTouchTarget(child, idBitsToAssign)

这个方法大意就是,如果子View有clickable控件,就让当前这个View(父View)持有这个TouchTarget.

从MyRelativelayout1执行这个持有方法,持有MyButton

到FrameLayout执行这个持有方法,持有MyRelativelayout1

到LinearLayout持有FrameLayout

到DecorView持有LinearLayout

一步步赋值(持有)上去.

所有ViewGroup都会有一个变量mFirstTouchTarget, 它是存放该ViewGroup中能消费事件的子View(既可clickable的控件). 好根据它来用来判断后面的流程要怎么走.

ps:明明父布局就是MyRelativeLayout1,为什么会有多出来这么多其他控件.因为手机本质是这样的布局.给个图但是不展开讲了

2.up流程

然后在up事件分发的时候,就在mFirstTouchTarget != null判断为真,执行给intercepted赋值为假的逻辑.

然后在2133代码判断后,up流程就会继续派发事件给子View.

以此达到down,move,up所有事件都最终交给子View处理.

当时研究我想,那么问题来了,如果知道是Button的onTouch事件消费了,能不能下次直接从MainActivity直接绕过中间没用的MyRelativeLayout1等等这些没用的东西到达Button呢.

从打印信息看来,目前还没这么智能.




以上是正常流程..

下面讲讲根据自己的需要改写流程的情况.


以下情景是点击右边的MyButton

1.把dispatchTouchEvent改写成直接返回false

    @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        Log.e(TAG, "MyRelativeLayout1.java - dispatchTouchEvent() ---------- return false");        return false;    }

就会打印log
MainActivity: MainActivity dispatchTouchEvent begin >> down
MyRelativeLayout1: MyRelativeLayout1.java - dispatchTouchEvent() ---------- return false
MainActivity: MainActivity onTouchEvent begin >> down
MainActivity: MainActivity onTouchEvent end false >> down
MainActivity: MainActivity dispatchTouchEvent end false >> down
MainActivity: MainActivity dispatchTouchEvent begin >> up
MainActivity: MainActivity onTouchEvent begin >> up
MainActivity: MainActivity onTouchEvent end false >> upMainActivity: MainActivity dispatchTouchEvent end false >> up
意思是不会交给子View处理,自己也不处理.
返回false也不拦截,所以就会把事件处理交给Activity的onTouchEvent.


2.把dispatchTouchEvent改成返回true

    @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        Log.e(TAG, "MyRelativeLayout1.java - dispatchTouchEvent() ---------- return true");        return true;    }
就会打印log
MainActivity: MainActivity dispatchTouchEvent begin >> down
MyRelativeLayout1: MyRelativeLayout1.java - dispatchTouchEvent() ---------- return true
MainActivity: MainActivity dispatchTouchEvent end true >> down
MainActivity: MainActivity dispatchTouchEvent begin >> up
MyRelativeLayout1: MyRelativeLayout1.java - dispatchTouchEvent() ---------- return trueMainActivity: MainActivity dispatchTouchEvent end true >> up
这样改写就把事件留住,不再分发子View,然后可以把业务逻辑写在这个方法里面
但是这样不规范,一般想要实现这种效果,都是用下面的方法.



3.在onInterceptTouchEvent中返回true, 在onTouchEvent也返回true,并在其中写处理逻辑

    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        Log.e(TAG, "MyRelativeLayout1 onInterceptTouchEvent >> "                + (ev.getAction() == MotionEvent.ACTION_DOWN ? "down" : ev.getAction() == MotionEvent.ACTION_UP ? "up" : ev.getAction() == MotionEvent.ACTION_MOVE ? "move" : "other"));        return true;    }    @Override    public boolean onTouchEvent(MotionEvent ev) {        Log.e(TAG, "MyRelativeLayout1 onTouchEvent >> "                + (ev.getAction() == MotionEvent.ACTION_DOWN ? "down" : ev.getAction() == MotionEvent.ACTION_UP ? "up" : ev.getAction() == MotionEvent.ACTION_MOVE ? "move" : "other"));        Log.e(TAG, "MyRelativeLayout1.java - onTouchEvent() ---------- 写处理逻辑");        return true;    }

MainActivity: MainActivity dispatchTouchEvent begin >> down
MyRelativeLayout1: MyRelativeLayout1 dispatchTouchEvent begin >> down
MyRelativeLayout1: MyRelativeLayout1 onInterceptTouchEvent >> down
MyRelativeLayout1: MyRelativeLayout1 onTouchEvent >> down
MyRelativeLayout1: MyRelativeLayout1.java - onTouchEvent() ---------- 写处理逻辑
MyRelativeLayout1: MyRelativeLayout1 dispatchTouchEvent end true >> down
MainActivity: MainActivity dispatchTouchEvent end true >> down
MainActivity: MainActivity dispatchTouchEvent begin >> up
MyRelativeLayout1: MyRelativeLayout1 dispatchTouchEvent begin >> up
MyRelativeLayout1: MyRelativeLayout1 onTouchEvent >> up
MyRelativeLayout1: MyRelativeLayout1.java - onTouchEvent() ---------- 写处理逻辑
MyRelativeLayout1: MyRelativeLayout1 dispatchTouchEvent end true >> up
MainActivity: MainActivity dispatchTouchEvent end true >> up


这是重写事件处理中最常用的方法了.
onInterceptTouchEvent中返回true使得不会再把事件派发到子View,然后回转到onTouchEvent执行自己的业务代码
使用场景经常是一个布局文件里面有很多子View,但是却不想分发自己处理,就会用这种写法.


稍微讲解一下这个流程的源码

public boolean dispatchTouchEvent(MotionEvent ev) {final boolean intercepted;//这是2103行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;}//这是2117行if (!canceled && !intercepted) {//这是2133行...newTouchTarget = addTouchTarget(child, idBitsToAssign);//这个方法会给成员变量mFirstTouchTarget赋值...}//这是2235行if (mFirstTouchTarget == null) {//这是2238行// No touch targets so treat this as an ordinary view.handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);} else {...}}private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,//这是2511行            View child, int desiredPointerIdBits) {...handled = super.dispatchTouchEvent(event);//这是2547...}//这是2581行

前面讲过,onInterceptTouchEvent如果返回true,会给变量intercepted赋值.

接着在2133行判断中为假,跳过里面派发给子View的代码.

没子View什么事,这时mFirstTouchTarget也会为null

然后在经过2238的判断里,进入dispatchTransformedTouchEvent方法

这个方法里面又会调用到父类,既View的dispatchTouchEvent方法,

public boolean dispatchTouchEvent(MotionEvent event) {...boolean result = false;...            if (!result && onTouchEvent(event)) {//9294行                result = true;            }//9296行...return result;}

里面就会执行熟悉的onTouchEvent方法.

我曾经想着事件都留到这了,这方法应该不需要返回true.事实

事实是假设返回false还是会让事件最终又回到MainActivity的onTouchEvent方法里.

毕竟全部方法都遵循返回false表示不拦截,事件继续往下一层流转.返回true表示在这里处理,不分发.



关于setOnTouchListener和setOnClickListener是在什么时候调用?他们的优先级是怎么样?

public boolean dispatchTouchEvent(MotionEvent event) {...boolean result = false;...if (onFilterTouchEventForSecurity(event)) {//noinspection SimplifiableIfStatementListenerInfo 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类里面有这么一个判断。

li.mOnTouchListener!=null && 中间省略 && li.mOnTouchListener.onTouch(this, event)

所以假设我们给某个控件执行了setOnTouchListener方法,就会在这里执行onTouch方法。

并且重要的一点是,如果onTouch方法中返回true。result置为true,就不会进入onTouchEvent方法了。

ps:如果是ViewGroup,则是在dispatchTransformedTouchEvent中会调用super.onDispatchTouchEvent,同样会进入到这个流程。



public boolean onTouchEvent(MotionEvent event) {...if (mTouchDelegate != null) {if (mTouchDelegate.onTouchEvent(event)) {return true;}}if (((viewFlags & CLICKABLE) == CLICKABLE ||(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {switch (action) {case MotionEvent.ACTION_UP:...performClick();...break;case MotionEvent.ACTION_DOWN:...break;case MotionEvent.ACTION_CANCEL:...break;case MotionEvent.ACTION_MOVE:...break;}return true;}return false;}
在View的onTouchEvent中,里面会判断这个View是不是CLICKABLE和LONG_CLICKABLE。Button这种默认CLICKABLE,TextView默认不是CLICKABLE。所有的View都不是LONG_CLICKABLE的。

然后我们可以通过xml属性或者java代码,或者更直接的,一旦调用了setOnClickListener或setOnLongClickListener方法,就会把对应的这两个CLICKABLE和LONG_CLICKABLE设为true。

进入这个判断里面,里面会根据各种传来MotinEvent事件进行判断,如果符合判断就会调用performClick方法,里面有执行mOnClickListener.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;}



ps:无意中发现有一个View的onTouch方法中有一个TouchDelegate,人如其名,是用来给View设置一个点击代理的。代理返回true的话,就会跳过下面的逻辑


所以得出OnTouchListener的优先级是高于onTouchEvent方法的,自然也就高于onClick,onLongClick这些方法。

对于View.onTouchEvent,如果自己重写这个方法,那么给他们设置的onClickListener和onLongClickListener监听单击和长按事件就作废了。

想要两者兼得,请根据业务逻辑好好构思怎么写。




ps:

View中有一个很重要的方法requestDisallowInterceptTouchEvent,可以在子控件中控制父控件是否拦截。一般用法如下

if (getCurrentItem() != 0) {getParent().requestDisallowInterceptTouchEvent(true);// 用getParent去请求,// 不拦截} else {// 如果是第一个页面,需要显示侧边栏, 请求父控件拦截getParent().requestDisallowInterceptTouchEvent(false);// 拦截}





0 0
原创粉丝点击