首页 > 系统 > Android > 正文

Android编程——Touch 事件的分发和消费机制

2019-11-09 15:49:04
字体:
来源:转载
供稿:网友

介绍

Android 中与 Touch 事件相关的方法包括: dispatchTouchEvent(MotionEvent ev) onInterceptTouchEvent(MotionEvent ev) onTouchEvent(MotionEvent ev) 能够响应这些方法的控件包括:ViewGroup 及其子类、Activity。方法与控件的对应关系如下表所示:

这里写图片描述

从这张表中我们可以看到 ViewGroup 及其子类对与 Touch 事件相关的三个方法均能响应,而 Activity 对 onInterceptTouchEvent(MotionEvent ev) 也就是事件拦截不进行响应。

另外需要注意的是 View 对 dispatchTouchEvent(MotionEvent ev) 和 onInterceptTouchEvent(MotionEvent ev) 的响应的前提是可以向该 View 中添加子 View,如果当前的 View 已经是一个最小的单元 View(比如 TextView),那么就无法向这个最小 View 中添加子 View,也就无法向子 View 进行事件的分发和拦截,所以它没有 dispatchTouchEvent(MotionEvent ev) 和 onInterceptTouchEvent(MotionEvent ev),只有 onTouchEvent(MotionEvent ev)。

Touch 事件分析

▐ 事件分发:public boolean dispatchTouchEvent(MotionEvent ev)

Touch 事件发生时 Activity 的 dispatchTouchEvent(MotionEvent ev) 方法会以隧道方式(从根元素依次往下传递直到最内层子元素或在中间某一元素中由于某一条件停止传递)将事件传递给最外层 View 的 dispatchTouchEvent(MotionEvent ev) 方法,并由该 View 的 dispatchTouchEvent(MotionEvent ev) 方法对事件进行分发。dispatchTouchEvent 的事件分发逻辑如下:

如果 return true,事件会分发给当前 View 并由 dispatchTouchEvent 方法进行消费,同时事件会停止向下传递; 如果 return false,事件分发分为两种情况: 如果当前 View 获取的事件直接来自 Activity,则会将事件返回给 Activity 的 onTouchEvent 进行消费; 如果当前 View 获取的事件来自外层父控件,则会将事件返回给父 View 的 onTouchEvent 进行消费。 如果返回系统默认的 super.dispatchTouchEvent(ev),事件会自动的分发给当前 View 的 onInterceptTouchEvent 方法。

▐ 事件拦截:public boolean onInterceptTouchEvent(MotionEvent ev)

在外层 View 的 dispatchTouchEvent(MotionEvent ev) 方法返回系统默认的 super.dispatchTouchEvent(ev) 情况下,事件会自动的分发给当前 View 的 onInterceptTouchEvent 方法。onInterceptTouchEvent 的事件拦截逻辑如下:

如果 onInterceptTouchEvent 返回 true,则表示将事件进行拦截,并将拦截到的事件交由当前 View 的 onTouchEvent 进行处理; 如果 onInterceptTouchEvent 返回 false,则表示将事件放行,当前 View 上的事件会被传递到子 View 上,再由子 View 的 dispatchTouchEvent 来开始这个事件的分发; 如果 onInterceptTouchEvent 返回 super.onInterceptTouchEvent(ev),事件默认会被拦截,并将拦截到的事件交由当前 View 的 onTouchEvent 进行处理。 ▐ 事件响应:public boolean onTouchEvent(MotionEvent ev)

在 dispatchTouchEvent 返回 super.dispatchTouchEvent(ev) 并且 onInterceptTouchEvent 返回 true 或返回 super.onInterceptTouchEvent(ev) 的情况下 onTouchEvent 会被调用。onTouchEvent 的事件响应逻辑如下:

如果事件传递到当前 View 的 onTouchEvent 方法,而该方法返回了 false,那么这个事件会从当前 View 向上传递,并且都是由上层 View 的 onTouchEvent 来接收,如果传递到上面的 onTouchEvent 也返回 false,这个事件就会“消失”,而且接收不到下一次事件。 如果返回了 true 则会接收并消费该事件。 如果返回 super.onTouchEvent(ev) 默认处理事件的逻辑和返回 false 时相同。 到这里,与 Touch 事件相关的三个方法就分析完毕了。下面的内容会通过各种不同的的测试案例来验证上文中三个方法对事件的处理逻辑。

