View的事件分发(一)分发流程

目录

做用

事件分发流程对于咱们开发者来说,有什么做用?php

  • 根据咱们本身的需求来自定义滑动触摸响应的规则。
  • 解决滑动冲突

相关知识

MotionEvent

引用官方介绍: Object used to report movement (mouse, pen, finger, trackball) events. Motion events may hold either absolute or relative movements and other data, depending on the type of device.java

大体意思就是用于报告移动(鼠标,笔,手指,轨迹球)事件的对象。运动事件能够保持绝对或相对运动以及其余数据,具体取决于设备的类型。android

在事件分发中,典型经常使用的事件总共有三个:ide

  • ACTION_DOWN ------按下操做
  • ACTION_MOVE -------移动操做
  • ACTION_UP -----------抬起操做

通常一个事件的完整流程是 ACTION_DOWN ---> ACTION_MOVE ---> ACTION_UP源码分析

经常使用方法 含义
getX/Y() 点击事件相对于 当前 View 左上角 的 x/y 轴距离
getRawX/Y() 点击事件相对于 手机屏幕左上角 的 x/y 轴距离

事件分发相关方法。

  • public boolean dispatchTouchEvent(MotionEvent ev)布局

    用于事件分发,将触摸事件向下传递给目标视图,若是它自己就是目标视图,则传递给本身来处理事件。返回结果受本身的 onTouchEvent 和下级 View 的 dispatchTouchEvent 方法影响。post

  • public boolean onInterceptTouchEvent(MotionEvent ev)学习

    在 dispatchTouchEvent 中调用。是否进行事件拦截,若是返回 false ,则表明不拦截事件;若是返回事件为 true,则拦截事件,而且此事件的后续事件都交给本身来处理,不会再调用此方法询问是否拦截。this

    只有 ViewGroup 有这个方法,默认返回 false,不拦截。spa

  • public boolean onTouchEvent(MotionEvent ev)

    在 dispatchTouchEvent 中调用。用来处理事件,返回 true 表明消费事件;返回 false 表明不消费事件

表明三者关系的伪代码:(摘抄于《Android艺术开发探索》第三章)

//伪代码,解释 dispatchTouchEvent 和 onInterceptTouchEvent 以及 onTouchEvent 的调用关系 
public boolean dispatchTouchEvent(MotionEvent ev){
    boolean consume;
    if(onInterceptTouchEvent(ev)){
        consume = onTouchEvent(ev);
    }else{
        consume = childView.dispatchTouchEvent(ev);
    }
    return consume ;
} 
复制代码

经过上面的伪代码,咱们能够大体的了解事件的传递规则:对于一个根 ViewGroup 来讲,点击事件产生后,首先会传递给它,这时它的 dispatchTouchEvent 就会被调用,若是它的 onInterceptTouchEvent 方法返回 true,就表示要拦截事件,接着事件就交给这个 ViewGroup 的 onTouchEvent 来处理,若是 onInterceptTouchEvent 返回 false,表示不拦截事件,当前事件就会传递给它的子元素,接着子元素的 dispatchTouchEvent 方法就会被调用,如此反复直到事件被最终处理。

分发流程

三个方法在具体项目中的具体的调用流程是什么呢?咱们用个 Demo 来演示。

都只是简单重写这三个方法,并打印日志。

public class Group1 extends FrameLayout {
    public static final String TAG = "----------";
    //......代码省略......

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.i(TAG, "dispatchTouchEvent-----------------"+ Util.getMotionEvent(ev) + this.getClass().getSimpleName());
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.i(TAG, "onInterceptTouchEvent-----------------" + Util.getMotionEvent(ev)+ this.getClass().getSimpleName());
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i(TAG, "onTouchEvent-----------------" + Util.getMotionEvent(event)+ this.getClass().getSimpleName());
        return super.onTouchEvent(event);
    }
}
复制代码

View 的代码:

public class View1 extends View {
    public static final String TAG = "----------";
    //......代码省略......

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.i(TAG,"dispatchTouchEvent-----------------" + Util.getMotionEvent(event)+ this.getClass().getSimpleName());
        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i(TAG,"onTouchEvent-----------------"+ Util.getMotionEvent(event) + this.getClass().getSimpleName());
        return super.onTouchEvent(event);
    }
}
复制代码

Util 类代码(打印出事件的名称):

