Android事件传递机制

参考博客与书籍

android之View坐标系(view获取自身坐标的方法和点击事件中坐标的获取)
Android高级进阶百度网盘

1.概述

1.1什么是触摸事件

  • 我们每对屏幕的一次操作(最常见就是点击,移动,和松开)都会产生触摸事件,每次产生的事件都会包含大量的信息(触摸点相对于当前View的x,y值,相对当前容器的x,y值,当前触摸的类型等)
  • 这些信息都被封装在一个MotionEvent类中,我们可以通过这个类提供的方法获取到

1.2事件传递的阶段与相关函数

首先我们需要明确的是,具有事件传递能力的是View,ViewGroup,Activity

虽然ViewGroup也是View的子类,但是我们说View的时候,是单个View,而不能作为其他View的容器

一个事件传递主要是3个过程:
分发->拦截(只有ViewGroup才可以拦截)->消费
注意:父控件会一级级的往下分发和拦截,传递下去之后再从最内层的UI控件一级一级往上开始消费事件

它们分别对应的方法是

/* 
 * 分发事件,不常用,当屏幕上有触摸事件的时候,最先调用此方法
 * 你可以在你的Activity或者View,ViewGroup中重写这个方法进行处理
 * /
public boolean dispatchTouchEvent(MotionEvent ev);


/*
 * 只有ViewGroup才有,因为他有子View,可以考虑拦截掉此次事件子View无法获取
 * 只有ViewGroup才能重写这个方法,然后进行处理
 * 返回值为true,则拦截,返回false则不拦截
 * /
public boolean onInterceptTouchEvent(MotionEvent ev);

/*
 * 在这里进行可以进行事件处理
 * 返回值为false表示传递回给父ViewGroup,返回值为true表示真正会消费此次事件,不传递回去
 * /
public boolean onTouchEvent(MotionEvent event);
  • 当一个事件传递过来的时候,每个ViewGroup都可能有以上三个过程(就可以重写这三个函数来处理)
  • 每个View和Activity都可能有分发和消费两个过程
  • 这里说可能是因为有被拦截或者下级控件将事件真正消费无法传递回来的情况

2.父ViewGroup和子view传递与函数返回值关系细节探索

给定场景:
布局是ViewGroupA里面包含一个ViewGroupB,ViewGroupB中包含一个viewA
然后在一个MainActivity中用ViewGroupA做根布局再
分别重写他们的以上三个方法(注意Activity和View没有onInterceptTouchEvent方法)

注意:

"按下->移到->松开"称为一个 "触摸过程"
按下会产生一个事件,移动因为是连续的,会连续产生很多移动事件,松开也会产生一个移动事件

2.1第一种情况,不人为干涉的情况下(即利用系统默认的返回值)

当有手指按下的时候,那么就会产生按下事件,此按下事件的传递的过程就是

  MainActivity调用分发 -> ViewGroupA调用分发和拦截 ->
  ViewGroupB调用分发和拦截 -> ViewA分发,消费->
  ViewGroupB消费 -> ViewGroupA消费 -> MainActivity调用消费
  • 注意这里说的消费是调用onTouchEvent事件(如果我们手动改成返回true,则表示真正消费此时事件,如果子控件真正消费一个事件那么该事件就不会向上级传递了,也说明系统默认返回的是false)
  • 上面也说明,默认ViewGroup的拦截返回false也就是不拦截,这也很好理解,最开始肯定还是要保证一定把事件传递下去给需要的view或者viewgroup

整个触摸过程还没有结束

  • 这时候会马上传递一个移动事件下来(哪怕你的手指没有移动,也确实会有移动事件,我们可以通过事件发生的x和y坐标判断是不是真的移动了)
    此时到之后都只有 MainActivity调用分发和消费,当手指抬起也是

这里重点解释下,无论是View还是ViewGroup:

  1. 因为当有按下事件传递下来,如果你在onTouchEvent中的按下这种情况下如果返回了false(系统默认)就表示了你不想真正进行消费,不想对按下这个触摸事件就行处理,那么这次整个触摸过程你都无法接收到任何事件(但不影响下个触摸过程)
    同时产生的效果是你的上级ViewGroup的对下次传过来的事件(移动和松开)拦截也不会调用(很容易想明白,你都不处理了,你的上级也不需要拦截了)
  2. 但是如果你按下事件时在onTouchEvent中返回true,表示你真正消费此次事件(那么你的所有上级都没有机会消费事件,也就是不会调用onTouchEvent)
    不过再之后你在onTouchEvent中对移动和松手触摸事件返回false,在这此触摸过程中还是会继续接收到余下的移动松手事件,同时事件传给上级控件

onTouchEvent 主要是按下事件最先发下来,返回false,就表示你不想处理触摸事件,整个触摸过程不再接收,但是只要是此次返回true,之后不管返回什么都会继续接收触摸事件事件

如果你在ViewA中的消费返回了true,那么整个触摸过程中每个事件的传递都是:

MainActivity调用分发 -> ViewGroupA调用分发和拦截 -> 
ViewGroupB调用分发和拦截 -> ViewA分发,消费

之后的移动松手都是重复以上过程

2.2第二种情况,测试下拦截:

如果你在**ViewGroupA中拦截写死了返回true,并且在消费中也返回了true,**那么结果就会变成这样

MainActivity调用分发 -> ViewGroupA调用分发和拦截 -> ViewGroupA消费 
以上是按下事件的传递,再之后移动事件与松手都会重复一下过程
MainActivity调用分发 -> ViewGroupA调用分发 -> ViewGroupA消费
  • 我们拦截函数返回true之后拦截函数不会再调用,其实很好理解,你已经做出确认拦截,以后子控件绝对无法接收到任何事件,那么拦截函数也没有再调用的必要了

还有一种情况就是:让子控件接收到按下的触摸事件,然后拦截之后的移动和松手事件,那么后面的子控件会接收一个"取消"的触摸事件,再然后在这个触摸过程中不会接收到任何事件

3.一些特殊ui控件的特殊情况

上面是常见情况,但是由于Android本身对ViewGroup有多种实现,存在一些特别的情况

  • 一个view默认情况下,onTouchEvent是返回false,但是如果将其添加了一个点击或者长按监听,就会返回true
  • 有些ViewGroup的onTouchEvent默认情况返回的是True,例如GridView(这个注意自己用之前测试一下,我就没有一一测试所有的情况了)
  • 同时如果我们给一个View通过Drawable中设置了按压和选择会改变背景(注意这个view必须将可点击或者可长按点击等使能才会生效,当然有些默认使能),注意如果你在接收到按下事件之后没有接收到松开事件(上级拦截掉了,或者在onTouchEvent中对按下返回了false),那么即使手指松开还会处于点击状态没有不会恢复
  • ViewPager这个ui控件,它默认情况不会拦截事件,但是当界面滑动时,他就会将事件拦截掉(其实很好理解,毕竟ViewPager内部肯定要对这个触摸事件分析,偏移量,页面选择情况等,不希望受到子控件的干扰)

以上这些特殊情况都是本人亲自踩的坑,希望大家能够按照业务需要处理好自己的ui控件的事件请求

4.MotionEvent相关方法

MotionEvent相关方法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值