一、概念



View可以说是Android中的第五大控件了,不管是Button还是TextView还是复杂的RelativeView,他们的共同基类都是View,View是界面层控件的一种抽象,View的四个参数就是左上右下,这四个属性是相对属性,相对于父控件的属性。我们还可以通过一定的方式来获取View滑动的速度和手势等。
二、滑动

View的滑动可通过三种方式来实现

(1)scrollTo/scrollBy:操作简单,适合对View内容的滑动,直接设置一个目的坐标即可

(2)动画:主要用于没有交互的View和比较复杂的动画效果

(3)属性:操作复杂,适用于有交互的View

弹性滑动:使View进行比较平滑的过渡,而不是生硬的过渡

(1)Scroller中使用的方式就是多次重绘的方式,通过invalidate函数来一点点更新View

(2)通过动画的方式,在规定的时间内完成一个动画,其实和Scroller类似,也是通过百分比的方式

(3)使用延时策略,例如Handler的postDelayed方法

三、事件分发

当你手指按了屏幕,点击事件就会遵循Activity->Window->View这一顺序传递。

这一传递过程有三个重要的方法,分别是:boolean dispatchTouchEcent(MotionEvent ev),boolean onInterceptTouchEvent(MotionEvent event),boolean onTouchEvent(MotionEvent event)

先一个一个简单介绍下:

(1)dispatchTouchEcent:

只要事件传递到了当前View,那么dispatchTouchEcent方法就一定会被调用。返回结果表示是否消耗当前事件。

(2)onInterceptTouchEvent:

在dispatchTouchEcent方法内部调用此方法,用来判断是否拦截某个事件。如果当前View拦截了某个事件,那么在这同一个事件序列中,此方法不会再次被调用。返回结果表示是否拦截当前事件。

(3)onTouchEvent:

在dispatchTouchEcent方法内调用此方法,用来处理事件。返回结果表示是否处理当前事件,如果不处理,那么在同一个事件序列里面,当前View无法再收到后续的事件。

上面的解释听起来比较抽象,我们可以用一段伪代码来表示上面三个方法的关系:

public boolean dispatchTouchEvent(MotionEvent ev){boolean consum = false;if(onInterceptTouchEvent(ev)){consum = onTouchEvent(ev);}else{consum = child.dispatchTouchEvent(ev);}return consum;
}

上面代码很好的解释了三个方法之间的关系,我们也可以从代码中大致摸索到事件传递的顺序规则:当点击事件传递到根ViewGroup里,会执行dispatchTouchEvent,在其内部会先调用onInterceptTouchEvent询问是否拦截事件,若拦截,则执行onTouchEvent方法处理这个事件;若不拦截,则执行子元素的dispatchTouchEvent,进入向下分发的传递,直到事件被处理。

在处理一个事件的时候,是有优先级的,如果设置了OnTouchListener,会先执行其内部的onTouch方法,这时若onTouch方法返回true,那么表示事件被处理了,不会向下传递了;如果返回了false,那么事件会继续传递给onTouchEvent方法处理,在onTouchEvent方法中如果当前设置了OnClickListener,那么就会调用其onClick方法。所以其优先级为:OnTouchListen>onTouchEvent>OnClickListen。

四、11条规定

《Android开发艺术探索》这本书里总结了11条关于事件传递的结论:

1:同一个事件序列是指手机接触屏幕那一刻起,到离开屏幕那一刻结束,有一个down事件,若干个move事件,一个up事件构成。

2:某个View一旦决定拦截事件,那么这个事件序列之后的事件都会由它来处理,并且不会再调用onInterceptTouchEvent。

3:正常情况下,一个事件序列只能被一个View拦截并消耗。这个原因可以参考第2条,因为一旦拦截了某个事件,那么这个事件序列里的其他事件都会交给这个View来处理,所以同一事件序列中的事件不能分别由两个View同时处理,但是我们可以通过特殊手段做到,比如一个View将本该自己处理的事件通过onTouchEvent强行传递给其他View处理。

4:一个View如果开始处理事件,如果它不处理down事件(onTouchEvent里面返回了false),那么这个事件序列的其他事件就不会交给它来继续处理了,而是会交给它的父元素去处理。

