Android事件传递(一)

1、事件的定义

  • 定义:当用户触摸屏幕时,将产生的触摸行为(Touch事件);
  • 事件的类型,有四种:

MotionEvent.ACTION_DOWN 手指初次接触到屏幕时触发;
MotionEvent.ACTION_UP 手指离开屏幕时触发;
MotionEvent.ACTION_MOVE 手指在屏幕上滑动时触发,会多次触发;
MotionEvent.ACTION_CANCEL 事件被上层拦截时触发;

  • 事件序列

正常情况下,一次手指触摸屏幕的行为会触发一系列点击事件
1、点击屏幕后立即松开,事件序列为DOWN–>UP;
2、点击屏幕滑动一会再松开,事件序列为DOWN–>MOVE–>…–>MOVE–>UP;

1.1 DOWN事件的特殊性

1、所有touch事件都是从DOWN事件开始的;
2、DOWN事件的处理结果会直接影响后续MOVE、UP事件的逻辑;
3、后续的MOVE、UP等事件分发交给谁,取决于它们的起始事件DOWN是由谁捕获的。

1.2 事件分发
  • 事件分发对象

Activity:控制生命周期 & 处理事件;
ViewGroup:一组View的集合(含多个子View);
View:所有UI组件的基类;

  • 事件分发主要方法

dispatchTouchEvent(MotionEvent ev):用来进行事件分发;
onInterceptTouchEvent(MotionEvent ev):判断是否拦截事件(只存在于ViewGroup中);
onTouchEvent(MotionEvent ev):处理点击事件

2、事件分发详解

2.1 事件分发–Activity
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            //空方法,子类可重写
            onUserInteraction();
        }
        //getWindow是PhoneWindow对象
        //最终调用ViewGroup.dispatchTouchEvent
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }
2.2事件分发— ViewGroup
  • ViewGroup的事件分发,可以用以下伪代码表示
    public boolean dispatchTouchEvent(MotionEvent ev) {
        boolean counsume = false;
        //调用onInterceptTouchEvent判断是否拦截
        if(onInterceptTouchEvent(ev)){
           //拦截则调用自身的onTouchEvent
           consume = onTouchEvent(ev);
        }else{
           //不拦截,将事件分发给子View
           consume = child.dispatchTouchEvent(ev);
        }
        return consume;
    }
2.3 事件分发— View
  • 事件分发流程
    在这里插入图片描述
2.4 总结:

1、一个事件序列从手指接触屏幕到手指离开屏幕,在这个过程中产生一系列事件,以DOWN事件开始,中间含有不定数的MOVE事件,以UP事件结束;
2、正常情况下,一个事件序列只能被一个View拦截并消耗;
3、某个View一旦决定拦截,那么这个时间序列都将由它的onTouchEvent处理,并且它的onIntenrceptTouchEvent不会被调用;
4、某个View一旦开始处理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent返回false),那么同一事件序列中其他事件都不会再交给它处理。并且重新交由它的父元素处理(父元素onTouchEvent被调用);
5、事件的传递过程是由外向内的,即事件总是先传递给父元素,然后再由父元素分发给子View,通过requestDisallowInterceptTouchEvent方法可以在子View中干预父元素的事件分发过程,但ACTION_DOWN除外;
6、ViewGroup默认不拦截任何事件,即onInterceptTouchEvent默认返回false。View没有onInterceptTouchEvent方法,一旦有点击事件传递给它,那么它的onTouchEvent方法就会被调用;
7、View的onTouchEvent默认会消耗事件(返回true),除非它是不可点击的(clickable和longClickable同时为false)。View的longClickable默认都为false,clickable要分情况,比如Button的clickable默认为true,TextView的clickable默认为false;
8、View的enable属性不影响onTouchEvent的默认返回值。哪怕一个View是disable状态的,只要它的clickable或者longClickable有一个为true,那么它的onTouchEvent就返回true;
9、onClick会影响的前提是当前View是可点击的,并且收到了ACTION_DOWN和ACTION_UP的事件,并且受长按事件影响,当长按事件返回true时,onClick不会响应;
10、onLongClick在ACTION_DOWN里判断是否响应,要想执行长按事件该View必须是longClickable并且设置了OnLongClickListener。

3、事件传递测试

3.1 正常事件传递过程
  • MyViewGroup.class
  public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.e(TAG, "parent:dispatchTouchEvent" + ev.getAction());
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.e(TAG, "parent:onInterceptTouchEvent" + ev.getAction());
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        Log.e(TAG, "parent:onTouchEvent" + ev.getAction());
        return super.onTouchEvent(ev);
    }
  • MyView.class
 @Override
    public void setOnTouchListener(OnTouchListener l) {
        Log.e(TAG, "child: setOnTouchListener");
        super.setOnTouchListener(l);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.e(TAG, "child: dispatchTouchEvent" + event.getAction());
        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e(TAG, "child: onTouchEvent" + event.getAction());
        return super.onTouchEvent(event);
    }
  • 测试结果
