前言
大家都知道View是经过三大步骤(测量,摆放,绘制)后才显示出来的。但是这些操作是什么时候开始?大概经过怎样的处理呢?
为了弄清楚这些问题,必须查看源码,所以今天通过源码9.0来进一步了解这个过程。
这些步骤是什么时候开始?
从我们打开一个界面开始,ActivityThread中handleResumeActivity方法就会被调用,具体细节,以后有时间再进行讨论,此时我们先认为这个为绘制的入口,接下来进一步源码分析。
#ActivityThread --->handleResumeActivity
ViewManager wm = a.getWindowManager();
wm.addView(decor, l);
将decorView添加到Window
WindowManager实现类WindowManagerImpl实现了addView方法
#WindowManager ---> addView
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
接着交给了WindowManagerGlobal处理
#WindowManagerGlobal---> addView
private final ArrayList<View> mViews = new ArrayList<View>();
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams = newArrayList<WindowManager.LayoutParams>();
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
root.setView(view, wparams, panelParentView);
创建ViewRootImpl,并且将ViewRootImpl与DecorView建立关联
#ViewRootImpl --->setView()
setView方法会调用requestLayout(),scheduleTraversals();
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
void scheduleTraversals() {
1429 if (!mTraversalScheduled) {
1430 mTraversalScheduled = true;
1431 mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
1432 mChoreographer.postCallback(
1433 Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
1434 if (!mUnbufferedInputDispatch) {
1435 scheduleConsumeBatchedInput();
1436 }
1437 notifyRendererOfFramePending();
1438 pokeDrawLockIfNeeded();
1439 }
1440 }
重点在mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);该方法中,mChoreographer相当于维护着Looper,Handler(这里不深入),经过处理后mTraversalRunnable中的run方法将会被调用,接着就会调用到我们熟悉的performTraversals()方法了。
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
void doTraversal() {
。。。
performTraversals();
。。。
}
紧接着就会进行调用三大步骤了,performMeasure(),performLayout (),performDraw()
...
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
performLayout(lp, mWidth, mHeight);
...
performDraw()
...
performMeasure()
接着单独分析 performMeasure(),主要调用了view 的mView.measure(),mView为setView()的时候传进来的DecorView,所以这里调用的是DecorView里面的measure。
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
mView = view;
}
DecorView继承了FrameLayout ,所以就到了ViewGroup的测量过程了。
这里先补充一下关于MeasureSpec知识
MeasureSpec可以理解为测量的一种规格说明,“测量规格”。系统会根据View的layoutParams和父容器的限制转化为对应的MeasureSpec。MeasureSpec是一个32位的数值,高2位代表的是SpecMode,低30位代表的是SpecSize,可以通过 MeasureSpec.getSize(),MeasureSpec.getMode()获取对应的值。下面简单说一下三种模式。
UNSPECIFIED
父容器不对子View做限制。
EXACTLY
精确的,意思就是子view的值是确定的,就是指定的SpecSize。对应LayoutParams中的match_parent和具体的数值(例如在xml中敲下的10dp)。
AT_MOST
顾名思义,就是最大是多少,最大不能超过SpecSize。对应LayoutParams中的wrap_parent
接下来看一下DecorView MeasureSpec的创建过程
/**
2930 * Figures out the measure spec for the root view in a window based on it's
2931 * layout params.
2932 *
2933 * @param windowSize
2934 * The available width or height of the window
2935 *
2936 * @param rootDimension
2937 * The layout params for one dimension (width or height) of the
2938 * window.
2939 *
2940 * @return The measure spec to use to measure the root view.
2941 */
2942 private static int getRootMeasureSpec(int windowSize, int rootDimension) {
2943 int measureSpec;
2944 switch (rootDimension) {
2945
2946 case ViewGroup.LayoutParams.MATCH_PARENT:
2947 // Window can't resize. Force root view to be windowSize.
2948 measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
2949 break;
2950 case ViewGroup.LayoutParams.WRAP_CONTENT:
2951 // Window can resize. Set max size for root view.
2952 measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
2953 break;
2954 default:
2955 // Window wants to be an exact size. Force root view to be that size.
2956 measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
2957 break;
2958 }
2959 return measureSpec;
2960 }
该方法主要是根据不同类型的rootDimension组合成不同的MeasureSpec。主要有三种,即MATCH_PARENT填充父控件,SpecSize是窗口的大小,模式SpecMode就是精确的模式了(EXACTLY)。对于WRAP_CONTENT,SpecSize就是窗口的大小了,但是模式的话就是AT_MOST,意思是最大不能超过窗口大小。最后一种就是具体数值了,模式就是精确的,SpecSize为rootDimension。
普通View MeasureSpec的创建过程
6771 public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
6772 int specMode = MeasureSpec.getMode(spec);
6773 int specSize = MeasureSpec.getSize(spec);
6774
6775 int size = Math.max(0, specSize - padding);
6776
6777 int resultSize = 0;
6778 int resultMode = 0;
6779
6780 switch (specMode) {
6781 // Parent has imposed an exact size on us
6782 case MeasureSpec.EXACTLY:
6783 if (childDimension >= 0) {
6784 resultSize = childDimension;
6785 resultMode = MeasureSpec.EXACTLY;
6786 } else if (childDimension == LayoutParams.MATCH_PARENT) {
6787 // Child wants to be our size. So be it.
6788 resultSize = size;
6789 resultMode = MeasureSpec.EXACTLY;
6790 } else if (childDimension == LayoutParams.WRAP_CONTENT) {
6791 // Child wants to determine its own size. It can't be
6792 // bigger than us.
6793 resultSize = size;
6794 resultMode = MeasureSpec.AT_MOST;
6795 }
6796 break;
6797
6798 // Parent has imposed a maximum size on us
6799 case MeasureSpec.AT_MOST:
6800 if (childDimension >= 0) {
6801 // Child wants a specific size... so be it
6802 resultSize = childDimension;
6803 resultMode = MeasureSpec.EXACTLY;
6804 } else if (childDimension == LayoutParams.MATCH_PARENT) {
6805 // Child wants to be our size, but our size is not fixed.
6806 // Constrain child to not be bigger than us.
6807 resultSize = size;
6808 resultMode = MeasureSpec.AT_MOST;
6809 } else if (childDimension == LayoutParams.WRAP_CONTENT) {
6810 // Child wants to determine its own size. It can't be
6811 // bigger than us.
6812 resultSize = size;
6813 resultMode = MeasureSpec.AT_MOST;
6814 }
6815 break;
6816
6817 // Parent asked to see how big we want to be
6818 case MeasureSpec.UNSPECIFIED:
6819 if (childDimension >= 0) {
6820 // Child wants a specific size... let him have it
6821 resultSize = childDimension;
6822 resultMode = MeasureSpec.EXACTLY;
6823 } else if (childDimension == LayoutParams.MATCH_PARENT) {
6824 // Child wants to be our size... find out how big it should
6825 // be
6826 resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
6827 resultMode = MeasureSpec.UNSPECIFIED;
6828 } else if (childDimension == LayoutParams.WRAP_CONTENT) {
6829 // Child wants to determine its own size.... find out how
6830 // big it should be
6831 resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
6832 resultMode = MeasureSpec.UNSPECIFIED;
6833 }
6834 break;
6835 }
6836 //noinspection ResourceType
6837 return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
6838 }
此方法传进来的参数: spec是父容器的MeasureSpec,pad。ing为父容器已使用的空间大小,childDimension为子View的lp.width.
子view可用的大小就是父容器的大小减去padding,即size(6775行)。
该方法主要就是根据父容器的MeasureSpec再结合子View的大小childDimension进行创建子View的MeasureSpec。childDimension >= 0意思是子View自己设置了具体的值。
了解完MeasureSpec的创建,我们继续了解measure的过程。
ViewGroup的measure过程
LinearLayout有自己的measure方式,FrameLayout也有自己的measure过程.。所以如果你想实现自己的ViewGroup,想要有自己的测量规则,那么你就应该实现onMeasure方法。下面我将已LinearLayout进行简单分析。
大概就是先遍历所有的子View,逐个测量孩子的大小,(调用孩子的measure()方法),如果孩子是ViewGroup,则又进入了onMeasure进行循环测量, 如果孩子是View,则进行View的测量过程,这个过程下面再单独进行分析。 测量完成后调用setMeasuredDimension()方法。
LinearLayout measure 过程
LinearLayout 测量分为水平和垂直,跟我们设置的方向有关系。下面以measureVertical为例简单阅读一下源码。
//遍历孩子,测量孩子
for (int i = 0; i < count; ++i) {
//测量子View
measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
heightMeasureSpec, usedHeight);
//累加高度,加上孩子的上下Margin
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
lp.bottomMargin + getNextLocationOffset(child));
//测量子view完成后,根据子View测量自己的大小
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
heightSizeAndState);
}
measureChildBeforeLayout调用了measureChildWithMargins。measureChildWithMargins里面主要就是获取view的MeasureSpec,上面有提到过。然后又回到view的measure方法中。
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
ViewGroup的绘制大概流程就是这样子,下面了解一下View的measure过程。
View的measure过程
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
主要就是去设置View的宽高了,够直接的。但是需要了解一下getDefaultSize方法。
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
可以看出当返回的大小为模式为:MeasureSpec.AT_MOST,MeasureSpec.EXACTLY,返回的大小都是specSize。需要注意,如果你的控件是直接继承View,那么你应该重写onMeasure方法设置wrap_content的时候的大小,不设置的话就相当于match_parent。
performLayout
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) {
....
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
....
}
调用了View中的layout方法,此方法作用是确定自身和子View的位置。setFrame方法中确定了自己的位置,mLeft 可以通过getLeft()获取,是不是似曾相识呢。然后调用onLayout方法确定子View的位置。
protected boolean setFrame(int left, int top, int right, int bottom) {
...
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
...
}
onLayout跟onMeasure方法一样,不同的layout有不同的layout方式。这里继续以LinearLayout进行分析。还是选择垂直方向来体验这个过程。
void layoutVertical(int left, int top, int right, int bottom) {
....
for (int i = 0; i < count; i++) {
....
final View child = getVirtualChildAt(i);
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
int gravity = lp.gravity;
if (gravity < 0) {
gravity = minorGravity;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = paddingLeft + ((childSpace - childWidth) / 2)
+ lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
childLeft = childRight - childWidth - lp.rightMargin;
break;
case Gravity.LEFT:
default:
childLeft = paddingLeft + lp.leftMargin;
break;
}
...
childTop += lp.topMargin;
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child, i);
}
}
遍历获得所有的子View,根据不同的Gravity,计算出对应的childLeft,childTop。然后调用了setChildFrame(child, childLeft, childTop + getLocationOffset(child),childWidth, childHeight),setChildFrame方法调用的是child.layout(left, top, left + width, top + height),所以回到前面说的setFrame方法确定了子View的位置了。
performDraw()
这里简单过一下流程,performDraw()会调用draw(fullRedrawNeeded),接着drawSoftware()最终调用 mView.draw(canvas),所以接下来看一下View的Draw方法查看View的Draw方法,很明了,注释写了几个步骤, 1. Draw the background 2. If necessary, save the canvas' layers to prepare for fading 3. Draw view's content 4. Draw children
5. If necessary, draw the fading edges and restore layers 6. Draw decorations (scrollbars for instance)。主要的是绘制背景,绘制自己,绘制孩子,还有绘制装饰。
public void draw(Canvas canvas) {
...
// Step 1, draw the background, if needed
if (!dirtyOpaque) {
drawBackground(canvas);
}
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);
...
}
dispatchDraw方法是去遍历孩子,然后调用孩子的draw方法,这样就完成了绘制的传递了。
补充:如果继承ViewGroup如果需要重写onDraw来绘制内容时,你需要设置setWillNotDraw(false)关闭WILL_NOT_DRAW标志位。默认情况下,View默认不开启这个标志位,但是ViewGroup默认开启。
/**
* If this view doesn't do any drawing on its own, set this flag to
* allow further optimizations. By default, this flag is not set on
* View, but could be set on some View subclasses such as ViewGroup.
*
* Typically, if you override {@link #onDraw(android.graphics.Canvas)}
* you should clear this flag.
*
* @param willNotDraw whether or not this View draw on its own
*/
public void setWillNotDraw(boolean willNotDraw) {
setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}
到此,View从测量到显示的大概流程就讲完了,以此记录学习。