5:如果一个View处理了down事件,却没有处理其他事件,那么这些事件不会交给父元素处理,并且这个View还能继续受到后续的事件。而这些未处理的事件,最终会交给Activity来处理。

6:ViewGroup的onInterceptToucheEvent默认返回false,也就是默认不拦截事件。

7:View没有InterceptTouchEvent方法,如果有事件传过来,就会直接调用onTouchEvent方法。

8:View的onTouchEvent方法默认都会消耗事件,也就是默认返回true,除非他是不可点击的(longClickable和clickable同时为false)。

9:View的enable属性不会影响onTouchEvent的默认返回值。就算一个View是不可见的,只要他是可点击的(clickable或者longClickable有一个为true),它的onTouchEvent默认返回值也是true。

10:onClick方法会执行的前提是当前View是可点击的,并且它收到了down和up事件。

11:事件传递过程是由外向内的,也就是事件会先传给父元素在向下传递给子元素。但是子元素可以通过requestDisallowInterceptTouchEvent来干预父元素的分发过程,但是down事件除外(因为down事件方法里,会清除所有的标志位)。

五、原理

事件的传递顺序是Activity->Windows->View

Window的唯一实现是PhoneWindow,PhoneWindow将事件直接传递给了DecorView

Activity -> PhoneWindow -> DecorView -> ViewGroup -> ... -> View

这样的事件分发机制逻辑非常清晰,可是,你是否注意到一个问题?如果最后分发到View,如果这个View也没有处理事件怎么办,就这样让事件浪费掉?

当然不会啦,如果没有任何View消费掉事件,那么这个事件会按照反方向回传,最终传回给Activity,如果最后 Activity 也没有处理,本次事件才会被抛弃:

Activity <- PhoneWindow <- DecorView <- ViewGroup <- ... <- View

看到这里,我不禁微微一皱眉,这个东西咋看起来那么熟悉呢?再仔细一看,这不就是一个非常经典的责任链模式吗, 如果我能处理就拦截下来自己干,如果自己不能处理或者不确定就交给责任链中下一个对象。

六、冲突解决

滑动冲突分为两种形式

(1)外部滑动方向和内部滑动方向不一致

(2)外部滑动方向和内部滑动方向一致

第一种的冲突主要是一个横向一个竖向的,所以我们只要判断滑动方向是竖向还是横向的,再让对应的View滑动即可。判断的方法有很多,比如竖直距离与横向距离的大小比较;滑动路径与水平形成的夹角等等。

对于这种情况,比较特殊,我们没有通用的规则,得根据业务逻辑来得出相应的处理规则。举个最常见的例子,ListView下拉刷新,需要ListView自身滑动,但是当滑动到头部时需要ListView和Header一起滑动,也就是整个父容器的滑动。如果不处理好滑动冲突,就会出现各种意想不到情况。

处理方式

(1)外部拦截法

让事件都经过父容器的拦截处理,如果父容器需要则拦截,如果不需要则不拦截,成为外部拦截法,其伪代码如下:

public boolean onInterceptTouchEvent(MotionEvent event) {boolean intercepted = false;int x = (int)event.getX();int y = (int)event.getY();switch (event.getAction()) {case MotionEvent.ACTION_DOWN: {intercepted = false;break;}case MotionEvent.ACTION_MOVE: {if (满足父容器的拦截要求) {intercepted = true;} else {intercepted = false;}break;}case MotionEvent.ACTION_UP: {intercepted = false;break;}default:break;}mLastXIntercept = x;mLastYIntercept = y;return intercepted;
}
在这里,首先down事件父容器必须返回false ,因为若是返回true,也就是拦截了down事件,那么后续的move和up事件就都会传递给父容器,子元素就没有机会处理事件了。其次是up事件也返回了false,一是因为up事件对父容器没什么意义,其次是因为若事件是子元素处理的,却没有收到up事件会让子元素的onClick事件无法触发。
(2)内部拦截法
父容器不拦截任何事件,将所有事件传递给子元素,如果子元素需要则消耗掉,如果不需要则通过requestDisallowInterceptTouchEvent方法交给父容器处理,称为内部拦截法,使用起来稍显麻烦。伪代码如下:
首先我们需要重写子元素的dispatchTouchEvent方法:
@Overridepublic boolean dispatchTouchEvent(MotionEvent event) {int x = (int) event.getX();int y = (int) event.getY();switch (event.getAction()) {case MotionEvent.ACTION_DOWN: {parent.requestDisallowInterceptTouchEvent(true);break;}case MotionEvent.ACTION_MOVE: {int deltaX = x - mLastX;int deltaY = y - mLastY;if (父容器需要此类点击事件) {parent.requestDisallowInterceptTouchEvent(false);}break;}case MotionEvent.ACTION_UP: {break;}default:break;}mLastX = x;mLastY = y;return super.dispatchTouchEvent(event);}

