简述
Touch事件分发中只有两个主角:ViewGroup和View。Activity的Touch事件事实上是调用它内部的ViewGroup的Touch事件,可以直接当成ViewGroup处理。
View在ViewGroup内,ViewGroup也可以在其他ViewGroup内,这时候把内部的ViewGroup当成View来分析。
ViewGroup的相关事件有三个:
- dispatchTouchEvent:分发事件。这个方法和事件分发机制密不可分,但这里不说这个。
- onInterceptTouchEvent:拦截事件。继承此方法,返回true,表示拦截了此事件。
- onTouchEvent:监听事件。继承此方法,处理事件,返回true表示消费了此事件。
View的相关事件只有两个:
- dispatchTouchEvent
- onTouchEvent
先分析ViewGroup的处理流程:首先得有个结构模型概念:ViewGroup和View组成了一棵树形结构,最顶层为Activity的ViewGroup,下面有若干的ViewGroup节点,每个节点之下又有若干的ViewGroup节点或者View节点,依次类推。如图:
分发流程
分发流程用一个图来说明,看:
下面的描述只需要看一遍,理解了就行,然后上面这个图就会自动留在你的脑海中了,好简单的图。
- 用户按下屏幕,滑动一下,再松开手,会发出一些列的事件:DOWN-MOVE-MOVE…-UP,这里主要说DOWN事件。
- Activity首先会把DOWN事件分发给最底层的ViewGroup1的onInterceptTouchEvent方法。
- onInterceptTouchEvent,继承并返回true,表示拦截此事件,不再传递下去;返回false,表示不拦截,事件直接传递给下一个ViewGroup的onInterceptTouchEvent方法。
- onTouchEvent,继承并返回true,表示消费了此事件,不再传递下去;返回false,表示没有消费,继续传递下去给别的View处理。
注意:
如果一直没有View消费DOWN事件,则后面的MOVE、UP事件就不会再触发了。
比如上面的例子,如果ViewGroup1、ViewGrouip2和View的onTouchEvent都返回false的话,那么只会触发DOWN事件,后面的UP事件将不会收到了。
如果某个ViewGroup消费了DOWN事件,则后续的事件会把此ViewGroup当初View,也就是忽略它的onInterceptTouchEvent。
比如上面的例子,如果ViewGroup2的onTouchEvent返回true,则后面的UP事件会忽略ViewGroup2的onInterceptTouchEvent,会变成:ViewGroup1.onInterceptTouchEvent -> ViewGroup2.onTouchEvent。
实用的实践例子
首先,从一个简单示例入手:
先看一个示例如下图所示:
布局文件 :
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
tools:context="com.example.touch_event.MainActivity"
tools:ignore="MergeRootFrame" >
<Button
android:id="@+id/my_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/hello_world" />
</FrameLayout>
MainActivity文件:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button mBtn = (Button) findViewById(R.id.my_button);
mBtn.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.d("", "### onTouch : " + event.getAction());
return false;
}
});
mBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Log.d("", "### onClick : " + v);
}
});
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d("", "### activity dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}
}
当用户点击按钮时会输出如下Log:
08-31 03:03:56.116: D/(1560): ### activity dispatchTouchEvent
08-31 03:03:56.116: D/(1560): ### onTouch : 0
08-31 03:03:56.196: D/(1560): ### activity dispatchTouchEvent
08-31 03:03:56.196: D/(1560): ### onTouch : 1
08-31 03:03:56.196: D/(1560): ### onClick : android.widget.Button{52860d98 VFED..C. ...PH... 0,0-1080,144 #7f05003d app:id/my_button}
我们可以看到首先执行了Activity中的dispatchTouchEvent方法,然后执行了onTouch方法,然后再是dispatchTouchEvent --> onTouch, 最后才是执行按钮的点击事件。这里我们可能有个疑问,为什么dispatchTouchEvent和onTouch都执行了两次,而onClick才执行了一次 ? 为什么两次的Touch事件的action不一样,action 0 和 action 1到底代表了什么 ?
覆写过onTouchEvent的朋友知道,一般来说我们在该方法体内都会处理集中touch类型的事件,有ACTION_DOWN、ACTION_MOVE、ACTION_UP等,不过上面我们的例子中并没有移动,只是单纯的按下、抬起。因此,我们的触摸事件也只有按下、抬起,因此有2次touch事件,而action分别为0和1。我们看看MotionEvent中的一些变量定义吧:
public final class MotionEvent extends InputEvent implements Parcelable {
// 代码省略
public static final int ACTION_DOWN = 0; // 按下事件
public static final int ACTION_UP = 1; // 抬起事件
public static final int ACTION_MOVE = 2; // 手势移动事件
public static final int ACTION_CANCEL = 3; // 取消
// 代码省略
}
可以看到,代表按下的事件为0,抬起事件为1,也证实了我们上面所说的。
在看另外两个场景:
1、我们点击按钮外的区域,输出Log如下 :
08-31 03:04:45.408: D/(1560): ### activity dispatchTouchEvent08-31
03:04:45.512: D/(1560): ### activity dispatchTouchEvent
2、我们在onTouch函数中返回true, 输出Log如下 :