View的事件分发,女神带你飞

事件的分发原理图:

  1. 对于一个root viewgroup来说,如果接受了一个点击事件,那么首先会调用他的dispatchTouchEvent方法。
  2. 如果这个viewgroup的onInterceptTouchEvent 返回true,那就代表要拦截这个事件。接下来这个事件就
  3. 给viewgroup自己处理了,从而viewgroup的onTouchEvent方法就会被调用。如果如果这个viewgroup的onInterceptTouchEvent
  4. 返回false就代表我不拦截这个事件,然后就把这个事件传递给自己的子元素,然后子元素的dispatchTouchEvent
  5. 就会被调用,就是这样一个循环直到 事件被处理。

图1:

简单的说就是只要各事件只要true分发就会一直走下去:

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

重要的事情说一遍:

也就是说在任何View或者ViewGrop中只要它想消费Touch事件,就直接onInterceptTouchEvent(true),这样它就不会把事件传下去给孩子view了,而是自己消费.

知其人先知其心,我们继续进一步了解下其他事件分发的api.

  • dispatchTouchEvent 分发事件

    return false; //不是目标对象,则分发,默认false;

    return true; // 是目标view,则不分发;

    dispatchTouchEvent作用是将touch事件向下传递直到遇到被触发的目标view.

    我们可以通过返回的boolean对touch事件的分发进行处理,是否要向下分发寻找目标view,当然这个方法也可以被重载,手动分配事件.

  • onInterceptTouchEvent 拦截事件

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

    return true; // 表示拦截;

    拦截是相当于它的孩子(也就是说不会拦截自己,如果拦截,则TouchEvent会传到他自己,而它孩子就接收不),不拦截会继续往他的孩子递归是否onInterceptTouchEvent.

  • onTouchEvent 触摸事件

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

    return true; // 表示消费;

    当onInterceptTouchEvent 确认拦截,会问自己是否要消费TouchEvent,如果拦截了又不消费则,Touch结束.

  • invalidate 重新绘制

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

    下面是调用流程:

    1. 当invalidate时会重新调用draw方法;
    2. draw会调用onDraw,而在draw内还会调用computeScroll();
    3. 此时如果想让computeScroll()循环被调用可以在computeScroll()内自己调用postInvaildate()重新绘制;

    invalidate刷新UI步骤:

    draw() -> onDraw() -> computeScroll()

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

开发中事件分发的常见问题(重点)

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

    答:

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

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

    答:

    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;
    }
  • view的OnTouchListener的 onTouch ,和 onTouchEvent,OnClickListerner 方法 三者优先级如何?

    答:

    onTouchListener优先级最高,如果onTouch方法返回 false(自己不消费) ,那onTouchEvent就被调用了,返回true(自己消费) 就不会被调用,至于onClick 优先级最低.

    优先级如下:

    OnTouchListener > onTouch > onTouchEvent > OnClickListerner

  • enable是否影响view的onTouchEvent返回值?

    答:

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

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

    答:

    • 让谁消费滑动:

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

    • 拦截内外滑动:

      用业务需求 来想明白以后 剩下的 其实就很好做了。

      核心的方法就2个:

      1. 外部拦截,也就是父亲拦截.(重写父容器的onInterceptTouchEvent即可).
      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;
}

事件分发的项目

需求:仿QQ侧滑菜单

1. google给我们提供了DrawerLayout控件来作为侧滑菜单控件,但是侧滑菜单一般都是覆盖到主页面顶部的.

2. 我们需求在向右拖拉滑动时可以侧滑出菜单,并且要求菜单不可覆盖到主页面上,需求主页面跟着侧滑菜单的滑动和位移.

图2:

上面红色框区域的结构可以这样设计ScrollView+多TextView.使用scrollView的原因是当菜单的item增加时可以滚动,当然listview,rv也可以做到.

问题分析:

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

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

?

问题解决:

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

2. 上下移动或静止的时候就不拦截,这样孩子又有了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);
}  

参考来源:希尔瓦娜斯女神

时间: 2024-09-28 19:19:49

View的事件分发,女神带你飞的相关文章

从源码的角度解析View的事件分发

