Android——简单易懂说原理之View的点击事件分发机制
第一次写自定义View还是在14年春节,之前公司有个通信的项目,里面有个图形化展示OCC或者ODF的端子状态界面。最早都是用XML手动嵌套的,直接写死了一排是12个端子,一个子框里有6排。后来随着业务的增长,一排12个端子显然不能满足需求,一开始是手动写一排16个端子和一排8个端子的XML,终于有一天变为需要用户自定义端子排数和每排端子数了,于是自定义一个端子的View也不得不提上日程。
罗里吧嗦地说了一堆前言没进去正题,我的意思是说,自定义View很常见,在自定义view过程中,了解机制也很重要。
当然,面试时候也经常会问。
有很多阅读源码的讲解文章,其实很多人是看不进去的,比如我,一堆源码当时能看懂,但是在脑中形成不了图,正所谓记得快忘的也快,所以这次准备利用图表+文字的形式来尝试用简单易懂的方式来讲述下常见功能的背后原理。
一、View和ViewGroup
View是Android所有控件的基类,ViewGroup可以理解为是View的组合。一个ViewGroup可以包含很多View以及ViewGroup,而这个包含的ViewGroup又可以包含View和ViewGroup,以此类推,如下图所示。
ViewGroup也继承自View。ViewGroup作为View或者ViewGroup这些组件的容器派生了多种布局控件子类,View的部分继承关系如下图所示。
二、Activity的构成
一个Activity包含一个Window对象,这个对象是由PhoneWindow来实现的;PhoneWindow将DecorView作为整个应用窗口的根View;这个DecorView将屏幕划分为两个区域:一个是TitleView,另一个是ContentView,我们平常做应用所写的布局就是展示在ContentView中的。
三、View的点击事件分发机制
当我们点击屏幕时,就产生了MotionEvent,系统会将这个MotionEvent传递给View的层级,MotionEvent在View中的层级传递过程就是点击事件的分发。
点击事件分发有3个重要的方法,如下:
- dispatchTouchEvent(MotionEvent ev)用来进行事件的分发;
- onInterceptTouchEvent(MotionEvent ev)用来进行事件的拦截,在dispatchTouchEvent()中调用,这个方法是ViewGroup特有的;
- onTouchEvent(MotionEvent ev)用来处理点击事件,在dispatchTouchEvent()中调用
一个完整的事件序列是以DOWN开始,以UP结束。
当点击事件产生后,事件首先会传递给当前的Activity,这时会调用Activity的dispatchTouchEvent()方法(事件的处理工作是交由Activity中的PhoneWindow来完成的),然后PhoneWindow再把事件处理工作交给DecorView,之后再由DecorView将事件处理工作交给根ViewGroup。
判断当前ViewGroup是否拦截了事件,默认onInterceptTouchEvent()返回值是false,即不拦截事件,如果想拦截事件,需要在自定义的ViewGroup中重写这个方法。
如果当前ViewGroup拦截了事件,那么后续的事件序列都将交给他处理,如果没有拦截,则交由子View来处理。
在事件分发中,如果有子View,则调用子View的dispatchTouchEvent()方法,如果ViewGroup没有子View,则调用super.dispatchTouchEvent(event)方法,因为ViewGroup是继承View的。
需要记住的是OnTouchListener中的onTouch()方法优先级要高于onTouchEvent(event)方法。
最后,点击事件分发的这3个重要方法的关系如下图所示。
onInterceptTouchEvent方法和onTouchEvent方法都在dispatchTouchEvent方法中调用。
四、总结
首先总结一下点击事件由上而下的传递规则。当点击事件产生后会由Activity来处理,传递给PhoneWindow,再传递给DecorView,最后传递给ViewGroup。一般在事件传递中只考虑ViewGroup的onInterceptTouchEvent方法,因为一般情况下我们不会重写dispatchTouchEvent方法。对于根ViewGroup,点击事件首先传递给它的dispatchTouchEvent方法,如果该ViewGroup的onInterceptTouchEvent方法返回true,则表示它要拦截这个事件,这个事件就会交给它的onTouchEvent方法处理;如果onInterceptTouchEvent方法返回false,则表示它不拦截这个事件,则这个事件会交给它的子元素的dispatchTouchEvent来处理,如此反复下去。如果传递给底层的View,View是没有子View的,就会调用View的dispatchTouchEvent方法,一般情况下最终会调用View的onTouchEvent方法。
接下来总结一下点击事件由下而上的传递规则。当点击事件传给底层的View时,如果其onTouchEvent方法返回true,则事件由底层的View消耗并处理;如果返回false则表示该View不做处理,则传递给父View的onTouchEvent处理。如果父View的onTouchEvent仍旧返回false,则继续传递给该父View的父View处理,如此反复下去。
本文地址:https://blog.csdn.net/nanquan11/article/details/110532888