Android开发

android进阶篇15、View的测量布局绘制三大流程源码解析,2021春招面试

本文主要是介绍android进阶篇15、View的测量布局绘制三大流程源码解析,2021春招面试,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

measure方法其实就干了一件事情,调用了onMeasure,DecorView重写了onMeasure方法,因此是调用的DecorView的onMeasure方法;在DecorView的onMeasure方法中又调用了super.onMeasure(widthMeasureSpec, heightMeasureSpec),也就是FrameLayout的onMeasure方法;

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
、、、
onMeasure(widthMeasureSpec, heightMeasureSpec);
、、、
}

3、FrameLayout的onMeasure方法

注释1处先求出子view个数;

在注释2处表示循环遍历每一个子view然后执行measureChildWithMargins;

测量完所有子view之后在注释3处设置自身的尺寸;

注释4和注释5表示会对所有设置MatchParent属性的子view重新measure,因为MatchParent属性比较特殊,刚开始并不知道父view的尺寸,所以需要重新测量;

我们继续看一下注释2处是怎么测量子view的,FrameLayout并没有measureChildWithMargins方法,而是在父类ViewGroup中定义的,我们接着看一下ViewGroup的measureChildWithMargins方法;

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount(); //1

for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
measureChildWithMargins(child, widthMeasureSpec, 0,heightMeasureSpec,0); //2
}
}

setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT)); //3

count = mMatchParentChildren.size(); //4
if (count > 1) {
for (int i = 0; i < count; i++) {
final View child = mMatchParentChildren.get(i);
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
、、、
child.measure(childWidthMeasureSpec, childHeightMeasureSpec); //5
}
}
}

4、ViewGroup的measureChildWithMargins方法

注释1处先求出子view的LayoutParams布局参数;

然后在注释2和注释3处通过父view的MeasureSpec和子view的布局参数确定子view的MeasureSpec;

最后在注释4处又调用了child.measure(childWidthMeasureSpec, childHeightMeasureSpec);这样又会递归调用到View的measure方法,在measure方法中又会调用onMeasure方法,如果这个子view是一个viewgroup类型的,则又会递归调用到该viewgroup的onMeasure方法;直到递归调用到最下层view时,则会调用到View的onMeasure方法;

protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); //1

final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin

  • widthUsed, lp.width); //2
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
    mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
  • heightUsed, lp.height); //3

child.measure(childWidthMeasureSpec, childHeightMeasureSpec); //4
}

5、View的onMeasure方法

注释1处的setMeasuredDimension方法设置自身的尺寸,通过getDefaultSize获取尺寸;

注释3和注释4将mMeasuredWidth和mMeasuredHeight赋值为我们测量后的值;因此measure流程结束我们就可以通过getMeasuredWidth和hgetMeasuredeight获取尺寸了,比如我们在onLayout方法中就会可以调用;

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); //1
}

protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
、、、
setMeasuredDimensionRaw(measuredWidth, measuredHeight);//2
}

private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth; //3
mMeasuredHeight = measuredHeight; //4

mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}

6、View的getDefaultSize方法

注释1处获得specMode;

从注释2处我们可知,如果我们自定义view直接继承自View,那么我们不管设置wrap_content还是match_parent效果都是一样的;

public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec); //1
int specSize = MeasureSpec.getSize(measureSpec);

switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY: //2
result = specSize;
break;
}
return result;
}

三、布局流程layout

1、performLayout方法

布局流程的入口函数就是performLayout,如下所示,注释1处将decorView赋值给host,然后在注释2处执行host的layout,也就是DecorView的layout,DecorView、DecorView的父类FrameLayout、FrameLayout的父类ViewGroup都没有重写layout函数,因此实际调用的是View的layout,这块跟measure的逻辑是一样的;

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
mInLayout = true;
final View host = mView; //1
if (host == null) {
return;
}

host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); //2
mInLayout = false;
}

2、View的layout方法

在View的layout方法中其实主要就是调用了onLayout,DecorView重写了onLayout,而在DecorView的onLayout中又调用了super.onLayout(changed, left, top, right, bottom);也就是FrameLayout的onLayout方法;

