“大哥大哥,快来,领导叫你过去面试!!”
“来了来了!”
看了看简历,面试的是中级安卓开发。
“你来说一下安卓事件分发机制吧!”
…
“大哥,老实说,事件分发我也不咋会,还幸亏你面试我的时候高抬贵手,没问我,要问我的话我就进不来咱们公司了!”
“抬啥手,你面试的时候面试的是实习生,我问你这干啥?”
“奥,好吧。。。。话说回来,大哥,你能给我讲讲事件分发嘛?”
“行吧,那就给你从头到尾说一遍吧,这个以后都会有用的。”
“谢谢大哥,我去给大哥泡咖啡!!!”
事件分发,安卓开发中老生常谈的一个问题了,不仅仅是为了应付面试,实际工作中也会经常用到(这里要说明一下,平时尽量要把知识学习的牢固一些,不要等到面试了才临时抱佛脚)。下面我会将事件分发好好扒一扒!
“小子,给我说一下Activity的构成吧!”
“构成?Activity不就是Activity嘛!啥构成啊?”
“。。。。。。记好了。。。。。。”
一个Activity包含了一个Window对象,Window是由PhoneWindow来实现的。PhoneWindow将DecorView作为整个应用窗口的根View,而这个DecorView又将屏幕划分为两个区域:一个是 TitleView,另一个是ContentView,而我们平时所写的就是展示在ContentView中的。
“好像有点明白了,但是大哥,这和事件分发有什么关系呢?”
“猴急的性子就是改不了,马上要说了!”
触摸事件对应的是MotionEvent类,事件的类型主要有以下三种:
“大哥,你刚刚说的阈值到底是多少啊?我记得以前看别人写的博客也这样说的,但一直没搞明白阈值到底是多少?”
“不错不错,小子,求知欲很强嘛!”
TouchSlop(系统常量)表示最小移动阈值,默认值一般为8dp。这个相信都能明白,意思就是如果你滑动小于这个值的话系统就会认为你这不属于滑动事件。这里需要注意,有的手机厂商为了“提高用户体验”,重新设置了这个值的大小,如果不确定可以打印下看看。
Log.i("ViewConfiguration", "TouchSlop=" + ViewConfiguration.get(this).scaledTouchSlop)
下面是执行结果:
2020-05-07 22:12:35.939 12599-12599/com.zj.weather I/ViewConfiguration: TouchSlop=21
“明白了吗?”
“呃呃。。。大哥你继续讲吧,我能听懂!”
其实View事件分发的本质很简单,当一个MotionEvent事件发生后,系统将这个点击事件传递到一个具体的View上,就是对MotionEvent事件分发的过程。
“小子,来给我说说事件分发过程由哪几个方法完成的!”
“嗯。。。有dispatchTouchEvent,还有onInterceptTouchEvent和onTouchEvent三个方法完成的。”
“不错不错,那你给我说说这三个方法的执行过程吧”
“大哥,你直接说吧,我只知道有这三个方法。。。。”
“好,不为难你了,下面要认真听了。”
咱们就一个一个方法来说吧!
**dispatchTouchEvent(分发):**当方法返回值为true时表示事件被当前视图消费掉;如果返回false时则表示交给父类中的onTouchEvent进行处理;如果返回为super.dispatchTouchEvent时则表示会继续分发该事件。
**onInterceptTouchEvent(拦截):**当方法返回值为true时表示拦截这个事件并交由自身的onTouchEvent方法进行消费;如果返回false则表示不拦截,需要继续传递给子视图。如果返回super.onTouchEvent(ev),这块就有点麻烦了,分为两种情况:
这块需要注意的是:LinearLayout、 RelativeLayout、FrameLayout等ViewGroup默认不拦截, 而 ScrollView、ListView等ViewGroup则可能拦截,需要看具体情况。
**onTouchEvent(消费):**方法返回值为true表示当前视图可以处理对应的事件;返回值为false表示当前视图不处理这个事件,它会被传递给父视图的onTouchEvent方法进行处理。如果返回super.onTouchEvent(ev),事件处理又分为了两种情况:
“小子,听懂了吗?所谓的事件分发就这么简单”
“啊?这都啥是啥啊!根本不理解,大哥,你能再好好说说吗?”
“真拿你没办法,行,听好了!”
你看啊,安卓中有事件传递处理能力的也就有Activity、ViewGroup、和View三种,你就想,Activity和View拦截事件干嘛呢?他们又不管,所以他们有分发和消费不就够了嘛。但是ViewGroup不一样啊,它里面会包含有子View,所以才要有拦截事件,没问题吧?算了,给你写一个伪代码吧!
fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
var result = false
if (onInterceptTouchEvent(ev)) {
result = onTouchEvent(ev)
} else {
result = child.dispatchTouchEvent(ev)
}
return result
}
“小子,这样总行了吧?”
“额。。。。大哥,你再说说吧。。。”
你看上面的代码,对应一个根ViewGroup来说,点击事件产 生后,首先会传递给它,这个时候它的dispatchTouchEvent方法就会被调用,如果这个ViewGroup的onInterceptTouchEvent方法返回true就表示它要拦截当前事件,接着事件就会交给这个ViewGroup处 理,这时如果它的mOnTouchListener被设置,则onTouch会被调用,否则的话onTouchEvent会被调用。 在onTouchEvent中,如果设置了mOnCLickListener,则onClick会被调用。这里需要注意,只要View的CLICKABLE和 LONG_CLICKABLE有一个为true,onTouchEvent就会返回true消耗这个事件;如果onInterceptTouchEvent方法返回false就表示它不拦截当前事件,这时当前事件就会继续传递给它的子元素,接着子元素的dispatchTouchEvent方法就会被调用,就这样一直反复直到事件被最终处理。
“小子,是不是很简单?”
“听你说的很简单,但我还要再好好消化消化!”
“嗯,确实,这块其实很麻烦,简单的还好说,如果多层滑动布局进行嵌套要处理滑动冲突就比较麻烦了。帮你总结一下吧!”
“记着上面这几点,你理解起来会更快一些的!”
“感谢大哥!”
“对了大哥,ACTION_CANCEL什么时候触发啊,如果触摸button然后滑动到外部抬起会触发点击事件吗,再滑动回去抬起会么?”
“你能不能一个问题一个问题问。。。。”
“理解了嘛?”
“嗯嗯,这个知道了,但我还有问题,如果点击事件被拦截了,但是我又想传到下面的View,该怎么搞啊?”
“这个简单!只要重写子类的requestDisallowInterceptTouchEvent()方法返回true就不会执行父类的 onInterceptTouchEvent(),就可以将点击事件传到下面的View了。”
“大哥,那我平时遇到的滑动冲突该怎么解决啊?”
“这个说起来就比较麻烦了,大概给你说下思路吧,这个需要你以后开发中慢慢体会!”
滑动冲突的处理规则:
滑动冲突的实现方法:
“小子,这样总可以了吧?”
“大哥,我还有问题。。。。”
“放过我吧,这么晚了,有问题明天再说吧!”
本文简单描述了一下安卓的事件分发机制,描述的不是特别详细,大家可以自己写个小Demo试试,加深一下理解。文章写的仓促,如果有错误,欢迎在评论区指出,不胜感激!喜欢的话欢迎点赞关注!