View的绘制流程
View 和 ViewGroup
View的继承树:
从View的继承树中可以看出,常见的TextView、ImageView均是继承于View的。
还有一个比较重要的ViewGroup也是继承于View的。
ViewGroup的继承树:
我们平时使用的一些布局就是ViewGroup的(用于包含其他的View),ViewGroup本身就是View。
View的绘制流程
一般我们的页面都是通过Activity中的onCreate()方法中的setContentView()设置进去的。
查看setContentView()
还需要继续去找对应的实现。我的版本为Android 10。
AppCompatDelegate类中的setContentView()是一个抽象方法。
通过查看对应的实现类即:AppCompatDelegateImpl 类,进入该类搜索setContentView方法。
@Override
public void setContentView(int resId) {
ensureSubDecor();
//找到ID为content的ViewGroup,它是一个Activity默认就有的布局。
ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
//清空contentParent内部的所有的子View
contentParent.removeAllViews();
//解析resId对应的布局文件,将他嵌入到contentParent内部
LayoutInflater.from(mContext).inflate(resId, contentParent);
//这里在发生内容变化的时候,通知某个类。这里发生了一个回调(Widow Callback)。
mAppCompatWindowCallback.getWrapped().onContentChanged();
}
setContentView()只是根据布局文件创建对应控件的View对象,并没有涉及怎么去绘制一个View。
View的绘制流程包括(View类中的三个API):measure(测量)、layout(布局)、draw(绘制)。
- 测量(measure)
这方法里面会去调用onMeasure()方法。在onMeasure方法中有一个setMeasuredDimension(),他必须被调用,否则会抛出IllegalStateException的异常。这个方法内部完成了对成员变量mMeasuredWidth和mMeasuredHeight的赋值。如果没有调用默认为零。
MeasureSpec中的前两位为父容器对子View的限制模式,后30位表示大小值。模式在MeasureSpec类中定义为:
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
/**
* Measure specification mode: The parent has not imposed any constraint
* on the child. It can be whatever size it wants.
*/
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
/**
* Measure specification mode: The parent has determined an exact size
* for the child. The child is going to be given those bounds regardless
* of how big it wants to be.
*/
public static final int EXACTLY = 1 << MODE_SHIFT;
/**
* Measure specification mode: The child can be as large as it wants up
* to the specified size.
*/
public static final int AT_MOST = 2 << MODE_SHIFT;
- 布局(layout)
参数的描述:
l Left position, relative to parent
t Top position, relative to parent
r Right position, relative to parent
b Bottom position, relative to parent
- 绘制(draw)
绘制过程的六步。
- Draw the background
- If necessary, save the canvas’ layers to prepare for fading
- Draw view’s content
- Draw children
- If necessary, draw the fading edges and restore layers
- Draw decorations (scrollbars for instance)
- If necessary, draw the default focus highlight
ViewGroup的绘制流程
ViewGroup 是一个 View的容器,可以包含子View,作为一个父容器,他应该承担更多的职责。
根据VIew的绘制流程的内容,一个View的测量、布局和绘制都是由父容器调用。如果以继承ViewGroup的方式自定义一个控件或布局,就必须要考虑是否以及如何测量布局和绘制子View。
ViewGroup的测量
这个我们可以去选择看DecorView的实现,或者去看线性布局等。DecorView的本质是一个FrameLayout。
VIewGroup的测量过程遵循View的测量过程,只是它还要发起子View的测量,通常测量子View在onMeasure方法中执行,一般的过程是遍历ViewGroup中的所有子View,获取子View在布局中的参数LayoutParams的宽高,然后指定一个模式,将宽高和模式构建成对子View的宽高的限制widthMeasureSpec和heightMeasureSpec ,然后调用子View的measure方法传入限制,完成对子View的测量。
ViewGroup的布局
ViewGroup的布局过程遵循View的布局过程,只是它还要发起子View的布局。ViewGroup同样会调用layout方法来完成自身的布局,并且在layout方法里还会调用onLayout方法。根据View类中layout方法的注释,如果View的子类是一个ViewGroup时,他必须实现onLayout方法,在onLayout方法中调用它包含的子View的layout方法去布局子View。
ViewGroup的绘制
在View类的绘制方法draw()中,第三步是调用onDraw方法绘制view的内容,第四步是调用dispatchDraw方法绘制子View,如果一个View没有子View,也就是不是VIewGroup,只需要实现onDraw方法绘制自己就可以了,如果是ViewGroup,则需要实现dispatchDraw()方法去绘制子View。
View的绘制流程深入解析
通过查看布局树,我们可以知道最顶层的视图是DecorView。
App的入口是ActivityThread类,他管理Activity的生命周期,其中包括了onResume方法的调用。
ActivityThread类执行handleResumeActivity方法,在handleResumeActivity 方法中会触发 onResume 方法的调用,接着后续代码会创建出 RootViewImpl 类来对窗口管理器 WindowManagerService 中添加PhoneView,在添加之前就会调用 RootViewImpl 内部的 requestLayout 方法来布局整个Activity的视图树。
- ActivityThread中的handleResumeActivity 方法
- RootViewImpl 内部的 requestLayout 方法
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();//检查是否在UI线程
mLayoutRequested = true;//标记当前正在请求的UI布局
scheduleTraversals();// 开启布局
}
}
scheduleTraversals 内部会执行一个 mTraversalRunnable 。
mTraversalRunnable 是一个Runnable对象,他的 run方法中调用了doTraversal()方法。
performTraversals 就是用来遍历视图树的。
这里面有
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);//开始测量
performLayout(lp, mWidth, mHeight);//开始布局过程
performDraw(); //开启绘制过程。
选择其中一个方法,会发现其中会调用View的相应方法。