注释1处的setFrame方法主要是给当前view设置尺寸,具体实现是在注释2、3、4、5处分别给左上右下赋值;所以执行完layout过程后就可以通过getWidth和getHeight获得view的宽高了,我们一般在onDraw方法中调用这两个方法获取view的宽高;

public void layout(int l, int t, int r, int b) {
、、、
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); //1
onLayout(changed, l, t, r, b);
、、、
}

protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;

// Invalidate our old position
invalidate(sizeChanged);
、、、
mLeft = left; //2
mTop = top; /

《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》

【docs.qq.com/doc/DSkNLaERkbnFoS0ZF】 完整内容开源分享

/3
mRight = right; //4
mBottom = bottom; //5
、、、
return changed;
}

3、FrameLayout的onLayout方法

注释1处onLayout又调用了layoutChildren;

注释2处计算出所有子view的数量;注释3处循环遍历每一个子view,并执行子view的layout方法;因此又会执行到View的layout方法,进而又会执行到onLayout方法;如果子view又是一个viewgroup,那么还会循环执行上述操作;如果子view是一个具体的view,就是调用到view的onLayout,其实view的onLayout是一个空实现;尺寸的赋值在第2步已经介绍了;

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */); //1
}

void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
final int count = getChildCount(); //2

for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();

final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();

int childLeft;
int childTop;
、、、

child.layout(childLeft, childTop, childLeft + width, childTop + height); //3
}
}
}

四、绘制流程draw

1、performDraw

方法的调用链如下边注释1、2、3所示,最终会调用到View的draw方法;

private void performDraw() {
、、、
boolean canUseAsync = draw(fullRedrawNeeded); //1
、、、
}

private boolean draw(boolean fullRedrawNeeded) {
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
scalingRequired, dirty, surfaceInsets)) { //2
return false;
}
}

private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
、、、
mView.draw(canvas); //3
、、、
}

2、View的draw方法

在view的draw方法中官方也给出了注释,注释中表示draw流程分为7步,在注释中也阐述的非常清楚了,我们不再重复,每一步都可以点进去对应的方法查看,我们主要看一下第三四步;

第3步是通过onDraw方法绘制自身,我们点进去发现是一个空实现,所以需要在具体的view中自己去实现;

第4步通过dispatchDraw(canvas)方法去绘制所有的子view,在View中是一个空实现,在viewgroup中给出了具体的实现;

public void draw(Canvas canvas) {

/*

  • Draw traversal performs several drawing steps which must be executed
  • in the appropriate order:
  •  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)
    
  •  7. If necessary, draw the default focus highlight
    

*/

// Step 1, draw the background, if needed
drawBackground(canvas);

// skip step 2 & 5 if possible (common case)
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
onDraw(canvas);

// Step 4, draw the children
dispatchDraw(canvas);

drawAutofilledHighlight(canvas);

// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}

// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);

// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);

if (isShowingLayoutBounds()) {
debugDrawFocus(canvas);
}

// we’re done…
return;
}
}

3、ViewGroup中的dispatchDraw方法

注释1表示在dispatchDraw方法中调用drawChild来绘制子view,注释2表示又会调用每个child的draw方法,这样层层递归最终绘制出整个view;

protected void dispatchDraw(Canvas canvas) {
final int childrenCount = mChildrenCount;
、、、
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); //1
}
transientIndex++;
if (transientIndex >= transientCount) {
transientIndex = -1;
}
}
}
、、、
}

protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime); //2
}

五、注意点:

requestLayout和invalidate以及postInvalidate的区别

1、requestLayout

在view中调用requestLayout后,会递归调用parent的requestLayout,最终会调用到ViewRootImpl的requestLayout方法,如注释1所示,最终调用到performTraversals执行三大流程,因为requestLayout将ForceLayout标志位置为true,因此onMeasure和onLayout肯定会执行,onDraw会根据视图的左上右下参数以及是否是脏数据来决定是否重新执行;

public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {

这篇关于android进阶篇15、View的测量布局绘制三大流程源码解析,2021春招面试的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!