ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

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

2021-12-07 13:01:20  阅读:101  来源: 互联网

标签:draw 15 int 注释 进阶篇 源码 child view View


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) {

标签:draw,15,int,注释,进阶篇,源码,child,view,View
来源: https://blog.csdn.net/m0_64604178/article/details/121766522

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有