本文主要讲述:

  • 自己对View事件机制的一些理解
  • 在项目中遇到的一些坑,解决方案
  • 收集了一些View的事件机制问题

事件的分发原理图

  • 对于一个root viewgroup来说,如果接受了一个点击事件,那么首先会调用他的dispatchTouchEvent方法。

  • 如果这个viewgroup的onInterceptTouchEvent 返回true,那就代表要拦截这个事件。接下来这个事件就

  • 给viewgroup自己处理了,从而viewgroup的onTouchEvent方法就会被调用。如果如果这个viewgroup的onInterceptTouchEvent

  • 返回false就代表我不拦截这个事件,然后就把这个事件传递给自己的子元素,然后子元素的dispatchTouchEvent

  • 就会被调用,就是这样一个循环直到 事件被处理。

    图:

完整事件流程:

dispatchTouchEvent(true) -> onInterceptTouchEvent(true) -> onTouchEvent(true) - 事件结束

重要的事情说一遍:

也就是说在任何View或者ViewGrop中只要它想消费Touch事件,那就onInterceptTouchEvent(true),这样它就不会把

事件传下去给孩子view了,自己消费.

api 描述:

  • dispatchTouchEvent 分发事件

    return false; //表示分发,默认false;

    return true; // 表示不分发;

  • onInterceptTouchEvent 拦截事件

    当dispatchTouchEvent 确认分发,会启动拦截事件;

    return false; //表示不拦截,默认false;

    return true; // 表示拦截;

注意:拦截是相当于它的孩子(也就是说不会拦截自己,如果拦截,则TouchEvent会传到他自己,而它孩子就接收不)

不拦截会继续往他的孩子递归是否onInterceptTouchEvent ;

  • onTouchEvent 触摸事件

    return false; //表示不消费,默认false;

    return true; // 表示消费;

当onInterceptTouchEvent 确认拦截,会问自己是否要消费TouchEvent,

如果拦截了又不消费则,Touch结束;

  • invalidate 重新绘制

    让整个view失效,这样view会被重新调用, 配合onDraw()使用;

    下面是调用流程:

    当invalidate时会重新调用draw方法,

    draw会调用onDraw,而在draw内还会调用computeScroll(),

此时如果想让computeScroll()循环被调用可以在computeScroll()内自己调用postInvaildate()重新绘制.

computeScroll() 源码是空实现,具体实现由自己来写

常见问题

  • 1.view的onTouchEvent,OnClickListerner和OnTouchListener的onTouch方法 三者优先级如何?

    答:

    onTouchListener优先级最高,如果onTouch方法返回 false ,那onTouchEvent就被调用了,返回true 就不会被调用。至于onClick 优先级最低。

  • 2.点击事件的传递顺序如何?

    答:

    Activity-Window-View。从上到下依次传递,当然了如果你最低的那个view onTouchEvent返回false 那就说明他不想处理 那就再往上抛,都不处理的话

    最终就还是让Activity自己处理了。举个例子,pm下发一个任务给leader,leader自己不做 给架构师a,小a也不做 给程序员b,b如果做了那就结束了这个任务。

    b如果发现自己搞不定,那就找a做,a要是也搞不定 就会不断向上发起请求,最终可能还是pm做。

//activity的dispatchTouchEvent 方法 一开始就是交给window去处理的
//win的superDispatchTouchEvent 返回true 那就直接结束了 这个函数了。返回false就意味
//这事件没人处理,最终还是给activity的onTouchEvent 自己处理 这里的getwindow 其实就是phonewindow
 public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }
//来看phonewindow的这个函数 直接把事件传递给了mDecor
 @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }
//devorview就是 我们的rootview了 就是那个framelayout 我们的setContentView里面传递的那个layout
//就是这个decorview的 子view了
     @Override
    public final View getDecorView() {
        if (mDecor == null) {
            installDecor();
        }
        return mDecor;
    }
  • 3.enable是否影响view的onTouchEvent返回值?

    答:

    不影响,只要clickable和longClickable有一个为真,那么onTouchEvent就返回true。

  • 4.滑动冲突问题如何解决 思路是什么?

    答:

    让谁消费滑动:

    要解决滑动冲突 其实最主要的就是有一个核心思想。你到底想在一个事件序列中,让哪个view 来响应你的滑动?比如 从上到下滑,是哪个view来处理这个事件,从左到右呢?

    拦截内外滑动:

    用业务需求 来想明白以后 剩下的 其实就很好做了。核心的方法 就是2个 外部拦截也就是父亲拦截,另外就是内部拦截,也就是子view拦截法。 学会这2种 基本上所有的滑动冲突.

    都是这2种的变种,而且核心代码思想都一样。

    外部拦截法:思路就是重写父容器的onInterceptTouchEvent即可。子元素一般不需要管。可以很容易理解,因为这和android自身的事件处理机制 逻辑是一模一样的

