1.我们知道在ViewRootImpl中的performTraversals方法中,会执行view的测量、布局、绘制。
那么具体的执行流程是哪样的,是怎么调用到View中的onMeasure、onLayout、onDraw方法的。
private void performTraversals() {
//执行测量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
//执行布局.
performLayout(lp, mWidth, mHeight);
//执行绘制
performDraw();
}
2.在看View测量之前,先看MeasureSpec类中定义的View mode相关的知识。
view的mode有三种:
1.UNSPECIFIED:未指定,不明确。
ViewGroup没有对子View施加任何约束,子View可以是任意大小。这种mode使用的比较少。
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
2.EXACTLY:准确的
该View必须使用父ViewGroup指定尺寸,对应Match_Parent 或者具体的数值如:36dp
public static final int EXACTLY = 1 << MODE_SHIFT;
3.AT_MOST:最多
该View的大小最大是父ViewGroup给定的尺寸,对应warp_content。
public static final int AT_MOST = 2 << MODE_SHIFT;
再看makeMeasureSpec方法。通过这个方法计算得到的measureSpec。是包含Mode和size两部分内容。
public static int makeMeasureSpec(int size,int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
通过getMode可以获取View使用的mode。
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
通过getSize可以获取View的大小。
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
3. View的测量过程 performMeasure方法
在ViewRootImpl中childWidthMeasureSpec屏幕的宽,childHeightMeasureSpec屏幕的高。
getRootMeasureSpec这个方法,就是根据穿过来的size大小和view的模式,来计算得到的measureSpec值。
也能看出MATCH_PARENT对应 MeasureSpec.EXACTLY。
WRAP_CONTENT对应MeasureSpec.AT_MOST
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
mView 就是DecorView。DecoView是继承自FrameLayout-->ViewGruop-->View
mView.measure()调用的是View的measure方法
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
View.Java中
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
//判断标志位,是否需要重新布局
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
if (forceLayout || needsLayout) {
//如果forceLayout为true则cacheIndex =-1;如果为false,则从缓存中获取
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
//cacheIndex<0说明需要重新测量
if (cacheIndex < 0 || sIgnoreMeasureCache) {
onMeasure(widthMeasureSpec, heightMeasureSpec);
} else {
//从缓存中获取
long value = mMeasureCache.valueAt(cacheIndex);
//将从缓存中获取的数据设置进来
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
}
}
//将测量后的宽高,缓存起来
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//将计算后的宽高,赋值给全局变量
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
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;
}
在View中,调用oMeasure方法,先调用子类的方法,也就是DecorView的onMeasure方法
在DecorView中会回调父类FrameLayout方法
在这个方法中,会不断遍历子View,进行测量
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
}
}
}
measureChildWithMargins方法中,通过 child.measure(childWidthMeasureSpec, childHeightMeasureSpec); 测量子View,如果子View是一个ViewGroup,继续上面的循环遍历,测量每一个View的宽高。
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//计算得到childView的MeasureSpec
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);
//调用子View的Measure方法,继续遍历
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
通过以上循环遍历,每个View的大小已经被测量出来了。但是在屏幕中摆放在什么位置,还得调用layout相关的方法。
4.ViewGroup performLayout方法执行流程。
host 就是DecorView
host.getMeasuredWidth() host.getMeasuredHeight() 得到的是测量后的结果。
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,int desiredWindowHeight) {
//host.getMeasuredWidth() host.getMeasuredHeight() 得到的是测量后的结果
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
}
ViewGroup中的layout方法是final修饰的,在子类中无法被复写。在这调用父类View的layout方法
@Override
public final void layout(int l, int t, int r, int b) {
if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
if (mTransition != null) {
mTransition.layoutChange(this);
}
//调用父类View的layout方法
super.layout(l, t, r, b);
} else {
mLayoutCalledWhileSuppressed = true;
}
}
View#layout
public void layout(int l, int t, int r, int b) {
//设置界面大小left,top,right,bottom 的值
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
//如果大小发生了变化,或者需要重新layout,则进入到onLayout的逻辑
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
//调用onLayout
onLayout(changed, l, t, r, b);
//调用OnLayoutChangeListener.onLayoutChange
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);
}
}
}
}
设置界面大小,当大小改变时,会调用onSizeChanged方法
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;
int oldWidth = mRight - mLeft;
int oldHeight = mBottom - mTop;
int newWidth = right - left;
int newHeight = bottom - top;
boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
if (sizeChanged) {
sizeChange(newWidth, newHeight, oldWidth, oldHeight);
}
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
}
private void sizeChange(int newWidth, int newHeight, int oldWidth, int oldHeight) {
onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);
}
在View中调用onLayout方法,其实是调用的子类的onLayout方法,看FrameLayout中。
通过layoutChildren循环遍历,调用每一个ViewGroup的onLayout方法,来确定子View在布局中的位置。
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false);
}
//通过调用layoutChildren来确定View在屏幕中的位置,
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
final int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
int childLeft;
int childTop;
int gravity = lp.gravity;
if (gravity == -1) {
gravity = DEFAULT_CHILD_GRAVITY;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
//计算得到距离左侧的距离
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
if (!forceLeftGravity) {
childLeft = parentRight - width - lp.rightMargin;
break;
}
case Gravity.LEFT:
default:
childLeft = parentLeft + lp.leftMargin;
}
//计算得到距离顶部的距离
switch (verticalGravity) {
case Gravity.TOP:
childTop = parentTop + lp.topMargin;
break;
case Gravity.CENTER_VERTICAL:
childTop = parentTop + (parentBottom - parentTop - height) / 2 +
lp.topMargin - lp.bottomMargin;
break;
case Gravity.BOTTOM:
childTop = parentBottom - height - lp.bottomMargin;
break;
default:
childTop = parentTop + lp.topMargin;
}
//调用child的layout方法,继续上面的循环遍历
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
通过以上方法,确定了View在屏幕中的位置,接下来执行绘制流程onDraw方法。
ViewRootImpl中
private void performDraw() {
.....
//fullRedrawNeeded 这个用来判断是否需要绘制全部视图
boolean canUseAsync = draw(fullRedrawNeeded);
}
private boolean draw(boolean fullRedrawNeeded) {
Surface surface = mSurface;
//mDirty表示需要绘制的区域
final Rect dirty = mDirty;
//如果fullRedrawNeeded为true,把dirty大小设置为整个屏幕
if (fullRedrawNeeded) {
dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
}
.....
//调用drawSoftware
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
scalingRequired, dirty, surfaceInsets)) {
return false;
}
}
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
// Draw with software renderer.
final Canvas canvas;
//锁定canvas,画布大小由dirty决定
canvas = mSurface.lockCanvas(dirty);
//设置画布支持的密度
//mDensity = context.getResources().getDisplayMetrics().densityDpi;
canvas.setDensity(mDensity);
//调用DecorView
mView.draw(canvas);
//提交已经绘制好的内容
surface.unlockCanvasAndPost(canvas);
}
mView.draw(canvas) 是真正的绘制,绘制到画布上。mView就是DecorView
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
if (mMenuBackground != null) {
mMenuBackground.draw(canvas);
}
}
View#draw
public void draw(Canvas canvas) {
/*
* 绘制的6个步骤
* 1. Draw the background. 绘制View的背景
* 2. If necessary, save the canvas' layers to prepare for fading
如果有必要,会保存canvas的图层信息,可跳过
* 3. Draw view's content. 绘制View的内容
* 4. Draw children.绘制子View
* 5. If necessary, draw the fading edges and restore layers
* 如果有必要,绘制边缘并保存图层,可跳过
* 6. Draw decorations (scrollbars for instance) 绘制View的装饰(例如:滚动条)
*/
// Step 1, draw the background, if needed
int saveCount;
//1.如果需要,则绘制背景
if (!dirtyOpaque) {
drawBackground(canvas);
}
// skip step 2 & 5 if possible (common case)
//如果可以跳过第2和第5步
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
//第三步,绘制内容
if (!dirtyOpaque){
onDraw(canvas);
}
// Step 4, draw the children
//第四步,绘制子View
dispatchDraw(canvas);
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);
if (debugDraw()) {
debugDrawFocus(canvas);
}
// we're done...
//绘制完成,return。这样就跳过了第2和第5步的绘制。
return;
}
}
1) 绘制背景:drawBackground(canvas)
绘制背景调用的是Drawable子类的draw(canvas)。这样就能够把背景绘制在画布上。
private void drawBackground(Canvas canvas) {
final Drawable background = mBackground;
//如果没有background,则不进行绘制
if (background == null) {
return;
}
//如果有偏移量,先偏移画布,然后再draw
final int scrollX = mScrollX;
final int scrollY = mScrollY;
if ((scrollX | scrollY) == 0) {
background.draw(canvas);
} else {
canvas.translate(scrollX, scrollY);
background.draw(canvas);
//画完之后,将画布移回去
canvas.translate(-scrollX, -scrollY);
}
}
2)绘制内容:onDraw(canvas);
先调用DecorView#onDraw
@Override
public void onDraw(Canvas c) {
super.onDraw(c);
mBackgroundFallback.draw(this, mContentRoot, c, mWindow.mContentParent,
mStatusColorViewState.view, mNavigationColorViewState.view);
}
super.onDraw(c)这个调用的是View的onDraw.在View中这个是空实现。
我们自定义View的时候,复写的就是这个方法。
protected void onDraw(Canvas canvas) {
}
3)绘制子View:dispatchDraw(canvas);
DecorView#dispatchDraw
//遍历子View 进行绘制
protected void dispatchDraw(Canvas canvas) {
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < childrenCount; i++) {
more |= drawChild(canvas, transientChild, drawingTime);
}
}
//drawChild绘制子View child.draw调用View的draw,继续上面的绘制流程
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
4)绘制装饰:onDrawForeground
绘制装饰就是指View除了背景、内容、子View的其他部分,例如滚动条等。
public void onDrawForeground(Canvas canvas) {
onDrawScrollIndicators(canvas);
onDrawScrollBars(canvas);
}
至此View的测量、布局、绘制流程已经梳理完成。绘制已经把View绘制在了画布canvas上。
那么画在是如何显示在屏幕上的呢?
看看 canvas = mSurface.lockCanvas(dirty);
这是锁定了一个Canvas,并把Java层的mCanvas,传递到了Native层。
public Canvas lockCanvas(Rect inOutDirty){
synchronized (mLock) {
mLockedObject = nativeLockCanvas(mNativeObject, mCanvas, inOutDirty);
return mCanvas;
}
}
而调用canvas.draw方法时,其实是调用的native方法。在native层会通过SkBitmap数据,这个是通过Skia绘制引擎实现的。
private static native void nDrawColor(...)
private static native void nDrawBitmap(...)
在绘制完成后,java层会调用 surface.unlockCanvasAndPost(canvas);
这个方法会通知Native层代码,将绘制好的Bitmap发送到一个队列里,然后显示到屏幕上。