标签:
上一篇博客职责链/责任链模式(Chain of Responsibility)分析理解和在Android的应用
介绍了职责链模式,作为理解View事件分发机制的基础。
套用职责链模式的结构分析,当我们的手指在屏幕上点击或者滑动,就是一个事件,每个显示在屏幕上的View或者ViewGroup就是职责对象,它们通过Android中视图层级组织关系,层层传递事件,直到有职责对象处理消耗事件,或者没有职责对象处理导致事件消失。
要理解有关View的事件分发,先要看几个关键概念
当手指接触到屏幕以后,所产生的一系列的事件中,都是由以下三种事件类型组成。
1. ACTION_DOWN: 手指按下屏幕
2. ACTION_MOVE: 手指在屏幕上移动
3. ACTION_UP: 手指从屏幕上抬起
例如一个简单的屏幕触摸动作触发了一系列Touch事件:ACTION_DOWN->ACTION_MOVE->…->ACTION_MOVE->ACTION_UP
对于Android中的这个事件分发机制,其中的这个事件指的就是MotionEvent。而View的对事件的分发也是对MotionEvent的分发操作。可以通过getRawX和getRawY来获取事件相对于屏幕左上角的横纵坐标。通过getX()和getY()来获取事件相对于当前View左上角的横纵坐标。
public boolean dispatchTouchEvent(MotionEvent ev)
这是一个对事件分发的方法。如果一个事件传递给了当前的View,那么当前View一定会调用该方法。对于dispatchTouchEvent的返回类型是boolean类型的,返回结果表示是否消耗了这个事件,如果返回的是true,就表明了这个View已经被消耗,不会再继续向下传递。
public boolean onInterceptTouchEvent(MotionEvent ev)
该方法存在于ViewGroup类中,对于View类并无此方法。表示是否拦截某个事件,ViewGroup如果成功拦截某个事件,那么这个事件就不在向下进行传递。对于同一个事件序列当中,当前View若是成功拦截该事件,那么对于后面的一系列事件不会再次调用该方法。返回的结果表示是否拦截当前事件,默认返回false。由于一个View它已经处于最底层,它不会存在子控件,所以无该方法。
public boolean onTouchEvent(MotionEvent event)
这个方法被dispatchTouchEvent调用,用来处理事件,对于返回的结果用来表示是否消耗掉当前事件。如果不消耗当前事件的话,那么对于在同一个事件序列当中,当前View就不会再次接收到事件。
上文部分内容来自《Android开发艺术探索》
为了验证和理解实际的运行状态,重写View和ViewGroup这些关键方法,打印方法调用。
继承View重写方法,加入结果打印。
注:View作为子控件,不存在内部子控件,所以传入事件就视图处理,而不存在拦截子控件的事件,所以没有onInterceptTouchEvent
方法
public class MyView extends View {
public MyView(Context context) {
super(context);
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean result=super.dispatchTouchEvent(ev);
Logger.d("result= "+result+" info="+MotionEvent.actionToString(ev.getAction()));
return result;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean result=super.onTouchEvent(event);
Logger.d("result= "+result+" info="+MotionEvent.actionToString(event.getAction()));
return result;
}
}
继承ViewGroup重写方法,加入结果打印。
注:ViewGroup没有实现onLayout布置控件位置,所以继承LinearLayout,对分发不影响
public class MyViewGroup extends LinearLayout {
public MyViewGroup(Context context) {
super(context);
}
public MyViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean result=super.dispatchTouchEvent(ev);
Logger.d("result= "+result+" info="+MotionEvent.actionToString(ev.getAction()));
return result;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean result=super.onTouchEvent(event);
Logger.d("result= "+result+" info="+MotionEvent.actionToString(event.getAction()));
return result;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean result=super.onInterceptTouchEvent(ev);
Logger.d("result= "+result+" info="+MotionEvent.actionToString(ev.getAction()));
return result;
}
}
最后把这两个控件加入布局文件就可以了。
<com.demo.licola.HttpDemo.view.MyViewGroup
android:layout_width="200dp"
android:layout_height="200dp"
android:id="@+id/ll_group"
android:background="@color/saffron"
>
<com.demo.licola.HttpDemo.view.MyView
android:layout_width="100dp"
android:layout_height="100dp"
android:id="@+id/view_child"
android:background="@color/colorAccent"
/>
</com.demo.licola.HttpDemo.view.MyViewGroup>
首先运行上面的代码后,可以看到两个色块。用手机点击里面的MyViewGroup的子控件MyView。
相信我,不论怎么滑动或者点击都是一样的结果,下面会分析这样的情况发生原因。
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}
修改代码,给View添加点击事件,也就是使用setOnClickListener
简单的处理。
然后手指点击迅速抬起,要不然打印结果太多了。
分析:产生点击事件,子控件onTouchEvent可以处理事件,得到Down事件,同时影响ViewGroup父控件dispatchTouchEvent返回ture可以向下分发,后继的UP事件也相继的传进来。
在原基础上,再次修改代码,给ViewGroup父控件的onInterceptTouchEvent方法返回true,表示拦截事件。然后给ViewGroup添加点击监听回调setOnClickListener
。
分析:有了上面的基础,这里就什么好说的了,因为父控件拦截了事件,同时能够响应事件,所有的事件都发送到ViewGroup上。
通过上面的3个实验的结果,可以大概对ViewGourp的事件分发有个基本的认识。
所以通过抽象源码提取关键实现,可以有下面的大概处理逻辑。
在ViewGroup中有如下逻辑:
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean consume =false;//默认不处理
if (onInterceptTouchEvent(ev)){//首先判断是否拦截
consume=onTouchEvent(ev);//看是否能够处理
}else {
//如果不拦截,遍历子控件,这里省略掉
//调用子控件的分发方法,下发事件。
consume=getChildView.dispatchTouchEvent(ev);
}
return consume;
}
我在上篇博客介绍分析了职责链模式,用该模式的思想来分析。
可以画出这样的UML图:
当然这是简略的画法,实际会复杂得多,而且View和VIewGorup采用设计模式组合模式的思想构造。
View和ViewGourp:都是实际的实现职责类,内部通过调用其他的方法判断是否能够处理请求事件。
链的构造:
很明显链的构造是在ViewHandler中组装的,内部链的实现方式。onTouchEvent和onInterceptTouchEvent都会影响链的构造。动态的生成职责链。
最后提出一些结论,给大家一些对事件分发的提示和总结。说不定面试的时候就用上了呢。
提示:ViewGroup在继承关系上继承View,所以下文可以用View指代ViewGroup。具体的原因自行Google。
这个就是我们在处理一些嵌套滑动时候遇到的主要问题。子控件拦截了事件,View对这个滑动事件不想要处理的时候,只能抛弃这个事件,而不会把这些传给父view去处理。这就是滑动的嵌套的父子控件同方向滑动不流畅的原因。好消息时NestedScrollView的出现很好的解决了这个问题。
Android开发-分析ViewGroup、View的事件分发机制、结合职责链模式
标签:
原文地址:http://blog.csdn.net/card361401376/article/details/51557934