Android touch 事件传递机制
前言:
(1)在自定义view的时候经常会遇到事件拦截处理,比如在侧滑菜单的时候,我们希望在侧滑菜单里面有listview控件,但是我们希望既能左右滑动又能上下滑动,这个时候就需要对触摸的touch事件进行拦截。这个时候我们就需要明白android touch 事件传递机制,
(2)以前很多时候比较模糊,也许是网上看到也有很多事件传递的相关文章,但我看着头晕,解释不彻底,有的说得一半,总算不满足不满意,于是据我自己的理解来彻底的来整理下具体的是怎么个传递方式,以最简单通俗易懂的方式分享给大家,希望大家看到有什么不对的地方及时提出纠正。谢谢
测试布局:
这是本次理解android touch 事件传递机制的布局文件
传递机制视图:
(1)android touch 事件传递机制示意图,由于网页原因会被拉伸,请大家单独将该图在另一个窗口打开查看。
(2)事件是从Activity触发事件然后传递到布局文件,一层一层的往子容器传递到最底层的view,如果每层布局文件未对该事件进行处理或者消费那么该事件会从最底层开始往上传到Activity进行消费。类似于一个U型。
(3)那么事件的发起是由Activity界面的touch事件发起传递到布局视图,但是该视图只是描述了布局文件或者view的相关事件传递机制,Activity事件没有进行描述,但是在下面测试中会涉及Activity相关事件传递来解释心中些许疑惑
事件传递另一个角度解释:
android touch 事件传递机制示意图可以总结如下规律
(0)忘掉以前的各种说法解释,因为各种组合说来说去的,我们需要简单的,换一个角度,换一个思维,忘记事件传递,以最简单通俗易懂的方式理解事件传递,我只需要一张图就搞定事件传递机制。
(1)一个事件由用户点击触发开始顺着箭头的方向进行传递,直到任意一个结束点结束事件传递。
(2)那么事件传递可以由A传到B,B可以不传到C,B不进行分发,那么就从B再传回A进行消费然后结束,也可以由B传到C然后传递D,或者不传到D进行消费或者传到父容器进行消费结束。
将图变成理论印在脑海里
一、touch事件传递过程,我们需要搞清楚三种种情况:
(1)事件从Activity界面开始 事件处理方法有两个 dispatchTouchEventon 和 TouchEvent 两种
(2)继承ViewGroup的子类事件处理有dispatchTouchEvent、onInterceptTouchEvent 和 onTouchEvent三种
(3)继承View的子类事件处理有dispatchTouchEventon 和 TouchEvent 两种
二、touch事件传递形象说明
(1)touch事件事件传递形象的理解可以这么认为:比如我有一个苹果(touch),我可以自己吃也可以分发(dispatchTouchEventon)给孩子吃(TouchEvent)。如果我不吃那么我就返回给我的父亲处理,如果我分发给孩子那么孩子,那么这个苹果交给我的孩子他有自己独立的权利进行处理,他可以继续分给他的孩子就是我的孙子进行处理,也可以自己吃了吃掉,如果我的孙子不处理他也可以返回给他的父亲就是我的孩子处理,我的孩子也有相同的权利进行处理。
(2)接着上面其实这个事件(苹果)的传递是从上往下,然后再由下往上传递,中途如果有人消费这个事件(吃掉苹果),那么这个事件就结束(苹果没有了),就结束传递。
(3)事件(苹果)传递,不像我们人一样要害羞要矜持,推来推去,比如这个苹果孩子不分发给他的孩子但是他自己又不想消费(吃掉)而是返回给我,那么我就是只有两个选择要么消费(吃掉)要么返回给我的父类进行处理,不能推来推去,就是不能孩子给我了事件(苹果),我又来分发给孩子,这是不行的,这样就是个死循环。
(4)总之我拿到这个事件(苹果)会往我的孩子进行传递,我的孩子也可以往他的孩子进行传递和消费,这样转往下走,如果有一个孩子消费掉这个事件(吃掉苹果),那么该事件结束。如果孩子都不消费那么就会从最下面的孩子一层层传上来传到我手里进行处理。
(5)记住 分发 拦截 处理。任何孩子拿到该事件第一步就是往下面分发,如果中途有拦截那么久就自己处理,直到分到最底层就辈分最低的孩子,如果该事件就往上给父亲处理。
二、事件处理逻辑流程
(1)事件无论是从开始触发还是在传递到不同的层级布局文件过程中,一定会对事件进行分发,当然在父容器中如果touch事件被拦截了就不会下传了。就是如果该事件传递到某一层那么该层的dispatchTouchEventon会首先被调用进行事件分发,比如示意图中的事件传递到C-ViewGroup层,那么C-ViewGroup中的 dispatchTouchEventon这个方法是一定会被调用的进行事件分发,当然如果事件在B-ViewGroup不分发事件就会往上传递给父类的onTouchEvent进行处理,或者在B-ViewGroup被拦截发事件就会往上传递给B-ViewGroup的onTouchEvent进行处理,也就不会传到C-ViewGroup这层。
(2)在对事件进行分发中,dispatchTouchEventon返回值为false表示对事件进行分发,返回true为不分发,当事件不进行分发的时候,那么该事件在该层就不会往下传递,就会返回传递给父容器onTouchEvent进行处理,那么如果该事件被拦截事件就传递给当前容器onTouchEvent进行处理,如果当前容器onTouchEvent返回false就表示不想消费处理,那么该事件就会往上传递给父容器onTouchEvent进行处理,记住onTouchEvent这里不能往下传,如果这里不消费就只能往上传递,只有dispatchTouchEventon才能往下分发。
测试验证事件传递机制:
一、在测试验证之前需要了解每个View的子类都具有下面三个和TouchEvent处理密切相关的方法:
(1)public boolean dispatchTouchEvent(MotionEvent ev) 分发TouchEvent
(2)public boolean onInterceptTouchEvent(MotionEvent ev) 拦截TouchEvent
(3)public boolean onTouchEvent(MotionEvent ev) 处理TouchEvent
二、界面效果
三、布局文件代码
<?xml version="1.0" encoding="utf-8"?>
<boyoi.com.event.transfer.ALinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#4bf3a8">
<boyoi.com.event.transfer.BViewGroup
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="50dp"
android:background="#4a8f8f">
<boyoi.com.event.transfer.CViewGroup
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="50dp"
android:background="#847834">
<boyoi.com.event.transfer.DView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="50dp"
android:background="#765982">
</boyoi.com.event.transfer.DView>
</boyoi.com.event.transfer.CViewGroup>
</boyoi.com.event.transfer.BViewGroup>
</boyoi.com.event.transfer.ALinearLayout>
布局文件相关代码
/**
* Created by yishujun on 16/6/12.
*/
public class ALinearLayout extends LinearLayout{
private final String TAG = "ALinearLayout";
public ALinearLayout(Context context) {
super(context);
}
public ALinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ALinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.i(TAG,"dispatchTouchEvent" );
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.i(TAG,"onInterceptTouchEvent" );
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(TAG,"onTouchEvent" );
return super.onTouchEvent(event);
}
}
/**
* Created by yishujun on 16/6/12.
*/
public class BViewGroup extends ViewGroup{
private final String TAG = "BViewGroup";
public BViewGroup(Context context) {
super(context);
}
public BViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
}
public BViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//该demo主要讲解android的事件传递,这里不解释,如有疑问请关注我接下来的自定义view相关博客
measureChildren(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//该demo主要讲解android的事件传递,这里不解释,,如有疑问请关注我接下来的自定义view相关博客
//下面为什么这么处理,因为例子中只有一共孩子,getChildAt(0)只能是只有一个孩子,不然得不到我们要的效果
View childView = getChildAt(0);
childView.layout(l, t, r-2*l, b-2*t);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.i(TAG,"dispatchTouchEvent" );
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.i(TAG,"onInterceptTouchEvent" );
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(TAG,"onTouchEvent" );
return super.onTouchEvent(event);
}
}
/**
* Created by yishujun on 16/6/12.
*/
public class CViewGroup extends ViewGroup {
private final String TAG = "CViewGroup";
public CViewGroup(Context context) {
super(context);
}
public CViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//该demo主要讲解android的事件传递,这里不解释,如有疑问请关注我接下来的自定义view相关博客
measureChildren(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//该demo主要讲解android的事件传递,这里不解释,,如有疑问请关注我接下来的自定义view相关博客
//下面为什么这么处理,因为例子中只有一共孩子,getChildAt(0)只能是只有一个孩子,不然得不到我们要的效果
View childView = getChildAt(0);
childView.layout(l, t, r-2*l, b-2*t);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.i(TAG,"dispatchTouchEvent" );
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.i(TAG,"onInterceptTouchEvent" );
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(TAG,"onTouchEvent" );
return super.onTouchEvent(event);
}
}
/**
* Created by yishujun on 16/6/12.
*/
public class DView extends View{
private final String TAG = "DView";
public DView(Context context) {
super(context);
}
public DView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public DView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 计算出自己的宽高
setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.i(TAG,"dispatchTouchEvent" );
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(TAG,"onTouchEvent" );
return super.onTouchEvent(event);
}
}
public class MainActivity extends AppCompatActivity {
private final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.i(TAG,"dispatchTouchEvent" );
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(TAG,"onTouchEvent" );
return super.onTouchEvent(event);
}
}
四:验证结果
测试用例一:所以的 touch事件都分发不拦截不消费,所有的dispatchTouchEvent,onInterceptTouchEvent,onTouchEven方法t都返回false,如以上代码默认代码,点击中间的D-View:
06-12 11:09:56.274 31712-31712/boyoi.com.event.transfer I/MainActivity: dispatchTouchEvent
06-12 11:09:56.274 31712-31712/boyoi.com.event.transfer I/ALinearLayout: dispatchTouchEvent
06-12 11:09:56.274 31712-31712/boyoi.com.event.transfer I/ALinearLayout: onInterceptTouchEvent
06-12 11:09:56.274 31712-31712/boyoi.com.event.transfer I/BViewGroup: dispatchTouchEvent
06-12 11:09:56.274 31712-31712/boyoi.com.event.transfer I/BViewGroup: onInterceptTouchEvent
06-12 11:09:56.274 31712-31712/boyoi.com.event.transfer I/CViewGroup: dispatchTouchEvent
06-12 11:09:56.274 31712-31712/boyoi.com.event.transfer I/CViewGroup: onInterceptTouchEvent
06-12 11:09:56.274 31712-31712/boyoi.com.event.transfer I/DView: dispatchTouchEvent
06-12 11:09:56.274 31712-31712/boyoi.com.event.transfer I/DView: onTouchEvent
06-12 11:09:56.274 31712-31712/boyoi.com.event.transfer I/CViewGroup: onTouchEvent
06-12 11:09:56.274 31712-31712/boyoi.com.event.transfer I/BViewGroup: onTouchEvent
06-12 11:09:56.274 31712-31712/boyoi.com.event.transfer I/ALinearLayout: onTouchEvent
06-12 11:09:56.274 31712-31712/boyoi.com.event.transfer I/MainActivity: onTouchEvent
06-12 11:09:56.377 31712-31712/boyoi.com.event.transfer I/MainActivity: dispatchTouchEvent
06-12 11:09:56.377 31712-31712/boyoi.com.event.transfer I/MainActivity: onTouchEvent
测试用例二:在mainActivity界面对touch事件不进行分发,mainActivity 里面的dispatchTouchEvent返回true 则结果:
06-12 11:26:24.288 31712-31712/boyoi.com.event.transfer I/MainActivity: dispatchTouchEvent
06-12 11:26:24.390 31712-31712/boyoi.com.event.transfer I/MainActivity: dispatchTouchEvent
由以上日志可知,如果Activity界面不进行事件分发那么事件将无法往下传递
测试用例三:在mainActivity界面对touch事件进行分发,但是在mainActivity 对该事件进行消费,不往下面传递,mainActivity 里面的 dispatchTouchEvent返回false,onTouchEvent 返回true 则结果:
06-12 11:32:06.521 31712-31712/boyoi.com.event.transfer I/MainActivity: dispatchTouchEvent
06-12 11:32:06.521 31712-31712/boyoi.com.event.transfer I/ALinearLayout: dispatchTouchEvent
06-12 11:32:06.521 31712-31712/boyoi.com.event.transfer I/ALinearLayout: onInterceptTouchEvent
06-12 11:32:06.521 31712-31712/boyoi.com.event.transfer I/BViewGroup: dispatchTouchEvent
06-12 11:32:06.521 31712-31712/boyoi.com.event.transfer I/BViewGroup: onInterceptTouchEvent
06-12 11:32:06.521 31712-31712/boyoi.com.event.transfer I/CViewGroup: dispatchTouchEvent
06-12 11:32:06.521 31712-31712/boyoi.com.event.transfer I/CViewGroup: onInterceptTouchEvent
06-12 11:32:06.521 31712-31712/boyoi.com.event.transfer I/DView: dispatchTouchEvent
06-12 11:32:06.521 31712-31712/boyoi.com.event.transfer I/DView: onTouchEvent
06-12 11:32:06.521 31712-31712/boyoi.com.event.transfer I/CViewGroup: onTouchEvent
06-12 11:32:06.521 31712-31712/boyoi.com.event.transfer I/BViewGroup: onTouchEvent
06-12 11:32:06.522 31712-31712/boyoi.com.event.transfer I/ALinearLayout: onTouchEvent
06-12 11:32:06.522 31712-31712/boyoi.com.event.transfer I/MainActivity: onTouchEvent
06-12 11:32:06.634 31712-31712/boyoi.com.event.transfer I/MainActivity: dispatchTouchEvent
06-12 11:32:06.634 31712-31712/boyoi.com.event.transfer I/MainActivity: onTouchEvent
结果与测试用例一样的效果
测试用例四:在CViewGroup界面对touch事件进行拦截, 并自己消费该事件,CViewGroup 里面的 onInterceptTouchEvent返回true,onTouchEvent 返回true 则结果:
06-12 11:38:28.949 31712-31712/boyoi.com.event.transfer I/MainActivity: dispatchTouchEvent
06-12 11:38:28.949 31712-31712/boyoi.com.event.transfer I/ALinearLayout: dispatchTouchEvent
06-12 11:38:28.949 31712-31712/boyoi.com.event.transfer I/ALinearLayout: onInterceptTouchEvent
06-12 11:38:28.949 31712-31712/boyoi.com.event.transfer I/BViewGroup: dispatchTouchEvent
06-12 11:38:28.949 31712-31712/boyoi.com.event.transfer I/BViewGroup: onInterceptTouchEvent
06-12 11:38:28.949 31712-31712/boyoi.com.event.transfer I/CViewGroup: dispatchTouchEvent
06-12 11:38:28.949 31712-31712/boyoi.com.event.transfer I/CViewGroup: onTouchEvent
由以上日志可以看到,事件传递到CViewGroup就不在往下传递了,也不往上传递了,自己消费了
测试用例五:在CViewGroup界面对touch事件进行拦截, 自己不消费该事件,CViewGroup 里面的 onInterceptTouchEvent返回true,onTouchEvent 返回false 则结果:
06-12 11:41:23.857 31712-31712/boyoi.com.event.transfer I/MainActivity: dispatchTouchEvent
06-12 11:41:23.857 31712-31712/boyoi.com.event.transfer I/ALinearLayout: dispatchTouchEvent
06-12 11:41:23.857 31712-31712/boyoi.com.event.transfer I/ALinearLayout: onInterceptTouchEvent
06-12 11:41:23.857 31712-31712/boyoi.com.event.transfer I/BViewGroup: dispatchTouchEvent
06-12 11:41:23.857 31712-31712/boyoi.com.event.transfer I/BViewGroup: onInterceptTouchEvent
06-12 11:41:23.857 31712-31712/boyoi.com.event.transfer I/CViewGroup: dispatchTouchEvent
06-12 11:41:23.857 31712-31712/boyoi.com.event.transfer I/CViewGroup: onInterceptTouchEvent
06-12 11:41:23.857 31712-31712/boyoi.com.event.transfer I/CViewGroup: onTouchEvent
06-12 11:41:23.857 31712-31712/boyoi.com.event.transfer I/BViewGroup: onTouchEvent
06-12 11:41:23.857 31712-31712/boyoi.com.event.transfer I/ALinearLayout: onTouchEvent
06-12 11:41:23.857 31712-31712/boyoi.com.event.transfer I/MainActivity: onTouchEvent
06-12 11:41:23.936 31712-31712/boyoi.com.event.transfer I/MainActivity: dispatchTouchEvent
06-12 11:41:23.936 31712-31712/boyoi.com.event.transfer I/MainActivity: onTouchEvent
由以上日志可以看到,事件传递到CViewGroup就不在往下传递了,但是自己也并未消费,而是将该事件传递给父容器了
好了其他的测试例子自己可以进行尝试,我就不一一尝试了,如果有疑问或者错误需要纠正请留言,我一般看到会及时回答,谢谢交流沟通。后面接下来我还会写一些自定义控件来阐述事件传递在什么时候用和怎么用,希望支持鼓励。