父容器示例代码:

@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean intercepted = false;
        int x = (int) ev.getX();
        int y = (int) ev.getY();
        switch (ev.getAction()) {
   //down事件肯定不能拦截 拦截了后面的就收不到了
            case MotionEvent.ACTION_DOWN:
                intercepted = false;
                break;
            case MotionEvent.ACTION_MOVE:
                if (你的业务需求) {
//如果确定拦截了 就去自己的onTouchEvent里 处理拦截之后的操作和效果 即可了
                    intercepted = true;
                } else {
                    intercepted = false;
                }
                break;
            case MotionEvent.ACTION_UP:
 //up事件 我们一般都是返回false的 一般父容器都不会拦截他。 因为up是事件的最后一步。这里返回true也没啥意义
 //唯一的意义就是因为 父元素 up被拦截。导致子元素 收不到up事件,那子元素 就肯定没有onClick事件触发了,这里的
//小细节 要想明白
                intercepted = false;
                break;
            default:
                break;
        }
        return intercepted;
    }

内部拦截法:内部拦截法稍微复杂一点,就是事件到来的时候,父容器不管,让子元素自己来决定是否处理。如果消耗了 就最好,没消耗 自然就转给父容器处理了。

子元素代码:

@Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                if (如果父容器需要这个点击事件) {
                    getParent().requestDisallowInterceptTouchEvent(false);
                }//否则的话 就交给自己本身view的onTouchEvent自动处理了
                break;
            case MotionEvent.ACTION_UP:
                break;
            default:
                break;
        }
        return super.dispatchTouchEvent(event);
    }

PS: 父亲容器代码也要修改一下,其实就是保证父亲别拦截down:

@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            return false;
        }
        return true;
    }

案列: 分发的例子

下面红色框区域的结构是ScrollView, 它的孩子是一些TextView;

分析:

  • 1.当点击它任意一个孩子(TextView)时,如果ScrollView不进行onInterceptTouchEvent ,则它就不可以在菜单上进行左右滑动;

  • 2.但是如果拦截了全部,则它的孩子又会消费不了TouchEvent;

解决方法:

只有左右移动的时候进行拦截,这样父亲就拥有了TouchEvent,可在菜单上继续左右滑动,

而上下移动或静止的时候就不拦截,这样孩子又有了TouchEvent,那么孩子就可以点击了;

实例代码:

/**
 * 当滑动的时候,需要拦截TouchEvent时间,让scrollView消化,否则会分发到孩子去;
 * 当不滑动的停止的时候,不拦截,则会分发到孩子去,也就是TexView;
 */
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    switch (ev.getAction()) {
    // 只有水平滑动时才拦截touch
    case MotionEvent.ACTION_DOWN:
        startX = (int) (ev.getRawX() + 0.5f);
        startY = (int) (ev.getRawY() + 0.5f);
        break;
    case MotionEvent.ACTION_MOVE:
        int newX = (int) (ev.getRawX() + 0.5f);
        int newY = (int) (ev.getRawY() + 0.5f);
        int dx = Math.abs(startX - newX);
        int dy = Math.abs(startY - newY);
        if (dx > dy) {
            // 水平滑动,只有水平滑动才会拦截事件
            return true;
        }
        startX = (int) ev.getRawX();// 初始化当前位置
    case MotionEvent.ACTION_UP:
        break;
    }
    return super.onInterceptTouchEvent(ev);
}  