Touch 案例介绍

这里写图片描述 当一个Touch事件(触摸事件为例)到达根节点,即Acitivty的ViewGroup时,它会依次下发,下发的过程是调用子View(ViewGroup)的dispatchTouchEvent方法实现的。简单来说,就是ViewGroup遍历它包含着的子View,调用每个View的dispatchTouchEvent方法,而当子View为ViewGroup时,又会通过调用ViwGroup的dispatchTouchEvent方法继续调用其内部的View的dispatchTouchEvent方法。上述例子中的消息下发顺序是这样的:①-②-⑤-⑥-⑦-③-④。dispatchTouchEvent方法只负责事件的分发,它拥有boolean类型的返回值,当返回为true时,顺序下发会中断。在上述例子中如果⑤的dispatchTouchEvent返回结果为true,那么⑥-⑦-③-④将都接收不到本次Touch事件。来个简单版的代码加深理解:

public boolean dispatchTouchEvent(MotionEvent ev){ ....//其他处理,在此不管 View[] views=getChildView(); for(int i=0;i<views.length;i++){ //判断下Touch到屏幕上的点在该子View上面 if(...){ if(views[i].dispatchTouchEvent(ev)) return true; } } ...//其他处理,在此不管 } public boolean dispatchTouchEvent(MotionEvent ev){ ....//其他处理,在此不管 return false; }

在此可以看出,ViewGroup的dispatchTouchEvent是真正在执行“分发”工作,而View的dispatchTouchEvent方法,并不执行分发工作,或者说它分发的对象就是自己,决定是否把touch事件交给自己处理,而处理的方法,便是onTouchEvent事件 ViewGroup还有个onInterceptTouchEvent,看名字便知道这是个拦截事件。这个拦截事件需要分两种情况来说明:

1.假如我们在某个ViewGroup的onInterceptTouchEvent中,将Action为Down的Touch事件返回true,那便表示将该ViewGroup的所有下发操作拦截掉,这种情况下,mTarget会一直为null,因为mTarget是在Down事件中赋值的。由于mTarge为null,该ViewGroup的onTouchEvent事件被执行。这种情况下可以把这个ViewGroup直接当成View来对待。

2.假如我们在某个ViewGroup的onInterceptTouchEvent中,将Acion为Down的Touch事件都返回false,其他的都返回True,这种情况下,Down事件能正常分发,若子View都返回false,那mTarget还是为空,无影响。若某个子View返回了true,mTarget被赋值了,在Action_Move和Aciton_UP分发到该ViewGroup时,便会给mTarget分发一个Action_Delete的MotionEvent,同时清空mTarget的值,使得接下去的Action_Move(如果上一个操作不是UP)将由ViewGroup的onTouchEvent处理。

总结

1.Touch事件分发中只有两个主角:ViewGroup和View。ViewGroup包含onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent三个相关事件。View包含dispatchTouchEvent、onTouchEvent两个相关事件。其中ViewGroup又继承于View。

2.ViewGroup和View组成了一个树状结构,根节点为Activity内部包含的一个ViwGroup。

3.触摸事件由Action_Down、Action_Move、Aciton_UP组成,其中一次完整的触摸事件中,Down和Up都只有一个,Move有若干个,可以为0个。

4.当Acitivty接收到Touch事件时,将遍历子View进行Down事件的分发。ViewGroup的遍历可以看成是递归的。分发的目的是为了找到真正要处理本次完整触摸事件的View,这个View会在onTouchuEvent结果返回true。

5.当某个子View返回true时,会中止Down事件的分发,同时在ViewGroup中记录该子View。接下去的Move和Up事件将由该子View直接进行处理。由于子View是保存在ViewGroup中的,多层ViewGroup的节点结构时,上级ViewGroup保存的会是真实处理事件的View所在的ViewGroup对象:如ViewGroup0-ViewGroup1-TextView的结构中,TextView返回了true,它将被保存在ViewGroup1中,而ViewGroup1也会返回true,被保存在ViewGroup0中。当Move和UP事件来时,会先从ViewGroup0传递至ViewGroup1,再由ViewGroup1传递至TextView。

6.当ViewGroup中所有子View都不捕获Down事件时,将触发ViewGroup自身的onTouch事件。触发的方式是调用super.dispatchTouchEvent函数,即父类View的dispatchTouchEvent方法。在所有子View都不处理的情况下,触发Acitivity的onTouchEvent方法。

7.onInterceptTouchEvent有两个作用:1.拦截Down事件的分发。2.中止Up和Move事件向目标View传递,使得目标View所在的ViewGroup捕获Up和Move事件。

实用

一般在新闻的页面中ViewPager中嵌套一个轮播图,请求父控件不要拦截 事件分发

当滑动到小viewpager的最后一个界面的时候,外面的viewpager要将小的viewpager的触摸事件拦截,当滑动小的viewpager的不是最后一个界面的时候,外面的viewpager不拦截小viewpager的触摸事件让小viewpager进行滑动操作创建自定义Viewpager进行操作//事件分发的@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) { //请求父控件不要拦截事件,true:不拦截,false:拦截 //getParent().requestDisallowInterceptTouchEvent(disallowIntercept); //1.需要判断是左右滑动还是上下滑动,因为只有左右才是viewpager手动滑动的操作 switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: //getParent().requestDisallowInterceptTouchEvent(false); //获取按下的x和y的坐标 downX = (int) ev.getX(); downY = (int) ev.getY(); break; case MotionEvent.ACTION_MOVE: //获取移动的x和y的坐标 int moveX = (int) ev.getX(); int moveY = (int) ev.getY(); //判断是上下还是左右滑动 if (Math.abs(moveX-downX) > Math.abs(moveY-downY)) { //左右 //从右往左,如果是最后一个条目,父控件拦截事件,实现切换界面的操作,如果不是最后一个条目,切换下一张图片 //getAdapter() : 获取ViewPager设置的adapter if (downX - moveX > 0 && getCurrentItem() == getAdapter().getCount()-1) { getParent().requestDisallowInterceptTouchEvent(false); }else if(downX - moveX > 0 && getCurrentItem() < getAdapter().getCount()-1){ getParent().requestDisallowInterceptTouchEvent(true); } //从左往右,如果是第一个条目,父控件拦截事件,打开侧拉菜单,如果不是第一个条目,切换到上一张图片 else if(downX - moveX < 0 && getCurrentItem() == 0){ getParent().requestDisallowInterceptTouchEvent(false); }else if(downX - moveX < 0 && getCurrentItem() > 0){ getParent().requestDisallowInterceptTouchEvent(true); } }else{ //上下 getParent().requestDisallowInterceptTouchEvent(false); } break; case MotionEvent.ACTION_UP: break; } return super.dispatchTouchEvent(ev);}布局文件中使用<com.itheima.zhbj97.ui.RoolViewPager android:id="@+id/menunewscenteritem_vp_viewpager" android:layout_width="match_parent" android:layout_height="185dp" ></com.itheima.zhbj97.ui.RoolViewPager>

ViewPager和View的事件响应规则

如果是缓慢的移动很短的距离,viewpager和view的事件都会执行如果是快速滑动很长的距离,view的事件会执行cancel事件,结束view的触摸操作,只去viewpager的事件具体操作 //设置view的触摸事件事件,实现按下viewpager停止自动滑动,抬起,viewpager重新进行自动滑动操作 rootView.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: //按下viewpager停止滑动 handler.removeCallbacksAndMessages(null);//取消handler发送延迟消息,如果是null,全部handler都会被取消发送消息 break; case MotionEvent.ACTION_UP: //抬起viewpager重新滑动 handler.sendEmptyMessageDelayed(0, 3000); break; case MotionEvent.ACTION_CANCEL: //view的事件取消执行的操作 handler.sendEmptyMessageDelayed(0, 3000); break; } //如果想要事件执行,返回true,返回事件不执行 return true; } });
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表