写在前面:看了《android群英传》的事件分发后,结合自己的一些理解,整理了一下,记录如下:
首先,在我的前面博客自定义view解析中有讲到view的结构树,它是由viewgroup和view相互嵌套组成的树形结构,一个viewgroup里可以放view也可以放其他viewgroup,然后逐层分发,那么当点击事件发生的时候,是由viewgroup执行呢?还是view执行?因此这里就产生了“事件拦截这一说法”。
然后,事件的分发和拦截的核心函数:
①dispatchTouchEvent
②onInterceptTouchEvent
③onTouchEvent
顾名思义,第一个是分发触摸事件,第二个是拦截触摸事件,第三个是处理触摸事件。
注意:只有ViewGroup类里才能重写onInterceptTouchEvent
顺序:(经过我个人的研究发现)事件最开始获取是在ViewGroup所处的Activity里得到,然后经过层层dispatch到达最小的view,再处理事件逻辑。
下面我给出代码验证一波:
public class MainActivity extends Activity {
private MyLinearLayout m1;
private MyButton m2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
m1 = new MyLinearLayout(this);
m2 = new MyButton(this);
m2.setText("点我!");
setContentView(m1);
m1.addView(m2);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.i("test", "MainActivity dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i("test", "MainActivity onTouchEvent");
return super.onTouchEvent(event);
}
}
class MyLinearLayout extends LinearLayout {
public MyLinearLayout(Context context) {
super(context);
}
public MyLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.i("test", "MyLinearLayout dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.i("test", "MyLinearLayout onInterceptTouchEvent");
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i("test", "MyLinearLayout onTouchEvent");
return super.onTouchEvent(event);
}
}
class MyButton extends Button {
public MyButton(Context context) {
super(context);
}
public MyButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.i("test", "MyButton dispatchTouchEvent");
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i("test", "MyButton onTouchEvent");
return super.onTouchEvent(event);
}
}
代码很简单,就是一个Acitivity里面两个自定义控件,然后各自重写了方法,并打上log,下面是log:
这里就可以清楚的看见最先拿到事件的正是MainActivity,然后dispatch给LinearLayout,在传给button。这里处理了两个事件一个是按下一个是抬起。
但是这里肯定有人不明白,为什么在LinearLayout和Activity里都有onTouchEvent但是他们的log并没有打出来,这是为什么?其实,事件从产生到消费(处理)是经过了两次传递的。从事件的产生获取,然后传递叫事件传递,传到最后被一个view处理了,再返回处理结果,是处理顺序,假设Activity为A对象,LinearLayout为B对象,Button为C对象,那么事件分发顺序为:A——>B——>C,而事件的处理顺序正好反过来,应该是C——>B——>A。如下图:
上面类中重写的方法默认返回值分发过程是false,而处理过程默认为true;分发事件时返回值改为true表示拦截事件,不再往下传递;处理事件返回值改为false表示不处理了交给上级处理。而dispatchTouchEvent是分发的第一步一般不去修改。
下面做三个实验,把LinearLayout的onInterceptTouchEvent方法返回值改为true;把button的onTouchEvent返回值改为false;上面一步的基础上再把LinearLayout的onTouchEvent返回值改为false。分别看下log
正如上文所述,被LinearLayout拦截后,Button不会收到事件,LinearLayout自己处理事件,后面抬起事件就直接传给LinearLayout处理不进行拦截和传递操作。然后后面两个修改onTouchEvent也同理只是处理时的位置不同,道理就是最小的view不处理就往上抛,直到有onTouchEvent返回为true的地方再进行处理。
ok,码了半天。事件分发应该差不多就这些东西,欢迎指正~