android进阶篇15、View的测量布局绘制三大流程源码解析

本文深入剖析了Android视图渲染的过程,从WindowManager的addView开始,详细讲解了如何通过ViewRootImpl、performTraversals、measure、layout到draw的整个流程。在measure阶段,解释了 DecorView、FrameLayout、ViewGroup的measure方法如何确定子view的大小。在layout阶段,介绍了如何进行布局。最后,提到了关键的onMeasure和onLayout方法在自定义View中的应用。文章适合Android开发者深入理解视图渲染机制。

if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;

wm.addView(decor, l); //2
}
}

2、WindowManagerImpl的addView如下所示,注释1处显示调用的mGlobal的addView,这个mGlobal是WindowManagerGlobal类型的,并且是通过单例模式创建的,WindowManagerGlobal是个单例类;

public void addView(@NonNull View view, 《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》开源 @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
mContext.getUserId()); //1
}

private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance(); //2

public static WindowManagerGlobal getInstance() {
synchronized (WindowManagerGlobal.class) {
if (sDefaultWindowManager == null) {
sDefaultWindowManager = new WindowManagerGlobal();
}
return sDefaultWindowManager;
}
}

3、WindowManagerGlobal的addView如下所示,注释1处创建ViewRootImpl对象,注释2处调用了ViewRootImpl的setView方法,这个view参数仍然是包含我们布局的DecorView;

public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow, int userId) {

final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
ViewRootImpl root;
synchronized (mLock) {

root = new ViewRootImpl(view.getContext(), display); //1
view.setLayoutParams(wparams);

mViews.add(view);
mRoots.add(root);
mParams.add(wparams);

// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView, userId); //2
}
}
}

4、在ViewRootImpl的setView方法中会调用requestLayout方法,而requestLayout方法又会调用scheduleTraversals方法,scheduleTraversals又会调用doTraversal,doTraversal又会调用performTraversals方法,这个方法应该会比较熟悉了,在这个方法中才真正开始执行view的测量布局绘制的流程;

5、performTraversals方法特别长,但其实总结起来主要就干了三件事,测量、布局和绘制,如下所示;

注释1处将mView赋值给host,这个mView在ViewRootImpl的setView方法中被赋值为docorview,所以这个host就是包含我们布局的decorView;

注释2处执行的performMeasure代表测量流程;

注释3处的performLayout代表布局流程;

注释4处的performDraw代表绘制流程;

private void performTraversals() {
// cache mView since it is used so much below…
final View host = mView; //1

int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); //2

performLayout(lp, mWidth, mHeight); //3

performDraw(); //4

}

二、测量流程measure

1、performMeasure

测量流程是从performMeasure方法开始的,如下所示,注释1表示mView为空则直接返回,这个mView我们在setView方法中赋过值,其实就是包含我们布局的DecorView;DecorView、DecorView的父类FrameLayout、FrameLayout的父类ViewGroup都没有measure方法,因此注释2处的measure方法其实是调用的View的measure方法;

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return; //1
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, “measure”);
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); //2
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}

2、View的measure方法

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(measureSp Android开源项目《ali1024.coding.net/public/P7/Android/git》 ec);

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的逻辑是一样的;

最后送福利了,现在关注我可以获取包含源码解析,自定义View,动画实现,架构分享等。
内容难度适中,篇幅精炼,每天只需花上十几分钟阅读即可。
大家可以跟我一起探讨,有flutter—底层开发—性能优化—移动架构—资深UI工程师 —NDK相关专业人员和视频教学资料,还有更多面试题等你来拿

录播视频图.png
重写layout函数,因此实际调用的是View的layout,这块跟measure的逻辑是一样的;

最后送福利了,现在关注我可以获取包含源码解析,自定义View,动画实现,架构分享等。
内容难度适中,篇幅精炼,每天只需花上十几分钟阅读即可。
大家可以跟我一起探讨,有flutter—底层开发—性能优化—移动架构—资深UI工程师 —NDK相关专业人员和视频教学资料,还有更多面试题等你来拿

[外链图片转存中…(img-msQb150W-1650014759685)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值