class Util {
    static String getMotionEvent(MotionEvent motionEvent) {
        int action = motionEvent.getAction();
        if (action == MotionEvent.ACTION_DOWN) {
            return "ACTION_DOWN----";
        } else if (action == MotionEvent.ACTION_UP) {
            return "ACTION_UP------";
        } else if (action == MotionEvent.ACTION_MOVE) {
            return "ACTION_MOVE----";
        } else if (action == MotionEvent.ACTION_CANCEL) {
            return "ACTION_CANCEL--";
        } else {
            return String.valueOf(motionEvent);
        }
    }
}
复制代码

MainActivity的代码:

public class MainActivity extends AppCompatActivity {
    public static final String TAG = "----------";
    //......代码省略......

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.i(TAG, "dispatchTouchEvent-----------------" + com.sjc.eventdispatch.Util.getMotionEvent(ev) + this.getClass().getSimpleName());
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i(TAG, "onTouchEvent-----------------" + com.sjc.eventdispatch.Util.getMotionEvent(event) + this.getClass().getSimpleName());
        return super.onTouchEvent(event);
    }

}
复制代码

MainActivity中的 xml 文件:

<?xml version="1.0" encoding="utf-8"?>
<com.sjc.eventdispatch.Group1 xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent">

    <com.sjc.eventdispatch.View1 android:id="@+id/view1" android:layout_width="200dp" android:layout_height="300dp" android:layout_gravity="center" android:background="@color/colorPrimaryDark" />
</com.sjc.eventdispatch.Group1>
复制代码

具体页面呈现:

模拟一:

  • 条件:全部方法都保持返回 super.xxx(event) 状态;
  • 动做:点击 View1 ;
//Log日志
----------: dispatchTouchEvent--------ACTION_DOWN----MainActivity
----------: dispatchTouchEvent--------ACTION_DOWN----Group1
----------: onInterceptTouchEvent--------ACTION_DOWN----Group1
----------: dispatchTouchEvent--------ACTION_DOWN----View1
----------: onTouchEvent--------ACTION_DOWN----View1
----------: onTouchEvent--------ACTION_DOWN----Group1
----------: onTouchEvent--------ACTION_DOWN----MainActivity

----------: dispatchTouchEvent--------ACTION_MOVE----MainActivity
----------: onTouchEvent--------ACTION_MOVE----MainActivity

----------: dispatchTouchEvent--------ACTION_UP------MainActivity
----------: onTouchEvent--------ACTION_UP------MainActivity
复制代码

从事件的传递过程,咱们能够看出,当用户触摸屏幕进行操做时,事件先传递给 Activity,而后经过 dispatchTouchEvent 分发给跟布局 Group1 ,Group1 进行事件分发,先进行调用本身的 onInterceptTouchEvent 进行询问是否拦截(默认不拦截),而后就传递给当前点击的子控件 View1,View1 经过本身的 dispatchTouchEvent 方法分发给本身,调用本身的 onTouchEvent 方法。View1 默认不消费事件,事件就传递给了Group1 本身的 onTouchEvent 方法,因为 Group1 也不消费事件,事件就回传给了 Activity,最终 Activity 的 onTouchEvent 接收了事件,而且后续事件也是由它直接接收。

结论:

  1. 若是一个控件不消费传递过来的 DOWN 事件,那么后续事件不会传递给它。
  2. 若是一个点击区域的全部控件都不消费事件,那么这个事件最终会传递个 Activity 。

模拟二:

  • 条件:View1 的 onTouchEvent 方法返回 true ;
  • 动做:点击 View1 ;
//log日志
----------: dispatchTouchEvent--------ACTION_DOWN----MainActivity
----------: dispatchTouchEvent--------ACTION_DOWN----Group1
----------: onInterceptTouchEvent--------ACTION_DOWN----Group1
----------: dispatchTouchEvent--------ACTION_DOWN----View1
----------: onTouchEvent--------ACTION_DOWN----View1

----------: dispatchTouchEvent--------ACTION_MOVE----MainActivity
----------: dispatchTouchEvent--------ACTION_MOVE----Group1
----------: onInterceptTouchEvent--------ACTION_MOVE----Group1
----------: dispatchTouchEvent--------ACTION_MOVE----View1
----------: onTouchEvent--------ACTION_MOVE----View1