2021-04-05 22:38:56.594 15581-15581/com.laoda.androdstudy E/Constraints: parent:dispatchTouchEvent===0
2021-04-05 22:38:56.594 15581-15581/com.laoda.androdstudy E/Constraints: parent:onInterceptTouchEvent===0
2021-04-05 22:38:56.595 15581-15581/com.laoda.androdstudy E/Constraints: child: dispatchTouchEvent===0
2021-04-05 22:38:56.595 15581-15581/com.laoda.androdstudy E/Constraints: child: onTouchEvent===0
2021-04-05 22:38:56.608 15581-15581/com.laoda.androdstudy E/Constraints: parent:dispatchTouchEvent===2
2021-04-05 22:38:56.608 15581-15581/com.laoda.androdstudy E/Constraints: parent:onInterceptTouchEvent===2
2021-04-05 22:38:56.608 15581-15581/com.laoda.androdstudy E/Constraints: child: dispatchTouchEvent===2
2021-04-05 22:38:56.608 15581-15581/com.laoda.androdstudy E/Constraints: child: onTouchEvent===2
2021-04-05 22:38:56.609 15581-15581/com.laoda.androdstudy E/Constraints: parent:dispatchTouchEvent===1
2021-04-05 22:38:56.609 15581-15581/com.laoda.androdstudy E/Constraints: parent:onInterceptTouchEvent===1
2021-04-05 22:38:56.609 15581-15581/com.laoda.androdstudy E/Constraints: child: dispatchTouchEvent===1
2021-04-05 22:38:56.609 15581-15581/com.laoda.androdstudy E/Constraints: child: onTouchEvent===1
  • 总结:正常事件分发流程中,父View首先将DOWN事件分发给子View,子View捕获到DOWN事件并消费该事件;后续父View继续向子View分发MOVE、UP事件。
  • 其中:action_DOWN=0; ACTION_UP = 1; ACTION_MOVE=2; ACTION_CANCEL=3;
3.2 ViewGroup中onInterceptTouchEvent()为true时
  • MyViewGroup.class
  @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.e(TAG, "parent:onInterceptTouchEvent" +"==="+ ev.getAction());
        return true;
    }
  • 测试结果
2021-04-05 22:41:27.654 21961-21961/com.laoda.androdstudy E/Constraints: parent:dispatchTouchEvent===0
2021-04-05 22:41:27.654 21961-21961/com.laoda.androdstudy E/Constraints: parent:onInterceptTouchEvent===0
2021-04-05 22:41:27.655 21961-21961/com.laoda.androdstudy E/Constraints: parent:onTouchEvent===0
  • 总结:这里为什么没有MOVE UP事件的日志呢? 如果ViewGroup的onTouchEvent返回的是true呢?

1、事件被父View拦截,子View捕获不到DOWN事件,且父View又不能自行消费,此时父View会向上传递自己不能处理事件的信号,后续上层也不会再向父View分发事件,故日志中无MOVE、UP的日志;
2、如果ViewGroup中onTouchEvent返回的是true,代表父View能自行消费事件,后续MOVE、UP事件都由父View消费。

3.3 当ViewGroup中onInterceptTouchEvent拦截MOVE事件时
  • MyViewGroup.class
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_MOVE) {
            return true;
        }
        Log.e(TAG, "parent:onInterceptTouchEvent" + "===" + ev.getAction());
        return super.onInterceptTouchEvent(ev);
    }
  • 测试结果
2021-04-06 10:43:46.439 3596-3596/com.laoda.androdstudy E/Constraints: parent:dispatchTouchEvent===0
2021-04-06 10:43:46.440 3596-3596/com.laoda.androdstudy E/Constraints: parent:onInterceptTouchEvent===0
2021-04-06 10:43:46.440 3596-3596/com.laoda.androdstudy E/Constraints: child: dispatchTouchEvent===0
2021-04-06 10:43:46.441 3596-3596/com.laoda.androdstudy E/Constraints: child: onTouchEvent===0
2021-04-06 10:43:46.493 3596-3596/com.laoda.androdstudy E/Constraints: parent:dispatchTouchEvent===2
2021-04-06 10:43:46.494 3596-3596/com.laoda.androdstudy E/Constraints: child: dispatchTouchEvent===3
2021-04-06 10:43:46.494 3596-3596/com.laoda.androdstudy E/Constraints: child: onTouchEvent===3
2021-04-06 10:43:46.498 3596-3596/com.laoda.androdstudy E/Constraints: parent:dispatchTouchEvent===1
2021-04-06 10:43:46.499 3596-3596/com.laoda.androdstudy E/Constraints: parent:onTouchEvent===1
  • 总结:此种情况,MOVE事件被父View拦截,子View只能捕获到DOWN事件不能继续向后捕获,则调用CANCEL事件;后续的MOVE、UP事件由父View消费。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值