1000字范文,内容丰富有趣,学习的好帮手!
1000字范文 > Android-事件分发机制(源码层面)

Android-事件分发机制(源码层面)

时间:2022-07-23 22:22:10

相关推荐

Android-事件分发机制(源码层面)

事件分发的流程:

Activity->window->view

例1:当我们对控件的onClick和onTouch事件进行注册时,点击控件两个方法都执行,且onTouch优先于onClick执行。当onTouch返回值为true时,表示这个触摸事件被onTouch消费了,此时不会再继续向下传递,onClick不执行。

1. 当触摸到控件,一定调用该控件View的dispatchTouchEvent():

public boolean dispatchTouchEvent(MotionEvent event) {if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&mOnTouchListener.onTouch(this, event)) {return true;}return onTouchEvent(event);

if 判断如果三个条件都为真,则返回true,否则执行onTouchEvent(event)。

条件为:

注册了监听器:mOnTouchListener 监听器不为空,当注册了touch事件监听器就不为空。

控件enable可用: 判断当前控件是否是可用的enable,按钮默认true。

onTouch()返回true:回调控件注册touch事件时的onTouch()方法,得到返回值。

所以dispatchTouchEvent() 首先执行onTouch(),如果注册touch的事件的onTouch()返回true,此时三个条件都满足,方法返回true,不向下执行,所以onClick()不执行。

2. 当三个条件不满足时,调用onTouchEvent()

public boolean onTouchEvent(MotionEvent event) {...//注2:如果该控件是可以点击的 ---> switch判断事件if (((viewFlags & CLICKABLE) == CLICKABLE ||(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {switch (event.getAction()) {//抬起手指case MotionEvent.ACTION_UP:...//经过层层判断if (!post(mPerformClick)) {//调用performClick()performClick();}...break;}return true;}return false;}

onTouchEvent()中判断该控件是否可点击,如果可点击判断是ACTION_UP抬起事件,调用performClick()。

public boolean performClick() {...if (mOnClickListener != null) {...mOnClickListener.onClick(this);//注1return true;}return false;}

只要mOnClickListener不为空,调用它的onClick()。

mOnClickListener在setOnListener时就赋值了,每当控件被点击,就会回调被点击控件的onClick()。

public void setOnClickListener(OnClickListener l) {...mOnClickListener = l;}

3.讲讲touch事件的层级传递

当点击一个按钮,按道理会执行多个事件,ACTION_DOWN、ACTION_UP。

在dispatchTouchEvent的事件分发机制中,只有前一个事件返回true,下一个事件才会执行。也就是如果按下按钮时,ACTION_DOWN返回false,后面一系列的action都不执行。

看performClick()注1位置,虽然点击事件在onTouch()返回false,但是在onTouchEvent()中会返回true。 所以dispatchTouchEvent()返回值还是true,action继续传递,而返回false时action会停止传递。

当换一个控件,比如imageView,给它注册一个touch触摸事件,控件默认不可点击的,因此在onTouchEvent的第14行判断时无法进入到if的内部,直接跳到第91行返回了false,也就导致后面其它的action都无法执行了。

***注:

只有dispatchTouchEvent(MotionEvent event)一直返回true,后续的事件才能执行。

让dispatchTouchEvent(MotionEvent event)返回ture有两种方式,第一:onTouch(this, event)返回true(这个时候不这行onTouchevent)。第二:如果onTouch为false,则只能通过onTouchEvent(event)返回true。

4.最后总结,回答三个问题

1)onTouch和onTouchEvent有什么区别,又该如何使用?

onTouch()优先执行,当onTouch()返回true()时表示消费事件,onTouchEvent()不执行。

onTouch()需要执行有两个前提条件:mOnTouchListener不能为空,控件是可点击enable的。非enable的onTouch事件永远不执行。

如果要监听非enable控件的touch事件,需要重写onTouchEvent()。

2)为什么给ListView引入了一个滑动菜单的功能,ListView就不能滚动了?

滑动菜单的功能是通过给ListView注册了一个touch事件来实现的。如果你在onTouch方法里处理完了滑动逻辑后返回true,那么ListView本身的滚动事件就被屏蔽了,自然也就无法滑动(原理同前面例子中按钮不能点击),因此解决办法就是在onTouch方法里返回false。

3)为什么图片轮播器里的图片使用Button而不用ImageView?

因为Button是可点击的,而ImageView是不可点击的。如果想要使用ImageView,可以有两种改法。

第一,在ImageView的onTouch方法里返回true,这样可以保证ACTION_DOWN之后的其它action都能得到执行,才能实现图片滚动的效果。

第二,在布局文件里面给ImageView增加一个android:clickable="true"的属性,这样ImageView变成可点击的之后,即使在onTouch里返回了false,ACTION_DOWN之后的其它action也是可以得到执行的。

***注:

button和imageview的区别是onTouchEvent中的注2部分的判断,

button可以进入这个判断,而imageview不能进入,只要进入这个判断,那么就是一定返回true,而不进入这个判断一定返回false。所以button的dispatchTouchEvent一直为true,一直可以接受到后续事件,然后在onTouch中处理,而imageview一旦onTouch中返回false,只能寄希望于上述判断,也就是将其设置为可点击,才能返回true,接受后续事件。

5. ViewGroup的事件分发流程

