RestAPP-简洁的事件分发

   2016-11-16 0
核心提示:事件分发是一个重点也是难点,所以,本篇幅有点长,如果耐心看完本篇,相信读者会有收获的。同时,读者也可以自己写例子测试,毕竟,纸上得来终觉浅,绝知此事要躬行。但是,对于水平高的读者,其实最好的方式是看源代码,因为一切原因都可以从源头找到答案。

事件分发是一个重点也是难点,所以,本篇幅有点长,如果耐心看完本篇,相信读者会有收获的。同时,读者也可以自己写例子测试,毕竟, 纸上得来终觉浅,绝知此事要躬行 。但是,对于水平高的读者,其实 最好的方式是看源代码,因为一切原因都可以从源头找到答案

关于事件分发

关于事件分发,其实主要就是理解三个函数,这三个函数分别是 dispatchTouchEvent(MotionEvent ev)onInterceptTouchEvent(MotionEvent ev) 以及 onTouchEvent(MotionEvent ev) ,这里直接上一张图,这张图将分发事件中的重要的三个函数之间的关系表达的比较清晰,知道了关系以后,我们还需要了解这三个函数,了解一个函数其实无非就是理解它的作用,传入的参数以及返回值。

先说 dispatchTouchEvent(MotionEvent ev) :

这个函数的作用是分发事件,不管是ViewGroup还是View都有这个方法,它的返回值受本身的 onInterceptTouchEvent(MotionEvent ev)child.dispatchTouchEvent(MotionEvent ev) 共同影响(从上面那张图就可以看出来),返回的各个值的意义如下:

return true :表示该View内部消化掉了所有事件。

return false :事件在本层不再继续进行分发,这个 false 也就是本身的 dispatchTouchEvent(MotionEvent ev) 返回值,而这个返回值会回溯给上层控件的 dispatchTouchEvent(MotionEvent ev) ,表示自己没有接受这个事件,不管上层控件是 view 还是 viewGroup ,都是交由上层控件的 onTouchEvent(MotionEvent ev) 方法进行消费(如果本层控件已经是Activity,那么事件将被系统消费或处理)。

如果事件分发返回系统默认的 super.dispatchTouchEvent(ev) ,事件将分发给本层的事件拦截 onInterceptTouchEvent(MotionEvent ev) 方法进行处理,而不是super的onInterceptTouchEvent。因为 return super.dispatchTouchEvent(ev) 会去运行父viewGroup的 dispatchTouchEvent(ev) ,然后运行onInterceptTouchEvent,那么这个onInterceptTouchEvent是谁的呢?根据方法是基于对象的,所以就会运行child的 onInterceptTouchEvent(MotionEvent ev) 也就是本层的事件拦截器,而不是super的onInterceptTouchEvent。详情可以参考规则中的第十条。

然后是 onInterceptTouchEvent(MotionEvent ev) :

这个函数的作用是拦截事件,只有 ViewGroup 有这个方法,返回的各个值的意义如下:

return true :表示将事件进行拦截,并将拦截到的事件交由本层控件 的 onTouchEvent 进行处理;

return false :则表示不对事件进行拦截,事件得以成功分发到子View。并由子View的dispatchTouchEvent进行处理。

如果返回 super.onInterceptTouchEvent(ev) ,默认false,即表示不拦截该事件,这样事件才能以分发下去。

最后是 onTouchEvent(MotionEvent ev) :

这个函数的作用是处理触摸事件, ViewGroupView 都有这个方法,返回值的意义如下:

如果return true,表示onTouchEvent处理完事件后消费了此次事件。此时事件终结;

如果return fasle,则表示不响应事件,如果是ACTION_DOWN事件,那么该事件将会不断向上层View的onTouchEvent方法传递,直到某个View的onTouchEvent方法返回true,如果到了最顶层View还是返回false,那么事件就会交给Activity处理。且在同一个事件系列中,当前View无法再次接收到该事件序列,如果不是ACTION_DOWN事件,那么不会返回给父view的onTouchEvent处理,而是给Activity处理,并且该view可以继续接收该事件序列;

如果return super.onTouchEvent(event);,默认是true,即表示处理事件。那这个和return true有什么区别呢?从代码就可以看出来,return super.onTouchEvent(event)会执行super.onTouchEvent(event)这个方法。比如,当你继承EditText后,重写onTouchEvent(MotionEvent event)方法,如果你将return super.onTouchEvent(event);换成return true,就会发现当你按返回取消输入框,再次点击自定义EditText时就会无法弹出输入框,解决办法可以是将return true修改成return super.onTouchEvent(event),或者是在之前调用一次super.onTouchEvent(event)方法, 弹出输入框是在action为ACTION_UP的时候弹出的。

