View绘制也是Android里重要知识点,明晰此流程方便我们了解Android的布局设置和自定义View。
View结构
Activity里包含了一个Window实现类PhoneWindow,而该类内部包含了一个DecorView对象,该DectorView对象是所有应用窗口(Activity界面)的根View。DecorView包含了一个WindowDecorActionBar和android.R.id.content的View,我们平时实现Activity自定义布局通过setContentView即填充此View。
- Activity:Activity是一个应用程序组件,用于实现用户交互。
- Window:它概括了Android窗口的基本属性和基本功能。其实现类是PhoneWindow。
- WindowManager:负责管理Window窗口。其实现类是WindowManagerImpl。
- WindowManagerGlobal:应用内单例,管理应用内所有View、ViewRootImpl、WindowManager。
- WindowManangerService:简称WMS,它是系统服务类,作用是管理所有应用程序中的窗口。
- DecorView:界面的根View,PhoneWindow的内部类。
- ViewRootImpl:ViewRoot是GUI管理系统与GUI呈现系统之间的桥梁。
- View:作为所有图形的基类。
- ViewGroup:对View继承扩展为视图容器类。
setContentView流程
Activity在onCreate时,通过setContentView实现了加载自定义布局。
1 | public void setContentView(View view) { |
PhoneWindow创建
首先PhoneWindow是在Activity的attach方法中创建的,PhoneWindow会设置一个WindowManager,所以每个Activity都会对应一个Window,Window会对应一个WindowManager。
1 | final void attach(...) { |
PhoneWindow的setContentView
1 | public void setContentView(int layoutResID) { |
初始化DecorView
1 | private void installDecor() { |
创建DecorView
1 | protected DecorView generateDecor(int featureId) { |
创建布局
1 | protected ViewGroup generateLayout(DecorView decor) { |
加载布局
1 | void onResourcesLoaded(LayoutInflater inflater, int layoutResource) { |
创建WindowDecorActionBar
1 | private void initWindowDecorActionBar() { |
整个setContentView流程如下:
- 在Activity生命周期attach方法里面创建出PhoneWindow;
- 调用PhoneWindow的setContentView方法;
- 在PhoneWindow里面创建DecorView,DecorView会去加载系统的一个布局;
- 将自己写的布局填充到DecorView布局里面id为android.R.id.content的View里。
- 初始化WindowDecorActionBar。
至此DecorView创建完毕,布局添加完成。
LayoutInflate流程
上面在分析setContentView过程中可以看见,以下两步利用了LayoutInflate加载布局:
- PhoneWindow的setContentView中的mLayoutInflater.inflate(layoutResID, mContentParent);
- PhoneWindow的generateLayout中的View in = mLayoutInflater.inflate(layoutResource, null);
LayoutInflate.inflate
1 | public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) { |
1 | public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) { |
rInflate
rInflateChildren底层也是调用了rInflate方法,因此核心看rInflate如何递归处理xml里的tag。
1 | void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs, |
上面方法主要就是循环递归解析xml文件,解析结束回调View类的onFinishInflate方法。
1 | protected void onFinishInflate() { |
View类的onFinishInflate方法是一个空方法。当我们自定义View时在构造函数inflate一个xml后可以实现onFinishInflate这个方法一些自定义的逻辑。
DecorView和PhoneWindow
布局加载之后,接下来则需要进行绘制。
Activity在onResume前,ActivityThread会在handleResumeActivity里调用makeVisible,将DecorView添加到WindowManager上并显示。
1 | void makeVisible() { |
WindowManager的实现类是WindowManagerImpl,通过addView将DecorView添加到ViewRootImpl上。
1 | @Override |
接着调用WindowManagerGlobal的addView。
1 | public void addView(View view, ViewGroup.LayoutParams params, |
最后在ViewRootImpl里,实现界面绘制。
1 | public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { |
ViewRootImpl.performTraversals
上面分析了从Activity到PhoneWindow到DecorView的绘制流程。根据前述流程,在id为android.R.id.content的View我们使用自定义layout的xml文件填充后,进行View树的绘制,绘制则是通过ViewRootImpl.performTraversals实现的。
1 | private void performTraversals() { |
核心就是上述三个方法:
- performMeasure:负责测量控件大小;
- performLayout:负责摆放控件位置;
- performDraw:负责绘制控件。
在performMeasure前,通过getRootMeasureSpec方法去计算预期宽高,作为参数传入,平时我们自定义View的Measure参数就是这样一层一层传下来的。
1 | int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); |
- MATCH_PARENT和具体大小则对应MeasureSpec.EXACTLY,设置为Window大小或者是具体大小。
- WRAP_CONTENT则对应MeasureSpec.AT_MOST,设置为控件大小。
performMeasure
1 | private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) { |
这里的mView即DecorView,也就是FrameLayout.onMeasure,实现父View的测量。
1 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
再来看看子View的measure,measure的主要目的就是对View树中的每个View的测量宽高进行赋值,所以一旦这两个变量被赋值意味着该View的测量工作结束。
1 | // final方法不可复写,已经我们自定义View只能覆写onMeasure方法 |
setMeasuredDimension用于存储测量的宽高值,getDefaultSize用于根据不同测量模式计算最终设置的大小值,getSuggestedMinimumHeight则对应xml里的android:minHeight字段判断最小值是否小于minHeight。
1 | public static int getDefaultSize(int size, int measureSpec) { |
上面说了View实际是嵌套的,而且measure是递归传递的,所以每个View都需要measure。实际能够嵌套的View一般都是ViewGroup的子类,所以在ViewGroup中定义了measureChildren, measureChild等方法来对子视图进行测量。
1 | protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) { |
至此View的measure过程就完成了。
最后通过一个图总结下上述流程:
performLayout
1 | private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) { |
ViewRootImpl的performLayout调用了DecorView的layout方法,具体布局实现为onLayout方法。
1 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { |
View里的onLayout为空方法,因为无子控件需要摆放,我们在自定义View时,如果View是子View一般也无需覆写onLayout方法。
再看看DecorView即FrameLayout的onLayout方法:
1 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { |
和measure流程类似,也是从父View到子View递归实现Layout布局。
至此View的layout过程就完成了。
用一张图来总结performLayout过程:
performDraw
测量和布局完成后,则进行绘制。
1 | private void performDraw() { |
实际上也是用了DecorView即FrameLayout.draw,FrameLayout.draw则是调用View.draw实现。
1 | public void draw(Canvas canvas) { |
draw的官方注释已经很详细了,主要分为6个步骤:
- 绘制背景
- 保存图层layer
- 绘制控件视图
- 绘制子控件视图
- 绘制渐变边缘和恢复图层layer
- 绘制前景
至此View的draw过程就完成了。