View树的绘制流程
参考Android 8.0源码
在View类和ViewGroup类中,都有同样的静态内部类MeasureSpec。每一个控件View都包含了一个静态内部类MeasureSpec,这个类用来记录控件的测量模式和测量大小。
在ViewGroup类中有一个静态内部类LayoutParams,还有一个静态内部类MarginLayoutParams继承自LayoutParams类。在其他的容器类型的控件中,也都有一个静态内部类LayoutParams,继承自ViewGroup类的内部类MarginLayoutParams。
这里先表明一下,ViewRootImpl类是在WindowManagerGlobal.java类中初始化的。是在WindowManagerGlobal.java类的addView()方法中进行初始化的,然后调用ViewRootImpl的setView()方法。以下是ViewRootImpl.java类中的调用关系:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
//---;
requestLayout();
//---;
}
public void requestLayout() {
//---;
scheduleTraversals();
//---;
}
void scheduleTraversals() {
//---;
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
//---;
}
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
void doTraversal() {
//---;
performTraversals();
//---;
}
private void performTraversals() {
//---;
}
performTraversals()
ViewRootImpl负责Activity整个GUI的绘制,而绘制是从ViewRootImpl的performTraversals()方法开始的。
performTravsals()方法中关键的三个方法如下:
//1576-2361;
private void performTraversals() {
//代码省略;
//2155,2181;
performMeasure(childWidthMeasureSpec,childHeightMeasureSped);
//代码省略;
//2200;
performLayout(lp,mWidth,mHeight);
//代码省略;
//2347;
performDraw();
//省略代码;
}
performMeasure()方法测量组件的大小,performLayout()方法用于子组件的定位(放在窗口的什么地方),而performDraw()方法自然就是将组件的外观绘制出来了。
performMeasure()--测量组件的大小
performMeasure()方法负责组件自身尺寸的测量。在layout布局文件中,每一个组件都必须设置layout_width和layout_height属性,属性值有三种可选模式:wrap_content、match_parent和数值,performMeasure()方法根据设置的模式计算出组件的宽度和高度。事实上,大多数情况下,模式为match_parent和数值的时候是不需要计算的,传过来的就是父容器自己计算好的尺寸或是一个指定的精确值,只有当模式为wrap_content的时候才需要根据内容进行尺寸的测量。
//2404-2414;
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
对象mView是View树的根视图,代码中调用了mView的measure()方法。
//View.java中的measure()方法;
//21961-22024;
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
//省略代码;
onMeasure(widthMeasureSpec, heightMeasureSpec);
//省略代码;
}
onMeasure()方法是为组件尺寸的测量预留的功能接口,当然也定义了默认的实现,默认的实现并没有太多意义,在绝大部分情况下,我们需要重写onMeasure()方法。
如果测量的是容器的尺寸,而容器的尺寸又依赖于子组件的大小,所以必须先测量容器中子组件的大小,不然,测量出来的宽度和高度永远为0。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom;
measuredWidth += optical ? opticalWidth : -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
performLayout()--确定子组件的位置
performLayout()方法用于确定子组件的位置,所以该方法只针对于ViewGroup容器类。作为容器必须为容器中的子View精确定义位置和大小。
//2467-2542;
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,int desiredWindowHeight) {
//代码省略;
host.layout(0, 0,host.getMeasuredWidth(),host.getMeasuredHeight());
//代码省略;
getRunQueue().post(new Runnable() {
@Override
public void run() {
int numValidRequests = finalRequesters.size();
for (int i = 0; i < numValidRequests; ++i) {
final View view = finalRequesters.get(i);
Log.w("View", "requestLayout() improperly called by " + view + " during second layout pass: posting in next frame");
view.requestLayout();
}
}
});
}
代码中的host是View树中的根视图(DecorView),也就是最外层容器,容器的位置安排在左上角(0,0),其大小默认会填满mContentParent容器。layout()方法的代码如下(在View.java类中):
//19571-19616;
public void layout(int l, int t, int r, int b) {
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
if (shouldDrawRoundScrollbar()) {
if(mRoundScrollbarRenderer == null) {
mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
}
} else {
mRoundScrollbarRenderer = null;
}
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLayoutChangeListeners != null) {
ArrayList<OnLayoutChangeListener> listenersCopy =
(ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
int numListeners = listenersCopy.size();
for (int i = 0; i < numListeners; ++i) {
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
}
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) {
mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
notifyEnterOrExitForAutoFillIfNeeded(true);
}
}
在layout()方法中,在定位之前如果需要重新测量组件的大小,则先调用onMeasure()方法,接下来执行setOptioncalFrame()或setFrame()方法确定自身的位置与大小(setOptioncalFrame()方法最终也是调用setFrame()方法的),此时只是保存了相关的值,与具体的绘制无关。随后,onLayout()方法被调用,该方法是空方法,代码如下:
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
onLayout()方法在这里的作用是当前组件为容器时,负责定位容器中的子组件,这其实是一个递归的过程,如果子组件也是一个容器,该容器依然要负责它的子组件的定位,依此类推,直到所有的组件都定位完成为止。也就是说,从最顶层的DecorView开始定位,像多米诺骨牌一样。从上往下驱动,最后每一个组件都放到了它应该出现的位置上。onLayout()方法和onMeasure()方法一样,是为开发人员预留的功能扩展接口,自定义容器时,该方法必须重写。
绘制组件
performDraw()方法执行组件的绘制功能,组件绘制是一个十分复杂的过程,不仅仅绘制组件本身,还要绘制背景、滚动条,好消息是每个组件只需要负责自身的绘制,而且一般来说,容器组件不需要绘制,ViewGroup已经做了大量的工作。
//2781-2841;
private void performDraw() {
//代码省略;
final boolean fullRedrawNeeded = mFullRedrawNeeded;
mFullRedrawNeeded = false;
mIsDrawing = true;
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
try {
draw(fullRedrawNeeded);
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
//代码省略;
}
在performDraw()方法中调用了draw()方法:
//2843-3017;
private void draw(boolean fullRedrawNeeded) {
//代码省略;
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
//代码省略;
}
//3022-3117;
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,boolean scalingRequired, Rect dirty) {
final Canvas canvas;
final int left = dirty.left;
final int top = dirty.top;
final int right = dirty.right;
final int bottom = dirty.bottom;
canvas = mSurface.lockCanvas(dirty);
if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
}
mView.draw(canvas);
surface.unlockCanvasAndPost(canvas);
}
在drawSoftware()中,出现了Canvas。绘制组件是通过Canvas类完成的,该类定义了若干个绘制图形的方法,通过Paint类配置绘制参数,便能绘制出各种图案效果。为了提高绘图的性能,使用了Surface技术,Surface提供了一套双缓存机制,能大大加快绘图效率,而我们绘图时需要的是Canvas对象也是Surface创建的。
drawSoftware()方法中调用了mView的draw()方法,mView是Activity界面中View树的根(DecorView),也是一个容器(具体来说是一个FrameLayout布局容器),所以直接来看FrameLayout类的draw()方法,但是FrameLayout中没有重写draw()方法,ViewGroup中也没有重写draw()方法,要到View类中去查看draw()方法。
//19088-19295;
public void draw(Canvas canvas) {
if(!dirtyOpaque){
drawBackground(canvas);
}
if(!dirtyOpaque){
onDraw(canvas);
}
dispatchDraw(canvas);
onDrawForeground(canvas);
}
drawBackground(canvas):绘制背景;
在该方法中,使用了mBackground,是一个Drawable对象,直接绘制在Canvas上,并且与组件要绘制的内容互不干扰。onDraw():绘制自己;View只是组件的抽象定义,并不知道自己什么样子,所以,View定义了onDraw()这样一个空方法,和onMeasure()、onLayout()一样,onDraw()方法同样是预留给子类扩展的功能接口,用于绘制组件自身。组件的外观由该方法来决定。
dispatchDraw(canvas):绘制子视图;
dispatchDraw()方法也是一个空方法,该方法服务于容器组件,容器中的子组件必须通过实现dispatchDraw()方法进行绘制,所以,View类虽然没有实现该方法但它的子类ViewGroup实现了该方法。
//3927-4058;
protected void dispatchDraw(Canvas canvas) {
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
int flags = mGroupFlags;
for (int i = 0; i < childrenCount; i++) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
final LayoutParams params = child.getLayoutParams();
attachLayoutAnimationParameters(child, params, i, childrenCount);
bindLayoutAnimation(child);
}
}
for (int i = 0; i < childrenCount; i++) {
while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
final View transientChild = mTransientViews.get(transientIndex);
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
more |= drawChild(canvas, transientChild, drawingTime);
}
transientIndex++;
if (transientIndex >= transientCount) {
transientIndex = -1;
}
}
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
}
在dispatchDraw()方法中,循环遍历每一个组件,并调用drawChild()方法绘制子组件,而子组件又调用View的draw()方法绘制自己;
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
组件的绘制也是一个递归过程,说到底Activity的UI界面的根一定是容器,根容器绘制结束后开始绘制子组件,子组件如果是容器继续往下递归绘制,否则将子组件绘制出来,直到所有组件正确绘制为止。
- onDrawForeground(canvas):绘制前景,其中有绘制进度条;
private void drawBackground(Canvas canvas) {
setBackgroundBounds();
}
protected void dispatchDraw(Canvas canvas) {
}
public void onDrawForeground(Canvas canvas) {
onDrawScrollBars(canvas);
}
总体来说,UI界面的绘制从开始到结束要经历几个过程:
- 测量大小,调用onMeasure()方法;
- 组件定位,回调onLayout()方法;
- 组件绘制,回调onDraw()方法;
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
----------------------last line for now-----------------------