----------: dispatchTouchEvent--------ACTION_UP------MainActivity
----------: dispatchTouchEvent--------ACTION_UP------Group1
----------: onInterceptTouchEvent--------ACTION_UP------Group1
----------: dispatchTouchEvent--------ACTION_UP------View1
----------: onTouchEvent--------ACTION_UP------View1
复制代码

典型的点击事件传递过程,用户点击一个按钮,按钮来响应操做,事件被这个按钮消费。而且每次事件传递过来时,Group1 都会调用 onInterceptTouchEvent 方法来进行检查是否拦截。

模拟三:

  • 条件1:View1 的 onTouchEvent 方法返回 true, Group1 的 onInterceptTouchEvent 返回 true 。
  • 动做1:点击 View1 ;
//Log日志
----------: dispatchTouchEvent--------ACTION_DOWN----MainActivity
----------: dispatchTouchEvent--------ACTION_DOWN----Group1
----------: onInterceptTouchEvent--------ACTION_DOWN----Group1
----------: onTouchEvent--------ACTION_DOWN----Group1
----------: onTouchEvent--------ACTION_DOWN----MainActivity
        
----------: dispatchTouchEvent--------ACTION_MOVE----MainActivity
----------: onTouchEvent--------ACTION_MOVE----MainActivity
        
----------: dispatchTouchEvent--------ACTION_UP------MainActivity
----------: onTouchEvent--------ACTION_UP------MainActivity
复制代码
  • 条件2:View1 的 onTouchEvent 方法返回 true ,Group1 的 onInterceptTouchEvent 返回 true ,Group1 的 onTouchEvent 返回 true 。
  • 动做2:点击 View1 。
//Log日志
----------: dispatchTouchEvent--------ACTION_DOWN----MainActivity
----------: dispatchTouchEvent--------ACTION_DOWN----Group1
----------: onInterceptTouchEvent--------ACTION_DOWN----Group1
----------: onTouchEvent--------ACTION_DOWN----Group1
    
----------: dispatchTouchEvent--------ACTION_MOVE----MainActivity
----------: dispatchTouchEvent--------ACTION_MOVE----Group1
----------: onTouchEvent--------ACTION_MOVE----Group1
    
----------: dispatchTouchEvent--------ACTION_UP------MainActivity
----------: dispatchTouchEvent--------ACTION_UP------Group1
----------: onTouchEvent--------ACTION_UP------Group1
复制代码

Group1 的 onInterceptTouchEvent 返回 true 拦截事件,它的 onTouchEvent 被调用。onTouchEvent 返回 true 表明事件被消费,后续事件都传递给它来处理;onTouchEvent 返回 false 表明事件没有消费,事件向上传递给 Activity ,后续事件都不会传递给 Group1 和 View1 来处理。

结论:

  1. ViewGroup 一旦拦截事件后,后续事件就会交给它来处理,而且不会再调用 onInterceptTouchEvent 方法询问是否拦截。
  2. 再次验证了 若是一个控件不消费传递过来的 DOWN 事件,那么后续事件不会传递给它

总结

其余知识点:

CANCEL 事件的理解

事件序列的非人为的提早结束。

模拟条件:

  • View1 的 onTouchEvent 方法返回 true ;
  • Group1 的 onInterceptTouchEvent 在 MOVE 事件上返回 true,其余事件返回false。
  • 其余方法保持返回 super.xxx()。

模拟动做: 点击 View1 ;

public class Group1 extends FrameLayout {
    public static final String TAG = "----------";
    //......代码省略......
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.i(TAG, "onInterceptTouchEvent--------" + Util.getMotionEvent(ev) + this.getClass().getSimpleName());
        boolean intercept;
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                intercept = false;
                break;
            case MotionEvent.ACTION_MOVE:
                intercept = true;
                break;
            case MotionEvent.ACTION_UP:
                intercept = false;
                break;
            default:
                intercept = false;
                break;
        }
        return intercept;
    }
}
复制代码
//log日志
----------: dispatchTouchEvent--------ACTION_DOWN----MainActivity
----------: dispatchTouchEvent--------ACTION_DOWN----Group1
----------: onInterceptTouchEvent--------ACTION_DOWN----Group1
----------: dispatchTouchEvent--------ACTION_DOWN----View1
----------: onTouchEvent--------ACTION_DOWN----View1
        
