要想做一个用户体验好的app,弄清楚系统对触摸事件的处理逻辑是最基本的。一个完整的触摸事件由一个ActionDown、N个ActionMove和一个actionup组成,比如我们点击屏幕的时候,如果只是点击不滑动N的值就为1,如果滑动N的值就会不断的增加。要处理好触摸事件,只需要了解3个方法就可以了,onTouchEvent()、onInterceptTouchEvent()、dispatchTouchEvent(),其中的onInterceptTouchEvent只有ViewGroup才有。
1、onTouchEvent():用来消费触摸事件。
2、onInterceptTouchEvent():用来拦截事件。
3、dispatchTouchEvent():用来分发触摸事件。
一个触摸事件感知的顺序是从外到里的,也就是说最先感知到的是Activity,然后是最外层的viewGroup,一直到最后的View,消费的时候则相反,也就是从里到外。3个方法调用德尔顺序是:dispatch--->intercept--->touchevent,假如一个ViewGroup接收到一个触摸事件,那么最先调用的是这个ViewGroup的dispatchTouchEvent()方法,然后调用这个ViewGroup的onInterceptTouchEvent(),然后调用这个ViewGroup里面的View的dispatchTouchEvent(),因为View没有onInterceptTouchEvent方法,所以就会调用这个View的onTouchEvent方法,然后再调用ViewGroup的onTouchEvent方法,接着ViewGroup就会把这个事件传递给自己的父类的onTouchEvent方法(这样德尔逻辑是我们没有改变系统自己的传递逻辑的情况下,后面将详细的演示改变传递逻辑后的情况),如果传到Activity的onTouchEvent还没有被消费掉的话那么这个事件就会消失。
下面我们来具体的演示事件处理情况,首先要分两种情况,一种是只有一个View的情况,一种是有一个viewGroup然后ViewGroup里面还含有View的情况(ViewGroup含有多个ViewGroup的情况也是类似的,就不一样举例说明了)。这两种情况我都是用自定义View的形式来演示的。
情况一、只含有View的情况
这里就新建一个类继承Button,然后我们重写Button的onTouchEvent和dispatchTouchEvent方法,自定义view的代码如下:
public class OnlyButton extends Button {
private String TAG = "OnlyButton";
public OnlyButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
public OnlyButton(Context context) {
super(context);
}
// 触摸消费
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "onTouchEvent---ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "onTouchEvent---ACTION_MOVE");
break;
case MotionEvent.ACTION_CANCEL:
Log.e(TAG, "onTouchEvent---ACTION_CANCEL");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG, "onTouchEvent---ACTION_UP");
break;
}
return super.onTouchEvent(event);
}
// 触摸分发
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "dispatchTouchEvent---ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "dispatchTouchEvent---ACTION_MOVE");
break;
case MotionEvent.ACTION_CANCEL:
Log.e(TAG, "dispatchTouchEvent---ACTION_CANCEL");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG, "dispatchTouchEvent---ACTION_UP");
break;
}
return super.dispatchTouchEvent(event);
}
}
然后看下Activity里面的代码:
public class SingleView extends Activity {
private String TAG = "SingleView";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_single_view);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "dispatchTouchEvent---ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "dispatchTouchEvent---ACTION_MOVE");
break;
case MotionEvent.ACTION_CANCEL:
Log.e(TAG, "dispatchTouchEvent---ACTION_CANCEL");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG, "dispatchTouchEvent---ACTION_UP");
break;
}
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "onTouchEvent---ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "onTouchEvent---ACTION_MOVE");
break;
case MotionEvent.ACTION_CANCEL:
Log.e(TAG, "onTouchEvent---ACTION_CANCEL");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG, "onTouchEvent---ACTION_UP");
break;
}
return super.onTouchEvent(event);
}
}
Activity布局文件的代码:
<com.xinxue.androidevents.views.OnlyButton
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="测试我" />
可以看到Activity界面只有一个自定义View,我们在自定义里面重写了两个事件处理的方法,同样的我们也重写了Activity的事件处理方法,下面是运行结果:
其中的singleview是我们的Activity,onlyButton是我们的自定义View,这些是系统默认的事件处理逻辑。上面提到的处理事件3个方法的返回值都是返回一个Boolean类型的值,dispatch的Boolean值代码是否给子View分发事件,intercept的Boolean代表是否拦截这次事件,touchevent的Boolean值代表是否消费这次事件。ok,知道了这些我们就来用自己的逻辑处理点击事件,我们发现上面的逻辑没有调用到Activity的onTouchevent方法,下面我们把onlyButton的onTouchEvent方法的返回值改为false,意思就是告诉Activity触摸事件没有被处理,那么Activity肯定会自己处理这次事件,我们看看是不是我们猜想的这样呢??看运行结果:
我们发现果真是这个逻辑,我们把代码改回去,onlyButton的dispatch方法返回值改为false,表示不给自己分发这次事件,按上面的逻辑,应该不会再调用onTouchEvent方法了,我们来看看是不是这样的呢??看运行结果:
不但结果是我们猜想的那样,而且我们发现在actionup事件触发的时候都没有再调用onlybutton的dispatch方法了,直接自己处理了,这样我们我们猜想:如果dispatch事件返回false,以后的事件处理都不会再调用这个View的dispatch方法,直接在父容器中就消费掉。后面我们将从含有ViewGroup的方法中来印证这个结论。
下面来一个疯狂的举动,都知道dispatch返回false是不再给子View分发事件,而Activity是整个UI的最上级,那么我们给它的dispatch返回false是不是就可以达到点击屏幕没有反应了呢??我们来看下运行效果:哎呀,果真如我们想的那样,运行了这么多种情况,你是不是对触摸事件由所领悟了呢??不要急,下面我们来看另一种情况。
情况二、含有ViewGroup的情况
这里自定义一个View继承自Linearlayout,Activity里面的代码不变,布局文件如下:
<com.xinxue.androidevents.views.ViewGroupAndView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#f00" >
<com.xinxue.androidevents.views.OnlyButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="按钮" />
</com.xinxue.androidevents.views.ViewGroupAndView>
里面包含了情况一定义的View,下面来看看viewGroup里面的代码:
public class ViewGroupAndView extends LinearLayout {
public ViewGroupAndView(Context context, AttributeSet attrs) {
super(context, attrs);
}
private String TAG = "ViewGroupAndView";
public ViewGroupAndView(Context context) {
super(context);
}
// 触摸消费
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "onTouchEvent---ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "onTouchEvent---ACTION_MOVE");
break;
case MotionEvent.ACTION_CANCEL:
Log.e(TAG, "onTouchEvent---ACTION_CANCEL");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG, "onTouchEvent---ACTION_UP");
break;
}
return super.onTouchEvent(event);
}
// 触摸分发
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "dispatchTouchEvent---ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "dispatchTouchEvent---ACTION_MOVE");
break;
case MotionEvent.ACTION_CANCEL:
Log.e(TAG, "dispatchTouchEvent---ACTION_CANCEL");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG, "dispatchTouchEvent---ACTION_UP");
break;
}
return super.dispatchTouchEvent(event);
}
// 拦截方法
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "onInterceptTouchEvent---ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "onInterceptTouchEvent---ACTION_MOVE");
break;
case MotionEvent.ACTION_CANCEL:
Log.e(TAG, "onInterceptTouchEvent---ACTION_CANCEL");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG, "onInterceptTouchEvent---ACTION_UP");
break;
}
return super.onInterceptTouchEvent(ev);
}
}
我们重写了一个intercept方法,下面来看看默认的运行逻辑:
其中multiview为Activity,viewGroupandView为定义的ViewGroup,我们看到先调用的是Activity的dispatch方法,然后是viewgroup的dispatch方法,而ViewGroup有一个是否拦截事件的方法,用来处理是否给自己的孩子分发事件,ViewGroup首先会调用自己的这个方法用来判断是否给自己的孩子分发,默认是给的(这算不算一种伟大的父爱呢?),调用孩子的dispatch方法之后,孩子自己就会判断我是否有能力处理这个事件,我们发现孩子虽然没有处理这个事件(没有返回true),但是这个事件却不再往上发了,直接就消失了!!这样我们就可以得出事件传到最底层的View中,默认就不会往上传递了,如果我们在这个View返回false,告诉他自己不能处理,情况又是怎么样的呢?会不会给Activity去处理呢??看运行结果:
我们发现也没有往上传,这次事件是真的消失了!!!那么如果view处理了,也就是返回了true,会不会告诉自己的父亲呢?我们来试验一下:
好吧,我们该下结论了:不管最底层的view处不处理本次事件,这次事件都会消失掉!!
照着我们现在的逻辑,是不是ViewGroup的intercept返回true,表示拦截这次事件,自己的孩子就收不到本次事件了呢??来看结果:
啊哈,果真如我们想的那样~~~~~~~~·利用这个我们就可以屏蔽子类的触摸事件啦!等等~仔细看上面的结果有没有发现一个问题,对!如果你不给自己的孩子分发,那么下次我你也收不到触摸事件了(父亲必须照顾自己的孩子!)!!!这样的设计是不是很有意思呢~~~~
ok,两种情况就介绍完了,还有一个就是点击事件和触摸事件一块的时候是怎么样的。其实吧,点击事件是在onTouchEvent里面调用的,也就是说点击事件是在触摸事件的后面才被触发的,点击事件能否触摸还要看触摸事件是否返回true,如果为true就不再触发了!
扫描关注我的微信公众号:
总结:
对于触摸事件分两种情况,一种是只有View的一种是含有ViewGroup的,如果含有ViewGroup的话,事件传递到最里面的view就不会再往上传了,也就是说不管最底层的view是否有能力处理这个事件,本次事件都会消失掉,我们要拦截子类的触摸事件只需要给父类的intercept返回true就可以了,但是如果返回了true下次就不会再收到触摸事件。其实对于触摸事件,我们只需要知道那3个方法是干嘛的,返回值代表什么意思,就非常容易弄清楚这块知识点,ok,今天的内容就说到这里吧,如果文中你们发现有什么问题,欢迎给我留言指正!!!