Android View事件机制一些事的更多相关文章

  1. Android View事件机制 21问21答

    原文: http://www.cnblogs.com/punkisnotdead/p/5179115.html#3358859 1.View的坐标参数 主要有哪些?分别有什么注意的要点? 答:Left ...

  2. Android View 事件分发机制 源码解析 (上)

    一直想写事件分发机制的文章,不管咋样,也得自己研究下事件分发的源码,写出心得~ 首先我们先写个简单的例子来测试View的事件转发的流程~ 1.案例 为了更好的研究View的事件转发,我们自定以一个My ...

  3. android view事件分发机制

    首先我们先写个简单的例子来测试View的事件转发的流程~ 1.案例 为了更好的研究View的事件转发,我们自定以一个MyButton继承Button,然后把跟事件传播有关的方法进行复写,然后添加上日志 ...

  4. Android View 事件分发机制 源代码解析 (上)

    一直想写事件分发机制的文章,无论咋样,也得自己研究下事件分发的源代码.写出心得~ 首先我们先写个简单的样例来測试View的事件转发的流程~ 1.案例 为了更好的研究View的事件转发,我们自定以一个M ...

  5. android View事件分发机制结论

    原始博客有对源码的分析:http://blog.csdn.net/lmj623565791/article/details/39102591 结论:1.view事件的分发流程: dispatchTou ...

  6. Android View事件传递机制

    ViewGroup dispatchTouchEvent onInterceptTouchEvent onTouch View dispatchTouchEvent onTouch 假设View的层级 ...

  7. Android View事件分发-从源码分析

    View事件分发-从源码分析 学习自 <Android开发艺术探索> https://blog.csdn.net/qian520ao/article/details/78555397?lo ...

  8. 谈谈我对Android View事件分发的理解

    写这篇博客的缘由.近期因为项目中用到相似一个LinearLayout中水平布局中,有一个TextView和Button,然后对该LinearLayout布局设置点击事件.点击TextView能够触发该 ...

  9. Android View事件分发与传递

    在Android中,人们主要通过手指与系统交互.Android把所有的touch事件都被封装成MotionEvent来进行处理,其中包括了手指点击的位置,时间等信息.其事件类型主要包括:ACTION_ ...

随机推荐

  1. iis部署python运行环境

    IIS部署 1.启用或者关闭windows功能,选择安装CGI,我这里已经安装过了. 2.安装后重新打开IIS看到CGI 3.配置ISAPI和CGI限制 4.右上角添加,路径是python安装路径,注 ...

  2. RandomAccessFile&IO流&排序&方法论

    RandomAccessFile&IO流&排序&方法论 我们总觉得历史是极其遥远的东西,与我们并无关联,又觉得历史隐藏在图书馆的旧书之中. 然而,我们每个人都有真真切切的历史. ...

  3. 聊聊LightProbe原理实现以及对LightProbe数据的修改

    0x00 前言 最近工作比较忙,所以文章已经很久没有更新了.这篇小文的主题也是在出差的高铁上想到,因为最近和一些朋友聊天,发现他们中很多人的项目中都使用了多个实时光源.细问之下主要是某些物体,例如角色 ...

  4. 验证码识别之w3cschool字符图片验证码(easy级别)

    起因: 最近在练习解析验证码,看到了这个网站的验证码比较简单,于是就拿来解析一下攒攒经验值,并无任何冒犯之意... 验证码所在网页: https://www.w3cschool.cn/checkmph ...

  5. Python 函数参数*expression 之后为什么只能跟关键字参数

    python 为何要设计这种? 正确: def f(a=2,b=2,c=3): return a+b+c print(f(*(1,1),c=1)) 错误: def f(a=2,b=2,c=3): re ...

  6. MyEclipse如何全局搜索

    1全局搜索的启动方式 CTRL+H 2全局搜索自己选择搜索方式 自己选择要搜索的东西,简单吧,里面还有很多好玩的东西需要你去发现,加油! [正在看本人博客的这位童鞋,我看你气度不凡,谈吐间隐隐有王者之 ...

  7. 亲密接触Redis-第一天

    引言 nosql,大规模分布式缓存遍天下,Internet的时代在中国由其走得前沿,这一切归功于我国特色的电商.因此nosql.大数据技术在中国应用的比国外还要前沿.从这一章开始我们将开始进入到真正的 ...

  8. SceneKit:简单的3D游戏场景搭建

    SceneKit是Apple用来开发休闲3D游戏的框架,不同于底层的OpenGL库,你仅仅需要很少的代码就可以快速看到实际的3D场景效果.下面简单的聊聊搭建一个3D游戏场景需要做的事情. 首先你必须用 ...

  9. 查看apk签名信息

    经常在注册开发者的时候会遇到要求填写申请应用的应用签名: 有两种很方便的方法: 1.如果没有源码或者没有打开eclipse,直接下载这个应用应用下载链接 使用截图,只要把包名输入,自动会出现签名信息. ...

  10. 向Github提交代码时遇到的一些问题

    今天分享一下我的一些小经验,那就是向github提交我们的代码.之前一直是直接使用的浏览器完成的代码的下载任务,没有使用过客户端,为了让自己在工作之前熟练使用GitHub,所以就有了下面的这篇博文了. ...