----------: dispatchTouchEvent--------ACTION_MOVE----MainActivity
----------: dispatchTouchEvent--------ACTION_MOVE----Group1
----------: onInterceptTouchEvent--------ACTION_MOVE----Group1
----------: dispatchTouchEvent--------ACTION_CANCEL--View1
----------: onTouchEvent--------ACTION_CANCEL--View1
        
----------: dispatchTouchEvent--------ACTION_MOVE----MainActivity
----------: dispatchTouchEvent--------ACTION_MOVE----Group1
----------: onTouchEvent--------ACTION_MOVE----Group1
----------: onTouchEvent--------ACTION_MOVE----MainActivity
        
----------: dispatchTouchEvent--------ACTION_UP------MainActivity
----------: dispatchTouchEvent--------ACTION_UP------Group1
----------: onTouchEvent--------ACTION_UP------Group1
----------: onTouchEvent--------ACTION_UP------MainActivity
复制代码

在这个事件流中,View1 首先消费了 DOWN 事件。当用户移动,产生了 MOVE 事件,因为 Group1 的 onInterceptTouchEvent 方法在 MOVE 事件中返回 true,表示开始拦截事件,MOVE 事件及后面的 UP 事件都交给 Group 1处理,再也不传递给 View1。而此时 View1 只消费了 DOWN 事件,处于一个事件流的中途阶段,为了让 View1 有一个完整的事件流,就传递给 View1 一个 CANCEL 事件,从而告诉 View1 这个事件流对于它来讲已经结束了。

requestDisallowInterceptTouchEvent

请求父控件不要拦截事件。属于 ViewParent 的方法,ViewParent是一个接口,ViewGroup 实现了 ViewParent 接口。

模拟条件:

  • View1 的 onTouchEvent 方法返回 true ,在 dispatchTouchEvent 方法中调用 requestDisallowInterceptTouchEvent 方法;
  • Group1 的 onInterceptTouchEvent 在 MOVE 事件上返回 true,其余事件返回false。
  • 其余方法保持返回 super.xxx()。

模拟动做: 点击 View1 ;

public class View1 extends View {
    public static final String TAG = "----------";
    //......代码省略......
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.i(TAG, "dispatchTouchEvent--------" + Util.getMotionEvent(event) + this.getClass().getSimpleName());
        getParent().requestDisallowInterceptTouchEvent(true);
        return super.dispatchTouchEvent(event);
    }
}
复制代码
----------: dispatchTouchEvent--------ACTION_DOWN----MainActivity
----------: dispatchTouchEvent--------ACTION_DOWN----Group1
----------: onInterceptTouchEvent--------ACTION_DOWN----Group1
----------: dispatchTouchEvent--------ACTION_DOWN----View1
----------: onTouchEvent--------ACTION_DOWN----View1
        
----------: dispatchTouchEvent--------ACTION_MOVE----MainActivity
----------: dispatchTouchEvent--------ACTION_MOVE----Group1
----------: dispatchTouchEvent--------ACTION_MOVE----View1
----------: onTouchEvent--------ACTION_MOVE----View1
        
----------: dispatchTouchEvent--------ACTION_UP------MainActivity
----------: dispatchTouchEvent--------ACTION_UP------Group1
----------: onInterceptTouchEvent--------ACTION_UP------Group1
----------: dispatchTouchEvent--------ACTION_UP------View1
----------: onTouchEvent--------ACTION_UP------View1
复制代码

咱们能够看到 View1 调用 requestDisallowInterceptTouchEvent 后,可以接收到 MOVE 事件以及后续其余事件,Group1 的拦截并无起做用。从而咱们可使用 requestDisallowInterceptTouchEvent 来解决一些开发商的滑动冲突之类的问题。

注意: View 调用 requestDisallowInterceptTouchEvent 请求父控件不拦截生效有意义的前提是: View接收到了 DOWN 事件,而且消费了 DOWN 事件。若是一个控件不消费 DOWN 事件,那么后续事件也不会传递给它。

源码分析

View的事件分发(二)源码分析


本篇文章用于记录学习过程当中的理解和笔记,若有错误,请批评指正,万分感谢!

事件分发系列文章:

View的事件分发(二)源码分析

View的事件分发(三)源码分析

参考文档

《Android开发艺术探索》第三章

Android事件分发机制,大表哥带你慢慢深刻

相关文章
相关标签/搜索