例2:当给父控件注册touch触摸监听,给子控件注册click点击监听,点击子控件,只会执行子控件onClick,不会执行父控件onTouch。

点击空白区域会执行父控件onTouch。

为啥呢?

ViewGroup中的onIntercepterTouchEvent()拦截触摸事件的方法,默认返回false不拦截事件:

public boolean onInterceptTouchEvent(MotionEvent ev) {return false;}

当我们在父控件的此方法返回true,表示拦截事件后,点击子控件和空白区域都只执行父控件的onTouch。不管你点击哪里,永远都只会触发父控件的touch事件了,子控件的点击事件完全被屏蔽掉了。

Android中touch事件的传递,是先传递到ViewGroup,再传递到View的。

上面说过,只要你触摸控件,就一定会调用该控件的dispatchTouchEvent方法。这个说法没错,只不过还不完整而已。

实际情况是,当你点击了某个控件,首先会去调用该控件所在布局的dispatchTouchEvent方法,然后在布局的dispatchTouchEvent方法中找到被点击的相应控件,再去调用该控件的dispatchTouchEvent方法。

如果我们点击了自定义的LinearLayout控件MyLayout中的按钮,会先去调用MyLayout的dispatchTouchEvent方法,可是你会发现MyLayout中并没有这个方法。那就再到它的父类LinearLayout中找一找,发现也没有这个方法。那只好继续再找LinearLayout的父类ViewGroup,你终于在ViewGroup中看到了这个方法,按钮的dispatchTouchEvent方法就是在这里调用的。

ViewGroup中的dispatchTouchEvent方法:

public boolean dispatchTouchEvent(MotionEvent ev) {...boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;if (action == MotionEvent.ACTION_DOWN) {.../当禁用掉事件拦截的功能 或 onInteerceptTouchEvent()关闭事件拦截 为false 时if (disallowIntercept || !onInterceptTouchEvent(ev)) {ev.setAction(MotionEvent.ACTION_DOWN);...//遍历所有子Viewfor (int i = count - 1; i >= 0; i--) {final View child = children[i];if ((child.mViewFlags & VISIBILITY_MASK) == VISIBL || child.getAnimation() != null) {child.getHitRect(frame);//判断这个子View是不是正被点击的if (frame.contains(scrolledXInt, scrolledYInt)) {//调用被点击子View的dispatchTouchEvent()处理点击事件if (child.dispatchTouchEvent(ev)) {mMotionTarget = child;//点击事件执行,返回true,结束方法,如一开始的结果,点击子控件只会执行子控件onClick,拦截掉父类的touch事件。return true;}}}}}}...//当点击空白区域,调用ViewGroup的父类View的dispatchTouchEvent(),同第一点,此时执行注册的onTouch。if (target == null) {...return super.dispatchTouchEvent(ev);}...return target.dispatchTouchEvent(ev);}

ViewGroup事件分发的流程图:

在ViewGroup中可以对事件传递进行拦截,修改onInterceptTouchEvent的返回值,true为拦截自己处理,false不拦截继续传给子View。

若子View将传递的事件消费掉,ViewGroup无法接收任何事件。

6. View的事件分发流程

[1] 三个角色

Activity:只分发和消费

事件由ViewRootImpl中DecorView dispatchTouchEvent分发Touch事件–>Activity.dispatchTouchEvent…–>ViewGroup.dispatchTouchEvent…返回true直接处理,false时调用onTouchEvent()。

ViewGroup:分发、拦截和消费

viewGroup的onInterceptTouchEvent返回true表示拦截,自己的onTouchEvent消费,不再下发。返回false传递给子View.dispatchTouchEvent()。

View:只分发和消费

dispatchTouchEvent返回true直接处理,false传递给父View。

[2] 三个核心事件

dispatchTouchEvent() :true事件被当前视图消费掉,false交给父类的onTouchEvent处理。

onInterceptTouchEvent(): true拦截 自己的onTouchEvent()消费,false传递给子View。

onTouchEvent: true消费,false给父类。

[3] 优先级

dispatchTouchEvent–onTouch–onInterceptTouchEvent–onTouchEvent

onTouch是View的触摸监听器Listener中的方法,优先级更高,返回false会调用onTouchEvent(),返回true表示直接处理了onTouchEvent不会被调用。

什么时候会触发Action_cancel事件?

当父View第一次拦截了Action_up/move,子View会收到Action_cancel。

触摸到控件,但不是在它的区域抬起,会收到Action_cancel。

点击事件被父拦截了,但是想往下传咋办?

重写子类的requestDisAllowInterceptTouchEvent()返回true,就不会执行父类的拦截方法onInterceptTouchEvent(),剥夺父View对除了Action_down以外事件的控制权。

解决View的事件冲突:

常见滚动布局和RecyclerView的滑动冲突,或者RecyclerView嵌套滑动冲突。

父需要就拦截,子要处理就剥夺父View的处理权。

一个事件序列只能被一个View拦截消耗。所有事件都会交给它处理。

同时对子View和父View设置点击方法,优先响应?

子View,父要优先需要对事件进行拦截。

---- 初次学习,记录有误欢迎指正

参考资料:

/guolin_blog/article/details/9097463 郭霖大神 上

https://guolin./article/details/9153747 下

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。