事件体系中的几个基础类

MotionEvent

点击事件的封装。

getX/Y

相当于当前View左上角的x,y坐标

getRawX/Y

相对于手机屏幕左上角的x,y坐标

GestureDetector 手势识别器

@Overridepublic boolean onTouchEvent(MotionEvent event) {if (gestureDetector == null) {gestureDetector = new GestureDetector(getContext(), this);gestureDetector.setOnDoubleTapListener(this);}gestureDetector.onTouchEvent(event);


VelocityTracker 移动速度计算器

@Overridepublic boolean onTouchEvent(MotionEvent event) {if (velocityTracker == null) {velocityTracker = VelocityTracker.obtain();}velocityTracker.addMovement(event);velocityTracker.computeCurrentVelocity(100);Log.d(tag, "X方向实时速度:" + velocityTracker.getXVelocity() + "  Y方向实时速度:" + velocityTracker.getYVelocity());
  • addMovement 收集事件,速度计算的素材
  • computeCurrentVelocity 计算速度,入参如果是1000,输出速度的单位就是像素/秒 ,如果是1,单位就是像素/毫秒。
  • getXVelocity()/getYVelocity() 取到X/Y方向的速度。

TouchSlop

系统能识别的最小滑动距离 ——滑动的最小粒度,如果小于这个粒度的move,将不认为有过move事件发生。

Activity的dispatchTouchEvent

 public boolean dispatchTouchEvent(MotionEvent ev) {if (ev.getAction() == MotionEvent.ACTION_DOWN) {onUserInteraction();}if (getWindow().superDispatchTouchEvent(ev)) {return true;}return onTouchEvent(ev);}

事件要么被底层的View处理,要么事件将由Activity的onTouchEvent来执行。

getWindow是什么?

phoneWindow是window的实现。

PhoneWindow的superDispatchTouchEvent

@Override
public boolean superDispatchTouchEvent(MotionEvent event) {return mDecor.superDispatchTouchEvent(event);
}

mDecor是什么?

// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;

在PhoneWindow中的顶层view:DecorView.而DecorView本身就是FrameLayout,

public boolean superDispatchTouchEvent(MotionEvent event) {return super.dispatchTouchEvent(event);
}

那么其实最终调用的就是ViewGroup的dispatchTouchEvent方法。

ViewGroup的dispatchTouchEvent

final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;if (!disallowIntercept) {intercepted = onInterceptTouchEvent(ev);ev.setAction(action); // restore action in case it was changed} else {intercepted = false;}
} else {// There are no touch targets and this action is not an initial down// so this view group continues to intercept touches.intercepted = true;
}
  • 1.当为Down事件时,肯定不存在FLAG_DISALLOW_INTERCEPT(不允许拦截)的标志(因为Down事件会清空所有状态),所以必定会走onInterceptTouchEvent的方法。
  • 2.mFirstTouchTarget==null的话,意味着没有child View能够处理该Touch Event ,如果此刻事件不是Down,那么事件将强制由自己拦截处理。所以intercepted强置为true。
for (int i = childrenCount - 1; i >= 0; i--) {final int childIndex = customOrder? getChildDrawingOrder(childrenCount, i) : i;final View child = (preorderedList == null)? children[childIndex] : preorderedList.get(childIndex);.....newTouchTarget = getTouchTarget(child);if (newTouchTarget != null) {// Child is already receiving touch within its bounds.// Give it the new pointer in addition to the ones it is handling.newTouchTarget.pointerIdBits |= idBitsToAssign;break;}....if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {newTouchTarget = addTouchTarget(child, idBitsToAssign);alreadyDispatchedToNewTouchTarget = true;break; }
  • newTouchTarget!=null 就break的意思是:前面的事件过程中已经找到了会处理touch event的child view,则不用再去查找了。
  • newTouchTarget==null的时候就需要逐一查找了,dispatchTransformedTouchEvent会间接的调用对应child view的dispatchTouchEvent,最终会调用到onTouchEvent, 如果处理该事件返回true,执行addTouchTarget(后面专门讲),并将alreadyDispatchedToNewTouchTarget置为true,它的意义是告诉后面的过程,在dispatchTransformedTouchEvent中已经执行touch event的处理,所以不要再做了。

addTouchTarget mFirstTouchTarget

 private TouchTarget addTouchTarget(View child, int pointerIdBits) {TouchTarget target = TouchTarget.obtain(child, pointerIdBits);target.next = mFirstTouchTarget;mFirstTouchTarget = target;return target;}
  • child View会被封装为一个TouchTarget.
  • 链表形式储存管理
  • mFirstTouchTarget存储最近添加进来的TouchTarget
  • 什么时候清空呢?在Down事件触发时,执行resetTouchState中,会将该链表整个的清空掉。

回头再看getTouchTarget

private TouchTarget getTouchTarget(View child) {for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {if (target.child == child) {return target;}}return null;}

遍历整个TouchTarget链表,查看child view是否跟链表中的某个touchTarget有关联,存在则说明该child view可处理该touch event.

 // Dispatch to touch targets.if (mFirstTouchTarget == null) {// No touch targets so treat this as an ordinary view.handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);} else {// Dispatch to touch targets, excluding the new touch target if we already// dispatched to it.  Cancel touch targets if necessary.TouchTarget predecessor = null;TouchTarget target = mFirstTouchTarget;while (target != null) {final TouchTarget next = target.next;if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {handled = true;} else {....if (dispatchTransformedTouchEvent(ev, cancelChild,target.child, target.pointerIdBits)) {handled = true;}....}predecessor = target;target = next;}}
  • mFirstTouchTarget是链表之首,如果它为空,则说明TouchTarget链表为空,该viewGroup下没有任何child view会处理该touch event, 调用dispatchTransformedTouchEvent且入参child为null,结果是viewGroup会调用自身的dispatchTouchEvent,最终会将自己onTouchEvent执行。详情如下:
 //noinspection SimplifiableIfStatementListenerInfo li = mListenerInfo;if (li != null && li.mOnTouchListener != null&& (mViewFlags & ENABLED_MASK) == ENABLED&& li.mOnTouchListener.onTouch(this, event)) {result = true;}if (!result && onTouchEvent(event)) {result = true;}
  • 当mFirstTouchTarget不为空,说明child view能够处理该touch event。alreadyDispatchedToNewTouchTarget为true的时候,只是将handled置为true就啥也没干,原因前面已经讲了。其他情况时,会执行dispatchTransformedTouchEvent,这个函数的作用就是将事件分发给可处理该touch event的child view.
  • 利用while (target != null)循环遍历整个TouchTarget链表,将事件分发给整个链表的child view.

时序图

来个Demo

页面结构

试验1:没有任何childView处理TouchEvent

试验2:最里层的 TextView处理TouchEvent

现象1

在Down事件时,ViewGroup下的没有任何childView处理TouchEvent,那么Up事件到来时,事件并没有下发到我们的布局中。

原因

顶层ViewGroup:DecorView拦截了该touch event,所以事件不会再下发到. 具体原因见上面小节 ViewGroup的dispatchTouchEvent的第2点。

得出结论:

如果没有任何childView处理TouchEvent,后续的touch event不再传递下去,父级会自己拦截处理。

现象2

如果ViewGroup下的没有任何childView处理TouchEvent,则父级的onTouchEvent会执行,否则父级的onTouchEvent不会执行。

原因

如果没有找到处理touchEvent的childView, touch event会被dispatch给自身,自身的onTouchEvent就会被调用到。

得出结论

如果ViewGroup下的没有任何childView处理TouchEvent,则父级的onTouchEvent会执行,否则父级的onTouchEvent不会执行。

现象3

ViewGroup 有onInterceptTouchEvent方法,而View是没有的!

原因

  • 功能决定:ViewGroup 是可以作为View容器的,事件往child views下发的权利由它来掌控,所以这个控制节点就由onInterceptTouchEvent方法来做,但是普通的View是没有这个控制权利的,固然也不需要这个方法。

  • 比较试验1和试验2,如果child view有处理touch event,则ViewGroup的onTouchEvent是没有机会执行的,那么如果ViewGroup想让自己来处理这个事件呢?那只有利用onInterceptTouchEvent返回true来将事件拦截下来。

结论

onInterceptTouchEvent是ViewGroup所独有的,是控制事件往下下发的关卡。ViewGroup的onTouchEvent获得执行机会有两种情况:

  • 其属下child view都不处理touch event,事件处理返回到ViewGroup。
  • 覆写onInterceptTouchEvent并返回true.

Note:

  • 上面对触控事件的讨论都是基于从Down开始到Up结束所包含的触控事件集合,因为在Down开始之初会将之前的状态clear掉。

【Android View事件分发机制】原理相关推荐

  1. 【Android View事件分发机制】关于拦截事件的注意点

    在父容器拦截事件时,为什么不能拦截DOWN事件呢? 先看看源码: 回顾一下事件分发机制原理,当事件来了之后,如果父容器不拦截,则会询问其child view ,当某child view 有事件需求,父 ...

  2. Android View 事件分发机制详解

    想必很多android开发者都遇到过手势冲突的情况,我们一般都是通过内部拦截和外部拦截法解决此类问题.要想搞明白原理就必须了解View的分发机制.在此之前我们先来了解一下以下三个非常重要的方法: di ...

  3. 一文读懂Android View事件分发机制

    Android View 虽然不是四大组件,但其并不比四大组件的地位低.而View的核心知识点事件分发机制则是不少刚入门同学的拦路虎.ScrollView嵌套RecyclerView(或者ListVi ...

  4. 【Android View事件分发机制】滑动冲突

    View内容滑动概念 scrollTo scrollBy scrollTo(x,y) x,y 是绝对值,如果x,y不变,重复调用是不会移动的. scrollBy(x,y) x,y是增量之,每次调用都会 ...

  5. 安卓自定义View进阶-事件分发机制原理【转自 app架构师 微信公众号】

    注意:本文中所有源码分析部分均基于 API23(Android 6.0) 版本,由于安卓系统源码改变很多,可能与之前版本有所不同,但基本流程都是一致的. 为什么要有事件分发机制? 安卓上面的View是 ...

  6. 安卓自定义View进阶-事件分发机制原理

    之前讲解了很多与View绘图相关的知识,你可以在 安卓自定义View教程目录 中查看到这些文章,如果你理解了这些文章,那么至少2D绘图部分不是难题了,大部分的需求都能满足,但是关于View还有很多知识 ...

  7. Android之事件分发机制

    本文主要包括以下内容 view的事件分发 viewGroup的事件分发 首先来看两张图 在执行touch事件时 首先执行dispatchTouchEvent方法,执行事件分发. 再执行onInterc ...

  8. Android面试老生常谈的 View 事件分发机制,看这一篇就够了

    本文首发我的微信公众号:徐公,想成为一名优秀的 Android 开发者,需要一份完备的 知识体系,在这里,让我们一起成长,变得更好~. 在 Android 开发当中,View 的事件分发机制是一块很重 ...

  9. Android 系统(218)---Android的事件分发机制以及滑动冲突的解决

    Android的事件分发机制以及滑动冲突的解决 声明:  本文主要涉及VIew的事件分发与滑动冲突的解决,关于View的事件分发流程的部分内容参考自:  Android事件分发机制详解:史上最全面.最 ...

最新文章

  1. 进程间通信(一)管道
  2. python collection counter_python collection模块中几种数据结构(Counter、OrderedDict、namedtup)详解...
  3. python京东抢购脚本_五个月抢京东抢茅台心得
  4. python切换消息窗_用Python切换窗口
  5. C/C++ realloc()函数解析
  6. Atitit.输入法配置说明v1 q229
  7. Java中字符输入输出流
  8. 2015年5月移动游戏Benchmark
  9. 学单片机的动力是什么,学单片机来做什么,需要多长时间把它学会
  10. SARscape操作:Sentinel-1 SLC影像镶嵌、裁切
  11. BigDecimal 使用浅析
  12. 乌班图服务器系统网卡驱动,Linux_Ubuntu系统无法衔接网络 该怎样安装无线网卡驱动?,ubuntu已经很好的兼容了主流的 - phpStudy...
  13. 实时监控网页变化,并增加多种提示信息
  14. Duality-Gated Mutual Condition Network for RGBT Tracking
  15. 关于大数据相关的问答汇总,持续更新中~
  16. 获取ZoneId 收录的时区和偏移量
  17. 用python打印学生名单_Python 之 MySql 每日一练 231——查询1990年出生的学生名单...
  18. 第1章 ADAMS/View 基础知识
  19. 晶体三极管原理及应用
  20. 怎么把html封装成桌面应用,如何将一个现有的Vue网页项目封装成electron桌面应用...

热门文章

  1. IDEA一直提示 错误: 找不到或无法加载主类
  2. 一个关于小程序Iot的具体实现(MQTT版)
  3. 物联网帝国——孙正义职业生涯最大的豪赌
  4. 【Python】《大话设计模式》Python版代码实现
  5. 【SQL Server】系统学习之三:逻辑查询处理阶段-六段式
  6. 蘋果全新MacBook價高質更精
  7. visual studio code跳转到定义处插件
  8. mvc学习-编辑提交需要注意-mvc重点
  9. 计算机组成原理与系统结构---内存编址方法
  10. 使用babel编译es6