写在前面:
最近在学习《Android开发艺术探索》关于View的工作原理的内容,虽然讲解的很细致,但总觉得仅仅看一遍还是难以对View的整体绘制过程有较为明晰的认识。接下来,我根据简单的代码,重点观察分析View的measure– layout – draw 绘制过程。
一、xml文件
其中,RevealLayout本质上就是LinearLayout。MyButton本质上就是Button。这里是为了方便观察绘制过程,对相关方法添加了打印信息。
<com.ryg.chapter_4.ui.RevealLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="12dp"
tools:context="${relativePackage}.${activityClass}" >
<com.ryg.chapter_4.ui.MyButton
android:id="@+id/button1"
style="@style/AppTheme.Button.Green"
android:onClick="onButtonClick"
android:text="View Test" />
</com.ryg.chapter_4.ui.RevealLayout>
二、 运行结果
如下图,就只显示一个简单的Button,重点是对其绘制过程的观察。
三、View绘制过程方法调用
控件构造器:RevealLayout()、MyButton() → RevealLayout().onMeasure→ RevealLayout().measureChildWithMargins→ MyButton.onMeasure → RevealLayout().onLayout → MyButton.layout → MyButton.onLayout → RevealLayout().draw → RevealLayout().onDraw → RevealLayout().dispatchDraw → MyButton.draw → MyButton.onDraw
四、View绘制过程分析
1) View的绘制过程从何处开始呢?
关于这个问题,可参考http://www.cnblogs.com/jerehedu/p/4679534.html。Acitvity启动过程中,追溯其源码,可得出下图所示的调用过程:
Activity在启动过程中会调用主线程ActivityThread的handleResumeActivity方法,此方法会将DecorView和WindowManagerImpl对象关联起来。最终,ActivityThread中生成的DecorView经过WindowManagerImpl、WindowManagerGlobal,最终调用了ViewRootImpl中的setView方法,将DecorView设置赋值给了ViewRootImpl中的mView属性。在ViewRootImpl中调用的performTraversals方法将会开启View的整个绘制过程。
private void performTraversals() {
// cache mView since it is used so much below...
final View host = mView;
……
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
……
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
……
performDraw();
……
}
2)View绘制过程深入
measure过程:
1.View的measure方法为final类型,不能被重写。在measure方法中会调用onMeasure方法,因而一般重写onMeasure方法实现自定义View。
2.View的onMeasure方法本身设定View的测量后大小为android:minWidth属性所设置的值,此属性默认为0。如果为View指定了背景,则View的测量后大小为背景图片的原始尺寸。另外,直接继承View的控件需要重写onMeasure方法设置wrap_content时的自身大小,否则其效果等同于match_parent.
3.对于ViewGroup实例,比如LinearLayout、RelativeLayout等,不仅要根据自己的布局特性重写onMeasure方法还要实现对子元素进行measure的方法。
4.view的大小取决于:父容器的MeasureSpec、父容器的padding以及view本身的LayoutParams。
5.measure过程可能要进行多次才能确定最终的测量宽/高,因而在measure过程中去获取view的宽/高可能会不准建议在onLayout方法中获取。
6.view的measure过程和Activity的生命周期方法并不同步。不能保证某一个生命周期阶段measure确定完成。
layout过程:
1.layout方法确定View本身的位置,onLayout方法确定所有子元素的位置。
2.onLayout的具体实现和具体布局有关,所以View和ViewGroup都只提供了空方法,需要子类重写。
3.对ViewGroup实例,在onLayout方法中会遍历子元素调用其layout方法。
4.在onLayout方法中需要对LayoutParameters.margin进行处理。
draw过程:
View.draw()分为以下几步:
(1)background.draw():绘制背景;
(2)onDraw():绘制自己;
(3)dispatchDraw():绘制子元素;
(4)onDrawScrollbars:绘制装饰。
1.自定义View一般会重写onDraw方法,需要处理padding。
2.dispatchDraw方法实现draw事件的一层层传递。
3.自定义View时序注意View中的setWillNotDraw方法。此方法为优化标志位。
setWillNotDraw(true)表示view不需要绘制任何内容,系统会进行响应优化。
默认情况下,View未启动此标志位,ViewGroup启动此标志位。
当自定义控件继承自ViewGroup时且需要绘制内容时需显式关闭此优化标记位。