重要的知识点(大家拿本子记一下,高考必考啊)

1.一个viewGroup一旦决定拦截事件(这里分两种情况,一个是拦截了ACTION_DOWN事件,还有一个是没有子View满足分发事件的条件或者子view在ACTION_DOWN时返回了false),那么后面的事件序列都会交给它处理,并且不会再调用 onInterceptTouchEvent(ev) 方法, 当ACTION_DOWN事件成功传入子view的时候, 那么父ViewGroup在别的事件分发的时候,比如ACTION_MOVE,每次都会调用onInterceptTouchEvent来判断是否拦截当前事件。 也就是说,父ViewGroup的onInterceptTouchEvent不会再次调用的时机只是自己来处理这个事件,也就是自己的onTouchEvent被调用,只有这个时候才不会再次调用onInterceptTouchEvent,当事件传入子view来处理事件的时候,父ViewGroup都会每次都调用onInterceptTouchEvent来决定是否拦截当前事件。

2.dispatchTouchEvent无论返回true还是false,事件都不再进行分发, 只有当其返回super.dispatchTouchEvent(ev),才表明其具有向下层分发的愿望 ,但是是否能够分发成功,则需要经过事件拦截onInterceptTouchEvent的审核。事件是否向上传递处理是由onTouchEvent的返回值决定的。

3.正常情况下,一个事件序列只能被一个view拦截且消耗,因为,一旦决定拦截事件,那么这个事件只能被这个view消耗,并且它的onInterceptTouchEvent(ev)方法也不会 再次调用 (这里的 拦截 和规则一中的拦截是一样的。这里的 再次调用 是指当确定拦截事件后,除了在ACTION_DOWN时调用onInterceptTouchEvent(ev),后面都不调用,其实跟规则一中说的一样),如果你想这个事件序列被多个view拦截消耗,那么你可以在拦截事件的那个view中的onTouchEvent()方法中调用你想让其拦截事件的那个view的onTouchEvent()方法来实现。

4.view一旦用onTouchEvent()开始处理事件,如果没有处理ATION_DOWN事件,那么同一个事件序列中的事件也不会交给他处理,会回溯给他的父控件, 如果你处理了ACTION_DOWN但是没有处理ACTION_MOVE或者ACTION_UP,那么这个事件还是被你消耗,不会调用父控件的onTouchEvent方法,最后会是Activity处理,后面的事件还是继续交给你处理 。其实,这就类似现实,如果别人第一次叫你做事,你没做好,那么后面就都不会放心叫你做了,如果你第一次做好了,后面没做好,别人还是会给你做的,所以,第一次很重要。

5.view的onTouchEvent默认都是消费事件的(返回true),除非是不可点击的,也就是longClickable和clickable都为false,只有这个属性会影响view的onTouchEvent的返回值,别的属性不会,比如,Enabled属性, 就算是Enabled属性为false,也就是disable状态,view的onTouchEvent默认返回的还是true。

6.事件传递是由外向内的,即事件总是传递给父元素,再由父元素分发给子控件,通过requestDisallowInterceptTouchEvent();方法可以在子元素中干预父元素的事件分发过程, 但是不能干预ACTION_DOWN事件 ,因为当时ACTION_DOWN事件的时候,父元素会重置FLAG_DISALLOW_INTERCEPT标志位。

7.使用内部拦截法的时候,为了弄清楚顺序,我就直接调试,结果,运行到父元素的dispatchTouchEvent后,不会去调用父元素的onInterceptTouchEvent方法,直接就到了子元素的dispatchTouchEvent,依然会运行到子view的onTouchEvent,等到ACTION_UP的时候才会又跑到父元素中的dispatchTouchEvent和onInterceptTouchEvent去判断是否拦截ACTION_UP事件。我倒腾了一天,才发现是需要移动,也就是让move多次调用才行,因为事件是由外向内的,当第一次ACTION_MOVE事件到的时候,先运行父ViewGroup的dispatchTouchEvent方法,此时FLAG_DISALLOW_INTERCEPT依然是设置成true,所以,不会运行父ViewGroup的onInterceptTouchEvent方法,直接就会运行子view的dispatchTouchEvent方法,然后FLAG_DISALLOW_INTERCEPT被设置成false,于是当第二次的ACTION_MOVE到来的的时候,才会去运行父viewGroup的onInterceptTouchEvent方法,然后子view收到ACTION_CANCEL事件, 等到第三个ACTION_MOVE的时候父viewGroup才开始拦截事件。 但是因为我之前是调试,所以都只有一次move事件,结果就不一样了。也是醉了。并且使用内部拦截法的时候,ACTION_UP事件也会被父view拦截,不会传递到子view中,也就意味着子view的onClick事件不会响应,这一点要记住。

