1、View绘制流程
- onMeasure() 确定View的测量宽高。
- onLayout() 确定View的最终宽高和四个顶点的位置。
- onDraw() 将View 绘制到屏幕上。
2、MeasureSpec有三种测量模式:
2.1. EXACTLY
(精确模式)
- 含义:父容器明确指定了子
View
的精确尺寸,子View
必须使用该尺寸。 - 典型场景:
- 布局中设置了固定值(如
android:layout_width="100dp"
)。 - 子
View
的宽/高设置为match_parent
,且父容器有确定尺寸。
- 布局中设置了固定值(如
- 子
View
行为:必须直接使用MeasureSpec
中的size
作为最终尺寸。
2.2. AT_MOST
(最大模式)
-
含义:父容器指定了子
View
的最大可用尺寸,子View
的尺寸不能超过该值,但可以更小。 -
典型场景:
-
子
View
的宽/高设置为wrap_content
。 -
父容器为
ScrollView
或RecyclerView
等可滚动的容器。
-
-
子
View
行为:根据自身内容计算尺寸,但最终尺寸不能超过MeasureSpec
中的size
。
2.3. UNSPECIFIED
(未指定模式)
- 含义:父容器对子
View
无任何约束,子View
可以自由决定尺寸(通常根据自身逻辑或内容)。 - 典型场景:
- 自定义
View
或ViewGroup
需要多次测量(如ListView
测量子View
的高度)。 - 系统内部测量(如
ScrollView
在测量子View
的滚动范围时)。
- 自定义
- 子
View
行为:完全由自身决定尺寸(可能使用默认值或内容所需尺寸)。
3、事件分发机制
3.1 事件分发:dispatchTouchEvent
用来进行事件的分发,如果事件能够传递给当前View,则该方法一定会被调用。返回结果受当前View的onTouchEvent和下级的dispatchTouchEvent的影响,表示是否消耗当前事件。
原型:public boolean dispatchTouchEvent(MotionEvent ev)
return:
- ture:当前View消耗所有事件
- false:停止分发,交由上层控件的onTouchEvent方法进行消费,如果本层控件是Activity,则事件将被系统消费,处理
3.2 事件拦截:onInterceptTouchEvent
需注意的是在Activity,ViewGroup,View中只有ViewGroup有这个方法。故一旦有点击事件传递给View,则View的onTouchEvent方法就会被调用
在dispatchTouchEvent内部使用,用来判断是否拦截事件。如果当前View拦截了某个事件,那么该事件序列的其它方法也由当前View处理,故该方法不会被再次调用,因为已经无须询问它是否要拦截该事件。
原型:public boolean onInterceptTouchEvent(MotionEvent ev)
return:
- ture:对事件拦截,交给本层的onTouchEvent进行处理
- false:不拦截,分发到子View,由子View的dispatchTouchEvent进行处理
- super.onInterceptTouchEvent(ev):默认不拦截
3.3 事件处理:onTouchEvent
在dispatchTouchEvent中调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一事件序列中,当前View无法再接受到剩下的事件,并且事件将重新交给它的父元素处理,即父元素的onTouchEvent会被调用
原型:public boolean onTouchEvent(MotionEvent ev)
return:
- true:表示onTouchEvent处理后消耗了当前事件
- false:不响应事件,不断的传递给上层的onTouchEvent方法处理,直到某个View的onTouchEvent返回true,则认为该事件被消费,如果到最顶层View还是返回false,则该事件不消费,将交由Activity的onTouchEvent处理。
- super.onTouchEvent(ev):默认消耗当前事件,与返回true一致。
事件分发机制总结:
在分析事件分发机制时,应该从事件分发的顺序入手一步一步解剖。从上文我们知道事件分发顺序为:Activity->Window->DecorView->ViewGroup->View。由于Window与DecorView可以看作是Activity->ViewGroup的过程,故这里将从三部分通过源码来分析事件分发机制:
- Activity对点击事件的分发机制
- ViewGroup对点击事件的分发机制
- View对点击事件的分发机制
- 当一个点击事件发生后,总是先传递给当前的Activity,由Activity的dispatchTouchEvent进行分发,而Activity会将事件传递给Window,然后由Window的唯一实现类PhoneWindow将事件传递给DecorView,接着DecorView将事件传递给自己的父类ViewGroup,此时的ViewGroup就是通过setContentView所设置的View,故可以称为顶级View,这时候ViewGroup可能是自己处理该事件或者传递给子View,但是最终都会调用View的dispatchTouchEvent来处理事件。
- 在View的dispatchTouchEvent中,如果设置了onTouchListener,会调用其onTouch方法,如果onTouch返回true,则不再调用onTouchEvent。如果有设置点击事件,则在onTouchEvent会调用onClick方法。如果子View的onTouchEvent返回了false,则表示不消耗事件,事件会回传给上一级的ViewGroup的onTouchEvent,如果所有的ViewGroup都没有返回true,则最终会回传到Activity的onTouchEvent。
4、requestLayout(), invalidate(), postInvalidate() 方法区别
requestLayout方法只会导致当前view的measure和layout,而draw不一定被执行,只有当view的位置发生改变才会执行draw方法,因此如果要使当前view重绘需要调用invalidate。
invalidate在UI线程中调用,postInvalidate在非UI线程中调用。因为android的UI线程是非线程安全的,所以在非UI线程中,需要使用postInvalidate来使View重绘。view调用invalidate将导致当前view的重绘(draw调用),view的父类将不会执行draw方法;viewGroup调用invalidate会使viewGroup的子view调用draw,也就是viewGroup内部的子view进行重绘;