Android事件分发分为ViewGroup和View两种.
触摸任何一个控件,事件分发都是从最外层开始,比如下图有3层布局,如果点击的是3,则事件分发的正常顺序为1-2-3.
不管是ViewGroup还是View,事件分发都是从其dispatchTouchEvent()开始。
一次点击触发的事件有DOWN,MOVE(可能滑动),UP等,上一次事件返回true,才能继续下一个事件。
View的事件分发机制:
先看View中的dispatchTouchEvent()方法
重点看红框内的代码,如果设置了mOnTouchListener(li.mOnTouchListener != null)且控件可用((mViewFlags & ENABLED_MASK) == ENABLED),则执行onTouch()方法,
onTouch为我们重写的方法,
onTouch方法要求返回一个boolean值,如果返回true,则此事件结束,开始下一个事件。
如果返回false,或者没有设置touchListener,或者控件不可用,则执行onTouchEvent()方法。
下面截取了onTouchEvent()方法中的部分代码:
public boolean onTouchEvent(MotionEvent event) {
if ((viewFlags & ENABLED_MASK) == DISABLED) {
……
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
……
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
……
if (!post(mPerformClick)) {
performClick();
}
………
break;
case MotionEvent.ACTION_DOWN:
………
break;
case MotionEvent.ACTION_CANCEL:
……
break;
case MotionEvent.ACTION_MOVE:
………
break;
}
return true;
}
return false;
}
上面代码的逻辑很清晰,如果控件不可用,直接返回。
如果控件是不可点击的(如ImageView默认不可点击),直接返回false,此事件结束(没有被消费),不再开始下一个事件。
如果控件是可点击的,不管是DOWN或MOVE或UP事件,最后都是返回true,此事件结束(被消费),开始下一个事件。
并且从UP事件中,我们可以看到点击事件是在这里执行的:
关于点击事件,有一点需要注意,当我们为一个控件设置点击监听器时,
可以看到在里面会将控件设置为可点击,所以主动的setClickable() 一定要放在按扭的setOnClickListener事件之后!
View的事件分发到此结束。
ViewGroup的事件分发机制:
从ViewGroup的dispatchTouchEvent()方法出发,因为里面代码逻辑比较复杂,这里就不贴代码了。我抽象的梳理下里面的流程:
首先在里面会判断一个方法onInterceptTouchEvent(MotionEvent ev)
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}
这个方法表示是否拦截事件传递,默认返回false。可重写此方法。
如果返回false,表示不拦截子View的事件,接下来会遍历所有的子View,找到被点击的子View,然后执行子View的dispatchTouchEvent(),这就回到了上面所讲的 View的事件分发机制 。在《View的事件分发机制》中,我们知道如果一个控件是可点击的,则此事件一定返回true,这时在ViewGroup中会直接返回true,此事件传递结束(被消费,不会传到ViewGroup),开始下一事件。如果子View的dispatchTouchEvent()返回false或没找到子View,则继续执行,最后会执行父View的dispatchTouchEvent。
如果返回true,表示将拦截子View的事件,不向子View传递。接下来会去执行父View的dispatchTouchEvent,而ViewGroup的父类是View,所以又回到了上面将的《View的事件分发机制》。
不管是返回true或false,最后执行的都是《View的事件分发机制》,需要区别的是 子View 还是 父View 。
ViewGroup的事件分发到此结束。
例子:
我们设计一个类似如下图的布局:1和2为线性布局,3为button
整体布局:
<com.example.shijianfenfa.MyLayout1 xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/layout1"
android:orientation="vertical"
tools:context=".MainActivity" >
<com.example.shijianfenfa.MyLayout2
android:layout_width="200dp"
android:layout_height="300dp"
android:background="#456784"
android:id="@+id/layout2"
android:orientation="vertical" >
<Button
android:layout_width="100dp"
android:layout_height="100dp"
android:id="@+id/button"
android:text="@string/hello_world" />
</com.example.shijianfenfa.MyLayout2>
</com.example.shijianfenfa.MyLayout1>
布局1:
public class MyLayout1 extends LinearLayout{
public MyLayout1(Context context, AttributeSet attrs) {
super(context, attrs);
}
}
布局2:
public class MyLayout2 extends LinearLayout{
public MyLayout2(Context context, AttributeSet attrs) {
super(context, attrs);
}
}
在MainActivity中为这3个View设置touch事件,通过点击,打印log,来看事件的分发流程:
public class MainActivity extends Activity {
private static final String TAG = "libiao";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LinearLayout lay1 = (LinearLayout) findViewById(R.id.layout1);
lay1.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.i(TAG, "lay1=="+event.getAction());
return false;
}
});
LinearLayout lay2 = (LinearLayout) findViewById(R.id.layout2);
lay2.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.i(TAG, "lay2=="+event.getAction());
return false;
}
});
Button button = (Button) findViewById(R.id.button);
button.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.i(TAG, "tv=="+event.getAction());
return false;
}
});
}
}
点击图中1,打印的log为:
事件分发流程:从ViewGroup 1 开始,不拦截,未找到子View,执行父View(MyLayout1)的dispatchTouchEvent(),不可点击,返回false,事件结束,只有down事件。
点击图中2,打印的log为:
事件分发流程:从ViewGroup的dispatchTouchEvent 开始,不拦截,找到子View(MyLayout2),还是一个ViewGroup,接着从ViewGroup 的dispatchTouchEvent 开始,不拦截,未找到子View,执行父View(MyLayout2)的dispatchTouchEvent,打印lay2==0,因为不可点击,返回false,继续执行MyLayout1的dispatchTouchEvent,打印lay1==0,因为不可点击,返回false,事件结束,只有down事件。
点击图中3,打印的log为:
事件分发流程:从ViewGroup的dispatchTouchEvent 开始,不拦截,找到子View(MyLayout2),还是一个ViewGroup,接着从ViewGroup 的dispatchTouchEvent 开始,不拦截,找到子View(button),执行View的dispatchTouchEvent,因为可点击,返回true,事件被消费,结束,开始下一个事件,所以在log中只有button的touch事件,且DOWN、MOVE、UP都执行了。
如果将button换成TextView,则点击图中3,打印的log为:
因为TextView默认不可点击,事件分发流程就不再累叙了。
如果我在MyLayout2中将事件拦截返回true :
public class MyLayout2 extends LinearLayout{
public MyLayout2(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return true;
}
}
则点击图中3,不管是Button还是TextView,打印的log都是:
具体的事件分发流程,我相信大家可以自己描述出来了。
如果将MyLayout1的touch事件返回true,
lay1.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.i(TAG, "lay1=="+event.getAction());
return true;
}
});
则点击图中3,打印的log是:
可见如果子View的前一次事件返回false,后一次事件是不会传递到子View的。
如果事件被自己消费或父View消费,那自己的onInterceptTouchEvent只会走一次;如果是被子View消费,那自己的onInterceptTouchEvent每个事件都会走一次。(onInterceptTouchEvent影响的是子View)
。。。。。。。。。。。。。。。。。。。end。。。。。。。。。。。。。。。。。。。。。。。。
每看一遍,你都会有新的收获。