8.内部拦截法和外部拦截法的区别:内部拦截法需要到该事件的第三个的时候才有用,也就是该事件的第一个依然被子view得到,外部拦截法则是到第二个就有用了,子view不会得到该事件的任何一个,比如,拦截ACTION_MOVE的时候, 使用内部拦截法在拦截第三个ACTION_MOVE的时候才拦截了 ,因为第一个ACTION_MOVE会被子view得到, 而使用外部拦截法则是第二个ACTION_MOVE的时候就拦截了 ,因为子view不会得到ACTION_MOVE中的任何一个。详情可以见9,10。所以,使用外部拦截法要好点。

9.当viewGroup没有拦截ACTION_DOWN而拦截了ACTION_MOVE或者ACTION_UP的时候,那么,第一个被拦截的动作不会在viewGroup中的onTouchEvent中触发,也不会在子view的onTouchEvent中触发,而是子view会受到ACTION_CANCEL事件。该事件序列后面的事件都会被拦截,并且下一个同类型的事件传来时,不会再调用viewGroup的onInterceptTouchEvent方法,直接就调用viewGroup的onTouchEvent方法,这里解释一下,什么是第一个被拦截的动作,比如,多个move的时候,第一个move就不会被父view或者子view执行,感觉是这个事件变成了ACTION_CANCEL事件传递到了子view。也就是说,一旦在这种情况下,ACTION_UP事件永远不会被子view接收。 也就意味着,不管是使用外部拦截法还是内部拦截法,只要拦截了,那么子view就收不到ACTION_UP事件。 还有就是ViewGroup就不要拦截ACTION_UP了,因为这样大家都得不到ACTION_UP事件,何必呢?

10.为了讲述方便,当从一个ViewGroup分发事件到子ViewGroup时,在子ViewGroup的dispatchTouchEvent方法中调用父类的dispatchTouchEvent,发现不会继续调用父类的onInterceprTouchEvent,而是直接调用子ViewGroup的onInterceptTouchEvent,为什么在这里调用父类的dispatchTouchEvent不会跟着调用父类的onInterceptTouchEvent?我调试和看源码发现当运行到findChildWithAccessibilityFocus()方法时,view会变成接受到事件的view,然后就不知道了。水平还是看不懂源代码。其实这是因为我对java的理解有错误,基于方法都是基于对象的,所以在子viewGroup中调用父类的dispatchTouchEvent,也就是super.dispatchTouchEvent()时,这时会运行到父viewGroup的dispatchTouchEvent里,会调用onInterceptTouchEvent方法, 这时的onInterceptTouchEvent其实就已经是子viewGroup的onInterceptTouchEvent方法,而不是父ViewGroup的dispatchTouchEvent方法,因为方法是基于对象的。

11.onClick发生的前提就是可点击,并且收到了 ACTION_DOWN和ACTION_UP事件 。这里解释一下,这里的收到了用词不是那么准确, 应该是能接收到事件,并且return super.onTouchEvent()了,记住是return super.onTouchEvent(),如果是return true都不行 ,因为return true没有执行view的onTouchEvent方法,而点击事件是在ACTION_UP中设置的。即在ACTION_UP的时候源码中调用了 performClick() 方法。这里贴一部分源代码

TextView的源代码:

final boolean superResult = super.onTouchEvent(event);

View的源代码:

case MotionEvent.ACTION_UP:
              boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
              if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                  // take focus if we don't have it already and we should in
                  // touch mode.
                  boolean focusTaken = false;
                  if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                      focusTaken = requestFocus();
                  }

                  if (prepressed) {
                      // The button is being released before we actually
                      // showed it as pressed.  Make it show the pressed
                      // state now (before scheduling the click) to ensure
                      // the user sees it.
                      setPressed(true, x, y);
                 }

                  if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                      // This is a tap, so remove the longpress check
                      removeLongPressCallback();

                      // Only perform take click actions if we were in the pressed state
                      if (!focusTaken) {
                          // Use a Runnable and post this rather than calling
                          // performClick directly. This lets other visual state
                          // of the view update before click actions start.
                          if (mPerformClick == null) {
                              mPerformClick = new PerformClick();
                          }
                          if (!post(mPerformClick)) {
                              performClick();
                          }
                      }
                  }
 
标签: 安卓开发
反对 0举报 0 评论 0
 

