写在前面:
作为安卓开发很多人在面试的时候曾经或者现在都遇到过这样两个问题:1、说说你熟悉的设计模式;2、Android 的触摸事件分发机制。这篇文章结合设计模式中的责任链模式来模拟实现Android 触摸事件分发。
一、责任链模式
责任链模式通俗的说就是多个责任人连接成一条链,当一个事件传递到当前责任人的时候如果当前责任人不处理则交给他的下一个节点责任人,直到没有人处理,有点像我们平常说的“踢皮球”。责任链的主要作用是将发送者和接受者解耦,能够让责任链上的每个责任人各司其职。说到“链”,其实他的结构很像数据结构中的链表,一般来说如果是一条单向的责任链那么当前责任人持有下一个责任人的引用即可。如果是双向责任链那么当前节点持有上一个和下一个责任人的引用。
二、事件分发
用户的触摸事件从当前的 activity 开始分发消费。activity默认不消费事件,activity 传给他所附属的 window,window 通过 superDispatchTouchEvent() 传给根View,也就是 activity 在 onCreate() 开头通过 setContentView() 创建的 View 。根View一般来说都是 ViewGroup,ViewGroup 默认也不消费事件,但他相对多一个拦截事件方法,默认是不拦截事件并交给下一级的 View 进行处理,如果 ViewGroup 需要处理当前事件则可以重写 onInterceptTouchEvent() 方法返回true,再交由 ViewGroup 的 onTouchEvent 方法处理。事件传到View这里一般来说都会进行处理,变成我们的点击事件、长按事件等等,如果View不消费则触摸事件沿着原路返回。所以大的流程来讲事件分发机制为 Activity -> Window -> View(广义,包含 ViewGroup和 其子 View )从这个事件传递模型来看其实就是一条双向的责任链。确实,Android 的事件分发中用到了责任链模式。(开始扣题)
三、武魂融合技-show me your code
首先创建我们的责任人基类,每个责任人继承并实现该基类。为将事情简单化我们省去模拟Activity找他的window和根View的流程,从而模拟一个事件从 Activity 到 ViewGroup 到子 View 的分发过程。每一个责任人都有分发的方法连接责任链上每个责任人。
abstract class AbsTouch {
var next:AbsTouch?=null//下一责任人
var previous:AbsTouch?=null//上一责任人
private var isDispatched=false//是否从上一责任人转发下来过
//事件分发主要方法
open fun dispatch(event: String){
val onTouch = onTouch(event)//具体事件处理
//事件被处理则责任链结束,不再向上或向下传递
if (onTouch){
println("$event 事件被处理")
return
}
//事件第一次来且不是责任链最后一位责任人,向下传递
if (!isDispatched&&next!=null){
println("懒得处理,给下一位 $event")
isDispatched=true
next?.dispatch(event)
}else{
//事件不是第一次来或者是责任链最后一位责任人,向上回传
println("懒得处理,给上一位 $event")
previous?.dispatch(event)
}
}
//具体决定是否消费触摸事件
abstract fun onTouch(event: String):Boolean
}
实现三个类来模拟我们的 Activity、ViewGroup、View三个责任人。Activity只有 onTouchEvent 方法
class Activity : AbsTouch(){
val TAG=Activity::class.simpleName
override fun onTouch(event: String): Boolean {
println("$TAG:$event 来啦")
//activity默认不处理,返回false
return false
}
}
ViewGroup 相对的多一个 onInterceptTouchEvent 方法对事件进行拦截
open class ViewGroup : AbsTouch(){
val TAG=ViewGroup::class.simpleName
//ViewGroup是否消费先决条件为是否拦截,不拦截直接转发
//拦截的话传给onTouchEvent
override fun dispatch(event: String) {
if (onIntercept())
onTouch(event)
else super.dispatch(event)
}
override fun onTouch(event: String): Boolean {
println("$TAG:$event 来啦")
return false
}
//相较于Activity和View ,ViewGroup多了一个onInterceptTouchEvent()方法
//且默认不拦截
open fun onIntercept():Boolean{
return false
}
}
View也只有一个 onTouchEvent 方法。默认是返回 true =消费事件。
class View : AbsTouch(){
val TAG=View::class.simpleName
var clickAbel=true
//view的onTouchEvent默认返回true
//当用户自定义为false或设置clickable=false时,view不消费事件
override fun onTouch(event: String): Boolean {
println("$TAG:$event 来啦")
return clickAbel
}
}
实例化三个责任人,将三者建立关系成责任链。事件从 Activity 出发,默认 ViewGroup 不拦截情况
@Test
fun onClick(){
val activity= Activity()
val viewGroup = ViewGroup()
activity.next=viewGroup
viewGroup.previous=activity
val view = View()
viewGroup.next=view
view.previous=viewGroup
activity.dispatch("点击事件")
}
事件分发结果如下
重写 ViewGroup 的拦截事件方法,替换责任链中的 ViewGroup , 当事件被拦截后事件将交由该ViewGroup 处理不再进行转发。
class NewViewGroup:ViewGroup() {
override fun onIntercept(): Boolean {
return true
}
}
此时事件分发结果如下:
如果都不处理那么事件分发流程就会变成这样:
、
写在后面:
1、 责任模式其实很简单,主要就是基类持有链上相邻节点的引用,然后根据业务逻辑去实现分发方法。使用场景也很多,除开这里的事件分发外,常见的还有okhttp的拦截器也用到了这个设计模式。
2、 事件分发三个主要角色 Activity、Window、View。
三个关键方法
dispatchTouchEvent:连接链上相邻节点,分发事件。
onInterceptTouchEvent:ViewGroup独有,拦截事件,拦截后直接走它的onTouchEvent。
onTouchEvent:是否处理事件,怎么处理。
与我们的实现例子不同,点击事件不是单一事件而往往是一串事件流,当一个责任人选择不处理最开始的ACTION_DOWN方法时其实它所带来的后续事件流也不会被该View处理。本文从大体出发讲的是大致流程,细节处没讲到的地方大家可以参考这篇图解 Android 事件分发机制,或者其它书籍资料。如果有表述出错的地方欢迎大家指正。