然后修改父容器的onInterceptTouchEvent方法:

@Overridepublic boolean onInterceptTouchEvent(MotionEvent event) {int action = event.getAction();if (action == MotionEvent.ACTION_DOWN) {return false;} else {return true;}}

这里父容器也不能拦截down事件


尊重作者,尊重原创,参考文章:

http://www.jianshu.com/p/057832528bdd

http://www.cnblogs.com/aademeng/articles/6541321.html

《Android开发艺术探索》

View事件分发机制相关推荐

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

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

  2. View事件分发机制(源码 API27)

    1.什么是事件分发机制 当用户触摸屏幕时,会产生一个touch事件,这个touch事件(motionEvent)传递到某个具体的view处理的整个过程 用户触摸屏幕会产生一个事件流(ACTION_DO ...

  3. View事件分发机制(源码分析篇)

    01.Android中事件分发顺序 1.1 事件分发的对象是谁 事件分发的对象是事件.注意,事件分发是向下传递的,也就是父到子的顺序. 当用户触摸屏幕时(View或ViewGroup派生的控件),将产 ...

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

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

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

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

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

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

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

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

  8. 【Android View事件分发机制】原理

    事件体系中的几个基础类 MotionEvent 点击事件的封装. getX/Y 相当于当前View左上角的x,y坐标 getRawX/Y 相对于手机屏幕左上角的x,y坐标 GestureDetecto ...

  9. View 事件分发机制

    View 中的事件消息传递,是android的一个重点和难点,我们只有掌握了它,才能更好的理解view,写出自己比较满意的自定义控件,解决控件嵌套时产生的滑动冲突和点击事件失效问题. 我们知道 Vie ...

最新文章

  1. 【LeetCode】309. Best Time to Buy and Sell Stock with Cooldown
  2. lambda 和 std::function
  3. MySQL max_allowed_packet设置及问题
  4. Unity3D 旋转
  5. ICPC2019南昌区域赛
  6. Oracle中日期和时间字段的日常使用
  7. CentOS开机流程
  8. cmd管道无法接收特定程序返回值_CQRS amp; Event Sourcing — 解决检索应用程序状态问题的一剂良方...
  9. Android系统源码分析--Process启动过程
  10. C语言练习题~斐波那契数列
  11. 计算机课后感400字,观后感400字
  12. 轻松熊喵喵个人笔记 -- java学习路线记录
  13. js实现,同域名下pc,移动网站模板切换跳转
  14. ipad 在线打代码 code-server
  15. 计算机作业上海世博会,上海世博会开启城市生活新未来
  16. 一串文字检测被删和被拉黑的好友!
  17. Java太密来福_这篇文章就是要让你入门java多线程【多线程入门】-Go语言中文社区...
  18. 联想笔记本电脑开机黑屏可能是什么原因
  19. Android lunch分析以及产品分支构建
  20. 关系与关系模式的区别——易懂

热门文章

  1. 图解ArcGIS数据三维显示
  2. mysql 1033_mysql报错1033 Incorrect information in file: ''''xxx.frm''''问题的解决方法(图)...
  3. java 图片不失真缩放,ico格式图片转换,透明图层,jar->exe
  4. 昨天的梦想 今天的幸福
  5. 微信公众号创建菜单注意问题
  6. 讯时网站管理系统通杀0DAY漏洞
  7. linux--常用命令
  8. java语言 用Switch语句划分成绩
  9. 卧室.餐厅.客厅要选择挂什么油画?
  10. 平面上点和直线的齐次表示