免责声明:本文仅代表作者个人观点,与乐学笔记(本网)无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
    本网站有部分内容均转载自其它媒体,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责,若因作品内容、知识产权、版权和其他问题,请及时提供相关证明等材料并与我们留言联系,本网站将在规定时间内给予删除等相关处理.

  • 安卓中通知功能的具体实现
    安卓中通知功能的具体实现
    通知[Notification]是Android中比较有特色的功能,当某个应用程序希望给用户发出一些提示信息,而该应用程序又不在前台运行时,就可以借助通知实现。使用通知的步骤1、需要一个NotificationManager来获得NotificationManager manager = (NotificationManager
    02-05 安卓开发
  • Android view系统分析-setContentView
    Android view系统分析-setContentView
    第一天上班,列了一下今年要学习的东西。主要就是深入学习Android相关的系统源代码,夯实基础。对于学习Android系统源代码,也没什么大概,就从我们平常使用最基础的东西学起,也就是从view这个切入点开始学习Android的源码,在没分析源码之前,我们有的时候
    02-05 安卓开发
  • 如何进行网络视频截图/获取视频的缩略图
    如何进行网络视频截图/获取视频的缩略图
    小编导读:获取视频的缩略图,截图正在播放的视频某一帧,是在音视频开发中,常遇到的问题。本文是主要用于点播中截图视频,同时还可以获取点播视频的缩略图进行显示,留下一个问题,如下图所示, 如果要获取直播中节目视频缩略图,该怎么做呢?(ps:直播是直
  • Android NDK 层发起 HTTP 请求的问题及解决
    Android NDK 层发起 HTTP 请求的问题及解决
    前言新的一年,大家新年快乐~~鸡年大吉!本次给大家带来何老师的最新文章~虽然何老师还在过节,但依然放心不下广大开发者,在此佳节还未结束之际,给大家带来最新的技术分享~ 事件的起因不说了,总之是需要实现一个 NDK 层的网络请求。为了多端适用,还是选择
  • Android插件化(六): OpenAtlasの改写aapt以防止资源ID冲突
    Android插件化(六): OpenAtlasの改写aapt以防
    引言Android应用程序的编译中,负责资源打包的是aapt,如果不对打包后的资源ID进行控制,就会导致插件中的资源ID冲突。所以,我们需要改写aapt的源码,以达到通过某种方式传递资源ID的Package ID,通过aapt打包时获取到这个Package ID并且应用才插件资源的命名
    02-05 安卓开发
  • Android架构(一)MVP架构在Android中的实践
    Android架构(一)MVP架构在Android中的实践
    为什么要重视程序的架构设计 对程序进行架构设计的原因,归根结底是为了 提高生产力 。通过设计是程序模块化,做到模块内部的 高聚合 和模块之间的 低耦合 (如依赖注入就是低耦合的集中体现)。 这样做的好处是使得程序开发过程中,开发人员主需要专注于一点,
    02-05 安卓开发
  • 安卓逆向系列教程 4.2 分析锁机软件
    安卓逆向系列教程 4.2 分析锁机软件
    安卓逆向系列教程 4.2 分析锁机软件 作者: 飞龙 这个教程中我们要分析一个锁机软件。像这种软件都比较简单,完全可以顺着入口看下去,但我这里还是用关键点来定位。首先这个软件的截图是这样,进入这个界面之后,除非退出模拟器,否则没办法回到桌面。上面那
    02-05 安卓开发
  • Android插件化(二):OpenAtlas插件安装过程分析
    Android插件化(二):OpenAtlas插件安装过程分析
    在前一篇博客 Android插件化(一):OpenAtlas架构以及实现原理概要 中,我们对应Android插件化存在的问题,实现原理,以及目前的实现方案进行了简单的叙述。从这篇开始,我们要深入到OpenAtlas的源码中进行插件安装过程的分析。 插件的安装分为3种:宿主启动时立
    02-05 安卓开发
  • [译] Android API 指南
    [译] Android API 指南
    众所周知,Android开发者有中文网站了,API 指南一眼看去最左侧的菜单都是中文,然而点进去内容还是很多是英文,并没有全部翻译,我这里整理了API 指南的目录,便于查看,如果之前还没有通读,现在可以好好看一遍。注意,如果标题带有英文,说明官方还没有翻
  • 使用FileProvider解决file:// URI引起的FileUriExposedException
    使用FileProvider解决file:// URI引起的FileUri
    问题以下是一段简单的代码,它调用系统的相机app来拍摄照片:void takePhoto(String cameraPhotoPath) {File cameraPhoto = new File(cameraPhotoPath);Intent takePhotoIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);takePhotoIntent.putExtra(Medi
    02-05 安卓开发
点击排行