事件分发

作者: Anwfly | 来源:发表于2019-07-15 16:13 被阅读141次

    知识要点:

    1.dispatchTouchEvent(MotionEvent ev)

    2.onInterceptTouchEvent(MotionEvent ev)

    3.onTouchEvent(MotionEvent ev)

    目标要求:

    掌握事件分发机制

    一. 基础认知

    1. 事件分发的对象是谁?点击事件(Touch事件)
    • 定义:当用户触摸屏幕时(View 或 ViewGroup派生的控件),将产生点击事件(Touch事件)
      Touch事件的相关细节(发生触摸的位置、时间等)被封装成MotionEvent对象

    • 事件类型(4种)

    事件类型 具体动作
    MotionEvent.ACTION_DOWN 按下View(所有事件的开始)
    MotionEvent.ACTION_UP 抬起View(与DOWN对应)
    MotionEvent.ACTION_MOVE 滑动View
    MotionEvent.ACTION_CANCEL 结束事件(非人为原因)

    特别说明:从手指接触屏幕 至 手指离开屏幕,这个过程产生的一系列事件

    注:一般情况下,事件列都是以DOWN事件开始、UP事件结束,中间有无数的MOVE事件,如下图:
    
    0.5290021690015503.png
    1. 事件分发的本质
      将点击事件(MotionEvent)传递到某个具体的View & 处理的整个过程
      即 事件传递的过程 = 分发过程。

    2. 事件在哪些对象之间进行传递?
      Activity、ViewGroup、View
      Android的UI界面由Activity、ViewGroup、View 及其派生类组成


      0.3402589943500538.png
    3. 事件分发的顺序
      即 事件传递的顺序:Activity -> ViewGroup -> View

    4. 事件分发过程由哪些方法协作完成?


      0.3574262937594854.png

    二.分发流程

    944365-aea821bbb613c195.png
    Touch事件相关方法 Activity ViewGroup View
    dispatchTouchEvent(MotionEvent ev)
    onInterceptTouchEvent(MotionEvent ev) 没有 没有
    onTouchEvent(MotionEvent event)
    1. Activity
    • dispatchTouchEvent(MotionEvent ev) -- 事件分发

      1.返回true 直接消费掉 ,没有分发;

      2.返回false,不消费,但是触摸事件不做处理;

      3.只有返回super.dispatchTouchEvent(ev)事件才会向子view分发,其实是调用了PhoneWindow的superDispatchTouchEvent(),进而调用了DecorView的superDispatchTouchEvent,里面又调用了super.dispatchTouchEvent(),而DecorView是一个FrameLayout

    • onTouchEvent(MotionEvent event) --事件处理
      1.返回false和默认(super.onTouchEvent(event)),不做处理;
      2.返回true,消费事件

    1. ViewGroup
    • dispatchTouchEvent(MotionEvent ev) -- 事件分发

      1.返回true 事件被消费了, 事件未分发

      2.返回false 事件未分发,事件传递到父容器的onTouchEvent()

      3.只有返回super.dispatchTouchEvent(ev),事件才会向下走,里面会调用onInterceptTouchEvent()

    • onInterceptTouchEvent(MotionEvent ev) -- 事件拦截

      1.返回true,事件拦截,调用自己的onTouchEvent()

      2.返回false,事件未拦截,分发给了子view

      3.super.onInterceptTouchEvent(ev)与返回false效果一样

    • onTouchEvent(MotionEvent event)

      1.返回false和super.onTouchEvent(event)事件不消费,事件传递给父容器
      2.返回true,事件会被消费

    1. View
    • dispatchTouchEvent(MotionEvent event)

      因为它最小,没有子view,所以这个方法其实没有进行分发的能力

      1.返回true,事件被消费,

      2.返回false ,事件传递到父容器的onTouchEvent()

      3.只有返回super.dispatchTouchEvent(ev),事件才会向下走,里面会调用自己 onTouchEvent()

    • onTouchEvent(MotionEvent event)

      1.返回false和super.onTouchEvent(event)事件不消费,事件传递给父容器
      2.返回true,事件会被消费

    三.点击事件触发的位置及拦截
    在onTouchEvent() 方法中MotionEvent.ACTION_UP中会调用performClick(),performClick()中会调用onClick()
    拦截点击事件: 在父容器中重写 onInterceptTouchEvent(MotionEvent ev) ,返回true,会走父容器的onTouchEvent()方法

    四.setOnTouchListener()和onTouchEvent()关系
    源码如下:

    public boolean dispatchTouchEvent(MotionEvent event) {
          ...
            if (onFilterTouchEventForSecurity(event)) {
              
                if (li != null && li.mOnTouchListener != null
                        && (mViewFlags & ENABLED_MASK) == ENABLED
                        && li.mOnTouchListener.onTouch(this, event)) {
                    result = true;
                }
    
                if (!result && onTouchEvent(event)) {
                    result = true;
                }
            }
            ...
            return result;
        }
    
    

    第7行会调用OnTouchListener.onTouch(this, event),在第11行,才会去调用onTouchEvent(event),所以setOnTouchListener的onTouch()方法先被调用,如果onTouch()方法返回true,事件被消费了,则onTouchEvent()方法不会调用.

    五.滑动事件冲突

    父容器想获取左右滑动事件,子view想获取垂直滑动事件(如Viewpager中有ListView),都想获取事件,怎么办?

    思路:可以进行选择性的拦截事件,当水平滑动的距离大于垂直滑动的距离时,拦截事件

      @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            //选择性的进行拦截,当水平滑动距离大于垂直滑动距离时 ,拦截
            switch (ev.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    mDownX = ev.getX();
                    mDownY = ev.getY();
                    break;
                case MotionEvent.ACTION_MOVE:
                    float moveX = ev.getX();
                    float moveY = ev.getY();
                    //水平和垂直的滑动距离(绝对值)
                    float dx = Math.abs(moveX - mDownX);
                    float dy = Math.abs(moveY- mDownY);
                    if (dx>dy){
                        return true;
                    }
                    break;
            }
    
            return false;//不拦截
        }
    
    • getX()和getY():由这两个函数获得的x,y值是相对的坐标值,相对于父容器坐标。

    • getRawX()和getRawY():有这两个函数获得的x,y值是绝对坐标,是相对于屏幕的。

    相关文章

      网友评论

        本文标题:事件分发

        本文链接:https://www.haomeiwen.com/subject/oqenkctx.html