目录
1.Touch(点击事件)事件的相关细节(发生触摸的位置、时间等)被封装成MotionEvent对象。
2. onTouchEvent()和onTouch()何时使用?
3. onTouchEvent()和onTouch()方法的返回值类型
4. onTouchEvent()和onTouch()方法优先级及控制关系
5. onTouch和onTouchEvent有什么区别,又该如何使用?
8. 为什么给ListView引入了一个滑动菜单的功能,ListView就不能滚动了?
9. 为什么图片轮播器里的图片使用Button而不用ImageView?
本文章作为学习总结
尊重原创老师 参考书集《《Android开发艺术探索》(任玉刚)(3)》及部分前辈网络总结,如涉及版权问题,请联系。
部分网络总结详见:
三、View的事件分发机制:
1.Touch(点击事件)事件的相关细节(发生触摸的位置、时间等)被封装成MotionEvent对象。
所以传入参数之后完整的方法展示:onTouchEvent(MotionEvent event)和onTouch(View v, MotionEventevent)。参数event为手机屏幕触摸事件封装类的对象,其中封装了该事件的所有信息,例如触摸的位置、触摸的类型以及触摸的时间等。该对象会在用户触摸手机屏幕时被创建。
2. onTouchEvent()和onTouch()何时使用?
①onTouchEvent(MotionEvent event)是View中定义的方法,而且是Public类型,所以Activity、ViewGroup、View均可以调用这个方法,最常见的关于这块的知识点在Android事件分发中,onTouchEvent()是每个事件处理对象都有的方法,用于根据下层的onTouchEvent()返回值类型判断是否调用,最常见的用法是自定义View时写入到view中,从而让该View获取用户对手机屏幕的各种操作,并对不同类型的操作实现不同的反馈,属于一个宏观的屏幕触摸监控方法;
②onTouch((View v, MotionEvent event)是View.OnTouchListener接口中实现的唯一方法,接收两个参数,第二个参数是之前提过的event事件对象,第一个参数是一个具体的view类型对象,这就意味着onTouch()方法必须和某个控件进行绑定,即某个控件实现了View.OnTouchListener接口,才能调用onTouch()方法。
3. onTouchEvent()和onTouch()方法的返回值类型
均是布尔型的返回值,所以完整的代码语句展示如下:
①public boolean onTouchEvent(MotionEventevent){
return true;
}
该方法的返回值是当已经完整地处理了该事件且不希望其他回调方法再次处理时返回true,否则返回false。即view如果通过onTouchEvent()方法处理了事件,不希望也不必要传到ViewGroup中时,即返回true,如果view处理不了事件,需要往上级传时,返回值为false,同理ViewGroup和Activity之间事件的控制也是如此。
②public boolean onTouch(View v, MotionEventevent) {
return true;
}
该方法的返回值类型主要用于控制是否执行onTouchEvent()方法及onTouchEvent()方法内部内置的各种click点击事件是否执行。
利用此特性可以单独拦截某个View的点击事件,比如
private void initView() {
Button btn_onTouch = findViewById(R.id.test_on_touch_btn);
LinearLayout ll_onTouch = findViewById(R.id.test_on_touch_ll);
// 重写父布局的onTouch()方法
ll_onTouch .setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
// 拦截底部布局的点击事件 返回值为true 消费事件
return true;
}
});
btn_onTouch.setOnClickListener(this);
ll_onTouch .setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.test_on_touch_btn:
Toast.makeText(this, "点击了按钮", Toast.LENGTH_SHORT).show();
break;
case R.id.test_on_touch_ll:
Toast.makeText(this, "点击了布局", Toast.LENGTH_SHORT).show();
break;
}
}
4. onTouchEvent()和onTouch()方法优先级及控制关系
①如果onTouch()方法返回值是true(事件被消费)时,则onTouchEvent()方法将不会被执行;
②只有当onTouch()方法返回值是false(事件未被消费,向下传递)时,onTouchEvent方法才被执行。
由此可见,给View设置监听OnTouchListener时,重写的onTouch()方法,其优先级比onTouchEvent()要高,假如onTouch方法返回false,会接着触发onTouchEvent,反之onTouchEvent方法不会被调用。内置诸如click事件的实现等等都基于onTouchEvent,假如onTouch返回true,这些事件将不会被触发。
可以看出,平时我们使用的OnClickListener,其优先级最低,即处于事件传递的尾端。
5. onTouch和onTouchEvent有什么区别,又该如何使用?
从源码中可以看出,这两个方法都是在View的dispatchTouchEvent中调用的,onTouch优先于onTouchEvent执行。如果在onTouch方法中通过返回true将事件消费掉,onTouchEvent将不会再执行。
另外需要注意的是,onTouch能够得到执行需要两个前提条件,第一mOnTouchListener的值不能为空,第二当前点击的控件必须是enable的。因此如果你有一个控件是非enable的,那么给它注册onTouch事件将永远得不到执行。对于这一
6.示例
比如说你当前有一个非常简单的项目,只有一个Activity,并且Activity中只有一个按钮。你可能已经知道,如果想要给这个按钮注册一个点击事件,只需要调用:
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Log.d("TAG", "onClick execute");
}
});
这样在onClick方法里面写实现,就可以在按钮被点击的时候执行。你可能也已经知道,如果想给这个按钮再添加一个touch事件,只需要调用:
button.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.d("TAG", "onTouch execute, action " + event.getAction());
return false;
}
});
OnTouchListener优先级比onTouchEvent高;OnClickListener最低。
onTouch方法里能做的事情比onClick要多一些,比如判断手指按下、抬起、移动等事件。那么如果我两个事件都注册了,哪一个会先执行呢?我们来试一下就知道了,运行程序点击按钮,打印结果如下:
可以看到,onTouch是优先于onClick执行的,并且onTouch执行了两次,一次是ACTION_DOWN,一次是ACTION_UP(你还可能会有多次ACTION_MOVE的执行,如果你手抖了一下)。因此事件传递的顺序是先经过onTouch,再传递到onClick。
细心的朋友应该可以注意到,onTouch方法是有返回值的,这里我们返回的是false,如果我们尝试把onTouch方法里的返回值改成true,再运行一次,结果如下:
我们发现,onClick方法不再执行了!为什么会这样呢?你可以先理解成onTouch方法返回true就认为这个事件被onTouch消费掉了,因而不会再继续向下传递。
7.MotionEvent:点击事件;
点击事件的分发过程由三个很重要的方法来共同完成:dispathTouchEvent、onInterceptTouchEvent和onTouchevent;
- public boolean dispatchTouchEvent(MotionEvent ev)
进行事件分发,
如果事件能够传递给当前的View,那么次方法一定会被调用,
返回结果受当前View的onTouchEvent和下级View的onInterceptTouchEvent方法的影响,
表示是否消耗当前事件。
- public boolean onInterceptTouchEvent(MotionEvent ev)
在上述方法内部调用,
用来判断是否拦截某个事件,如果当前View拦截了某个事件,那么在同一个事件序列当中,此方法不会再次调用,
返回结果表示是否拦截当前事件。
- public boolean onTouchevent(MotionEvent ev)
在dispathTouchEvent方法中调用,用来处理点击事件,
返回结果表示是否消耗当前事件,如果不消耗,则在统一序列中,当前View无法再次接收到事件。
8. 为什么给ListView引入了一个滑动菜单的功能,ListView就不能滚动了?
如果你阅读了Android滑动框架完全解析,教你如何一分钟实现滑动菜单特效这篇文章,你应该会知道滑动菜单的功能是通过给
ListView注册了一个touch事件来实现的。如果你在onTouch方法里处理完了滑动逻辑后返回true,那么ListView本身的滚动事件就被屏蔽了,自然也就无法滑动(原理同前面例子中按钮不能点击),因此解决办法就是在onTouch方法里返回false。
9. 为什么图片轮播器里的图片使用Button而不用ImageView?
提这个问题的朋友是看过了Android实现图片滚动控件,含页签功能,让你的应用像淘宝一样炫起来 这篇文章。当时我在图片轮播器里使用Button,主要就是因为Button是可点击的,而ImageView是不可点击的。如果想要使用ImageView,可以有两种改法。第一,在ImageView的onTouch方法里返回true,这样可以保证ACTION_DOWN之后的其它action都能得到执行,才能实现图片滚动的效果。第二,在布局文件里面给ImageView增加一个android:clickable="true"的属性,这样ImageView变成可点击的之后,即使在onTouch里返回了false,ACTION_DOWN之后的其它action也是可以得到执行的。
10.前辈总结
总结一下流程:
事件分发从Action_Down开始,最初由Activity的dispatchTouchEvent()方法接收,不拦截不中断的正常分发流程:
Activity的disPatchTouchEvent()方法到PhoneWindow的superDispatchTouchEvent方法,再到DecorView的superDispatchTouchEvent方法,再到ViewGroup的dispatchTouchEvent方法,在ViewGroup的dispatchTouchEvent方法中判断是否拦截,若拦截调用ViewGroup的onTouchEvent方法,该ViewGroup消费掉;若不拦截,该ViewGroup遍历子View根据点击的位置等条件判断是否为接收事件的子View,是,则分发给该子View的dispatchTouchEvent()方法,然后会调用View的onTouchEvent方法,在onTouchEvent方法中会判断该子View是否可点击,是,则事件最终传递到View的onClick方法消费;否则,事件返回向上传递,直到消费或者终止。
在dispatchTouchEvent()方法中返回true或者false,事件不向下传递,只用调用super.dispatchTouchEvent方法,事件才会向下传递。
在onTouchEvent()方法中返回true,事件在该方法中消费,不会向下或者向上传递;返回super.onTouchEvent方法,将会调用ViewonTouchEvent方法,判断长按事件和点击事件的执行条件存不存在,存在则会在点击事件中消费。
在onInterceptTouchEvent()方法中返回true表示拦截事件,事件可能会在该ViewGroup中消费掉;返回false表示事件继续往下传递
当某个View的onTouchEvent()返回true,那么事件不会向下或者向上传递,而Action_MOVE和Action_UP事件将会在该View的onTouchEvent方法中处理
ACTION_DOWN事件,哪个View的onTouchEvent 返回true,哪个view消费此down事件,但是需要逐层传递,直到找到消费点。而时候的move和up时间直接找到消费点,相当于走了捷径,走捷径的原因就是down事件在前面探路了,直接返回了true。
onTouchEvent消费事件的情况:在哪个View的onTouchEvent 返回true,那么ACTION_MOVE和ACTION_UP的事件从上往下传到这个View后就不再往下传递了,而直接传给自己的onTouchEvent 并结束本次事件传递过程。
对于ACTION_MOVE、ACTION_UP总结:ACTION_DOWN事件在哪个控件消费了(return true), 那么ACTION_MOVE和ACTION_UP就会从上往下(通过dispatchTouchEvent)做事件分发往下传,就只会传到这个控件,不会继续往下传,如果ACTION_DOWN事件是在dispatchTouchEvent消费,那么事件到此为止停止传递,如果ACTION_DOWN事件是在onTouchEvent消费的,那么会把ACTION_MOVE或ACTION_UP事件传给该控件的onTouchEvent处理并结束传递。
四、View的事件分发源码解析:
p144--3.4.2