有好多朋友问过我各种问题,比如:onTouch和onTouchEvent有什么区别,又该如何使用?为什么给ListView引入了一个滑动菜单的功能,ListView就不能滚动了?为什么图片轮播器里的图片使用Button而不用ImageView?等等……对于这些问题,我并没有给出非常详细的回答,因为我知道如果想要彻底搞明白这些问题,掌握Android事件分发机制是必不可少的,而Android事件分发机制绝对不是三言两语就能说得清的. 在我经过较长时间的筹备之后,终于决定开始写这样一篇文章了.目前虽

Android View的事件分发机制

准备了一阵子,一直想写一篇事件分发的文章总结一下.这个知识点实在是太重要了. 一个应用的布局是丰富的,有TextView,ImageView,Button等.这些子View的外层还有ViewGroup.如RelativeLayout.LinearLayout.作为一个开发人员,我们会思考.当点击一个button,Android系统是如何确定我点的就是button而不是TextView的?然后还正确的响应了button的点击事件. 内部经过了一系列什么过程呢? 先铺垫一些知识能更加清晰的理解事件分

Android事件分发机制详解(1)----探究View的事件分发

探究View的事件分发 在Activity中,只有一个按钮,注册一个点击事件 [java] view plaincopy button.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Log.d("TAG", "onClick execute"); } }); 如果在需要一个触摸事件 [java] view plaincopy button.setO

Android事件分发详解(一)——View的事件分发

MainActivity如下: package cc.cv; import android.os.Bundle; import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnTouchListener; import android.widget.Button; import android.widge

View的事件分发机制解析

引言 Android事件构成 在Android中,事件主要包含点按.长按.拖拽.滑动等,点按又包含单击和双击,另外还包含单指操作和多指操作.全部这些都构成了Android中的事件响应.总的来说.全部的事件都由例如以下三个部分作为基础: 按下(ACTION_DOWN) 移动(ACTION_MOVE) 抬起(ACTION_UP) 全部的操作事件首先必须运行的是按下操作(ACTION_DOWN).之后全部的操作都是以按下操作作为前提,当按下操作完毕后.接下来可能是一段移动(ACTION_MOVE)然后

13.View的事件分发机制——dispatchTouchEvent详解

在前面的第二篇文章中,我们提过,View的事件分发是一种委托思想:上层委托下层,父容器委托子元素来处理这个流程.接下来,我们就将深入去学习View的事件分发机制. 1.事件的传递流程 事件,在Android中对应的类是MotionEvent,因此,我们要分析的就是MotionEvent这个类.对点击事件的分发,其实就是对MotionEvent的对象进行处分发.所以,当一个MotionEvent产生以后(从驱动读取),系统需要把这个事件传递给一个具体的View,这个传递的过程就是分发过程,点击事件

Android开发-分析ViewGroup、View的事件分发机制、结合职责链模式

介绍 上一篇博客职责链/责任链模式(Chain of Responsibility)分析理解和在Android的应用 介绍了职责链模式,作为理解View事件分发机制的基础. 套用职责链模式的结构分析,当我们的手指在屏幕上点击或者滑动,就是一个事件,每个显示在屏幕上的View或者ViewGroup就是职责对象,它们通过Android中视图层级组织关系,层层传递事件,直到有职责对象处理消耗事件,或者没有职责对象处理导致事件消失. 关键概念介绍 要理解有关View的事件分发,先要看几个关键概念 Mot

Android View 的事件分发原理解析

作为一名 Android 开发者,每天接触最多的就是 View 了.Android View 虽然不是四大组件,但其并不比四大组件的地位低.而 View 的核心知识点事件分发机制则是不少刚入门同学的拦路虎,也是面试过程中基本上都会问的.理解 View 的事件能够让你写出更好自定义 View 以及解决滑动冲突. 1. View 事件认识 1.1 MotionEvent 事件 当你用手指轻触屏幕,这个过程在 Android 中主要可以分为以下三个过程: ACTION_DOWN:手指刚接触屏幕,按下去

View的事件分发机制

View事件的分发机制由三个方法共同完成,这三个方法是: public boolean  dispatchTouchEvent(MotionEvent ev); public boolean onInterceptTouchEvent(MotionEvent ev); public boolean onTouchEvent(MotionEvent ev); 它们之间的关系可以用下面的伪代码表示: public boolean dispatchTouchEvent(MotionEvent ev)