写点什么

Android View 的绘制流程,35 岁技术人如何转型做管理

用户头像
Android架构
关注
发布于: 40 分钟前

return MeasureSpec.makeMeasureSpec(resultSize, resultMode);


}


上面的代码有点多,希望你仔细看一些注释,代码写得很多,其实计算原理很简单:


1、如果我们在 xml 的 layout_width 或者 layout_height 把值都写死,那么上述的测量完全就不需要了,之所以要上面的这步测量,是因为 match_parent 就是充满父容器,wrap_content 就是自己多大就多大, 我们写代码的时候特别爽,我们编码方便的时候,google 就要帮我们计算你 match_parent 的时候是多大,wrap_content 的是多大,这个计算过程,就是计算出来的父 View 的 MeasureSpec 不断往子 View 传递,结合子 View 的 LayoutParams 一起再算出子 View 的 MeasureSpec,然后继续传给子 View,不断计算每个 View 的 MeasureSpec,子 View 有了 MeasureSpec 才能更测量自己和自己的子 View。


2、上述代码如果这么来理解就简单了


  • 如果父 View 的 MeasureSpec 是 EXACTLY,说明父 View 的大小是确切的,(确切的意思很好理解,如果一个 View 的 MeasureSpec 是 EXACTLY,那么它的 size 是多大,最后展示到屏幕就一定是那么大)。


1、如果子 View 的 layout_xxxx 是 MATCH_PARENT,父 View 的大小是确切,子 View 的大小又 MATCH_PARENT(充满整个父 View),那么子 View 的大小肯定是确切的,而且大小值就是父 View 的 size。所以子 View 的 size=父 View 的 size,mode=EXACTLY


2、如果子 View 的 layout_xxxx 是 WRAP_CONTENT,也就是子 View 的大小是根据自己的 content 来决定的,但是子 View 的毕竟是子 View,大小不能超过父 View 的大小,但是子 View 的是 WRAP_CONTENT,我们还不知道具体子 View 的大小是多少,要等到 child.measure(childWidthMeasureSpec, childHeightMeasureSpec) 调用的时候才去真正测量子 View 自己 content 的大小(比如 TextView wrap_content 的时候你要测量 TextView content 的大小,也就是字符占用的大小,这个测量就是在 child.measure(childWidthMeasureSpec, childHeightMeasureSpec)的时候,才能测出字符的大小,MeasureSpec 的意思就是假设你字符 100px,但是 MeasureSpec 要求最大的只能 50px,这时候就要截掉了)。通过上述描述,子 View MeasureSpec mode 的应该是 AT_MOST,而 size 暂定父 View 的 size,表示的意思就是子 View 的大小没有不确切的值,子 View 的大小最大为父 View 的大小,不能超过父 View 的大小(这就是 AT_MOST 的意思),然后这个 MeasureSpec 做为子 View measure 方法 的参数,做为子 View 的大小的约束或者说是要求,有了这个 MeasureSpec 子 View 再实现自己的测量。


3、如果如果子 View 的 layout_xxxx 是确定的值(200dp),那么就更简单了,不管你父 View 的 mode 和 size 是什么,我都写死了就是 200dp,那么控件最后展示就是就是 200dp,不管我的父 View 有多大,也不管我自己的 content 有多大,反正我就是这么大,所以这种情况 MeasureSpec 的 mode = EXACTLY 大小 size=你在 layout_xxxx 填的那个值。


  • 如果父 View 的 MeasureSpec 是 AT_MOST,说明父 View 的大小是不确定,最大的大小是 MeasureSpec 的 size 值,不能超过这个值。


1、如果子 View 的 layout_xxxx 是 MATCH_PARENT,父 View 的大小是不确定(只知道最大只能多大),子 View 的大小 MATCH_PARENT(充满整个父 View),那么子 View 你即使充满父容器,你的大小也是不确定的,父 View 自己都确定不了自己的大小,你 MATCH_PARENT 你的大小肯定也不能确定的,所以子 View 的 mode=AT_MOST,size=父 View 的 size,也就是你在布局虽然写的是 MATCH_PARENT,但是由于你的父容器自己的大小不确定,导致子 View 的大小也不确定,只知道最大就是父 View 的大小。


2、如果子 View 的 layout_xxxx 是 WRAP_CONTENT,父 View 的大小是不确定(只知道最大只能多大),子 View 又是 WRAP_CONTENT,那么在子 View 的 Content 没算出大小之前,子 View 的大小最大就是父 View 的大小,所以子 View MeasureSpec mode 的就是 AT_MOST,而 size 暂定父 View 的 size。


3、如果如果子 View 的 layout_xxxx 是确定的值(200dp),同上,写多少就是多少,改变不了的。


  • 如果父 View 的 MeasureSpec 是 UNSPECIFIED(未指定),表示没有任何束缚和约束,不像 AT_MOST 表示最大只能多大,不也像 EXACTLY 表示父 View 确定的大小,子 View 可以得到任意想要的大小,不受约束


1、如果子 View 的 layout_xxxx 是 MATCH_PARENT,因为父 View 的 MeasureSpec 是 UNSPECIFIED,父 View 自己的大小并没有任何约束和要求,


那么对于子 View 来说无论是 WRAP_CONTENT 还是 MATCH_PARENT,子 View 也是没有任何束缚的,想多大就多大,没有不能超过多少的要求,一旦没有任何要求和约束,size 的值就没有任何意义了,所以一般都直接设置成 0


2、同上...


3、如果如果子 View 的 layout_xxxx 是确定的值(200dp),同上,写多少就是多少,改变不了的(记住,只有设置的确切的值,那么无论怎么测量,大小都是不变的,都是你写的那个值)


到此为止,你是否对 MeasureSpec 和三种模式、还有 WRAP_CONTENT 和 MATCH_PARENT 有一定的了解了,如果还有任何问题,欢迎在我简书(用户名:Kelin)评论里留言。


2、View 的测量过程主要是在 onMeasure()方法


打开 View 的源码,找到 measure 方法,这个方法代码不少,但是测量工作都是在 onMeasure()做的,measure 方法是 final 的所以这个方法也不可重写,如果想自定义 View 的测量,你应该去重写 onMeasure()方法


public final void measure(int widthMeasureSpec, int heightMeasureSpec) {


......


onMeasure(widthMeasureSpec,heightMeasureSpec);


.....


}


3、View 的 onMeasure 的默认实现


打开 View.java 的源码来看下 onMeasure 的实现


protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {


setMeasuredDimension(


getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),


getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));


}


View 的 onMeasure 方法默认实现很简单,就是调用 setMeasuredDimension(),setMeasuredDimension()可以简单理解就是给 mMeasuredWidth 和 mMeasuredHeight 设值,如果这两个值一旦设置了,那么意味着对于这个 View 的测量结束了,这个 View 的宽高已经有测量的结果出来了。如果我们想设定某个 View 的高宽,完全可以直接通过 setMeasuredDimension(100,200)来设置死它的高宽(不建议),但是 setMeasuredDimension 方法必须在 onMeasure 方法中调用,不然会抛异常。我们来看下对于 View 来说它的默认高宽是怎么获取的。


//获取的是 android:minHeight 属性的值或者 View 背景图片的大小值


protected int getSuggestedMinimumWidth() {


return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());


}


//@param size 参数一般表示设置了 android:minHeight 属性或者该 View 背景图片的大小值


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: //表示该 View 的大小父视图未定,设置为默认值


result = size;


break;


case MeasureSpec.AT_MOST:


case MeasureSpec.EXACTLY:


result = specSize;


break;


}


return result;


}


getDefaultSize 的第一个参数 size 等于 getSuggestedMinimumXXXX 返回的的值(建议的最小宽度和高度),而建议的最小宽度和高度都是由 View 的 Background 尺寸与通过设置 View 的 minXXX 属性共同决定的,这个 size 可以理解为 View 的默认长度,而第二个参数 measureSpec,是父 View 传给自己的 MeasureSpec,这个 measureSpec 是通过测量计算出来的,具体的计算测量过程前面在讲解 MeasureSpec 已经讲得比较清楚了(是有父 View 的 MeasureSpec 和子 View 自己的 LayoutParams 共同决定的)只要这个测试的 mode 不是 UNSPECIFIED(未确定的),那么默认的就会用这个测量的数值当做 View 的高度。


对于 View 默认是测量很简单,大部分情况就是拿计算出来的 MeasureSpec 的 size 当做最终测量的大小。而对于其他的一些 View 的派生类,如 TextView、Button、ImageView 等,它们的 onMeasure 方法系统了都做了重写,不会这么简单直接拿 MeasureSpec 的 size 来当大小,而去会先去测量字符或者图片的高度等,然后拿到 View 本身 content 这个高度(字符高度等),如果 MeasureSpec 是 AT_MOST,而且 View 本身 content 的高度不超出 MeasureSpec 的 size,那么可以直接用 View 本身 content 的高度(字符高度等),而不是像 View.java 直接用 MeasureSpec 的 size 做为 View 的大小。


4、ViewGroup 的 Measure 过程


ViewGroup 类并没有实现 onMeasure,我们知道测量过程其实都是在 onMeasure 方法里面做的,我们来看下 FrameLayout 的 onMeasure 方法,具体分析看注释哦。


//FrameLayout 的测量


protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {


....


int maxHeight = 0;


int maxWidth = 0;


int childState = 0;


for (int i = 0; i < count; i++) {


final View child = getChildAt(i);


if (mMeasureAllChildren || child.getVisibility() != GONE) {


// 遍历自己的子 View,只要不是 GONE 的都会参与测量,measureChildWithMargins 方法在最上面


// 的源码已经讲过了,如果忘了回头去看看,基本思想就是父 View 把自己的 MeasureSpec


// 传给子 View 结合子 View 自己的 LayoutParams 算出子 View 的 MeasureSpec,然后继续往下传,


// 传递叶子节点,叶子节点没有子 View,根据传下来的这个 MeasureSpec 测量自己就好了。


measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);


final LayoutParams lp = (LayoutParams) child.getLayoutParams();


maxWidth = Math.max(maxWidth, child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);


maxHeight = Math.max(maxHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);


....


....


}


}


.....


.....


//所有的孩子测量之后,经过一系类的计算之后通过 setMeasuredDimension 设置自己的宽高,


//对于 FrameLayout 可能用最大的字 View 的大小,对于 LinearLayout,可能是高度的累加,


//具体测量的原理去看看源码。总的来说,父 View 是等所有的子 View 测量结束之后,再来测量自己。


setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),


resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT));


....


}


到目前为止,基本把 Measure 主要原理都过了一遍,接下来我们会结合实例来讲解整个 match 的过程,首先看下面的代码:


<?xml version="1.0" encoding="utf-8"?>


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"


android:id="@+id/linear"


android:layout_width="match_parent"


android:layout_height="wrap_content"


android:layout_marginTop="50dp"


android:background="@android:color/holo_blue_dark"


android:paddingBottom="70dp"


android:orientation="vertical">


<TextView


android:id="@+id/text"


android:layout_width="match_parent"


android:layout_height="wrap_content"


android:background="@color/material_blue_grey_800"


android:text="TextView"


android:textColor="@android:color/white"


android:textSize="20sp" />


<View


android:id="@+id/view"


android:layout_width="match_parent"


android:layout_height="150dp"


android:background="@android:color/holo_green_dark" />


</LinearLayout>


上面的代码对于出来的布局是下面的一张图



对于上面图可能有些不懂,这边做下说明:


整个图是一个 DecorView,DecorView 可以理解成整个页面的根 View,DecorView 是一个 FrameLayout,包含两个子 View,一个 id=statusBarBackground 的 View 和一个是 LineaLayout,id=statusBarBackground 的 View,我们可以先不管(我也不是特别懂这个 View,应该就是 statusBar 的设置背景的一个控件,方便设置 statusBar 的背景),而这个 LinearLayout 比较重要,它包含一个 title 和一个 content,title 很好理解其实就是 TitleBar 或者 ActionBar,content 就更简单了,setContentView()方法你应该用过吧,android.R.id.content 你应该听过吧,没错就是它,content 是一个 FrameLayout,你写的页面布局通过 setContentView 加进来就成了 content 的直接子 View。


整个 View 的布局图如下:



这张图在下面分析 measure,会经常用到,主要用于了解递归的时候 view 的 measure 顺序


注:


1、 header 的是个 ViewStub,用来惰性加载 ActionBar,为了便于分析整个测量过程,我把 Theme 设成 NoActionBar,避免 ActionBar 相关的 measure 干扰整个过程,这样可以忽略掉 ActionBar 的测量,在调试代码更清晰。


2、包含 Header(ActionBar)和 id/content 的那个父 View,我不知道叫什么名字好,我们姑且叫它 ViewRoot(看上图),它是垂直的 LinearLayout,放着整个页面除 statusBar 的之外所有的东西,叫它 ViewRoot 应该还 ok,一个代号而已。


既然我们知道整个 View 的 Root 是 DecorView,那么 View 的绘制是从哪里开始的呢,我们知道每个 Activity 均会创建一个 PhoneWindow 对象,是 Activity 和整个 View 系统交互的接口,每个 Window 都对应着一个 View 和一个 ViewRootImpl,Window 和 View 通过 ViewRootImpl 来建立联系,对于 Activity 来说,ViewRootImpl 是连接 WindowManager 和 DecorView 的纽带,绘制的入口是由 ViewRootImpl 的 performTraversals 方法来发起 Measure,Layout,Draw 等流程的。


我们来看下 ViewRootImpl 的 performTraversals 方法:


private void performTraversals() {


......


int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);


int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);


......


mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);


......


mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());


......


mView.draw(canvas);


......


}


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;


......


}


return measureSpec;


}


performTraversals 中我们看到的 mView 其实就是 DecorView,View 的绘制从 DecorView 开始, 在 mView.measure()的时候调用 getRootMeasureSpec 获得两个 MeasureSpec 做为参数,getRootMeasureSpec 的两个参数(mWidth, lp.width)mWith 和 mHeight 是屏幕的宽度和高度, lp 是 WindowManager.LayoutParams,它的 lp.width 和 lp.height 的默认值是 MATCH_PARENT,所以通过 getRootMeasureSpec 生成的测量规格 MeasureSpec 的 mode 是 MATCH_PARENT ,size 是屏幕的高宽。


因为 DecorView 是一个 FrameLayout 那么接下来会进入 FrameLayout 的 measure 方法,measure 的两个参数就是刚才 getRootMeasureSpec 的生成的两个 MeasureSpec,DecorView 的测量开始了。


首先是 DecorView 的 MeasureSpec ,根据上面的分析 DecorView 的 MeasureSpec 是 Windows 传过来的,我们画出 DecorView 的 MeasureSpec 图:



注:


1、-1 代表的是 EXACTLY,-2 是 AT_MOST


2、由于屏幕的像素是 1440x2560,所以 DecorView 的 MeasureSpec 的 size 对应于这两个值


那么接下来在 FrameLayout 的 onMeasure()方法 DecorView 开始 for 循环测量自己的子 View,测量完所有的子 View 再来测量自己,由下图可知,接下来要测量 ViewRoot 的大小



//FrameLayout 的测量


protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {


....


int maxHeight = 0;


int maxWidth = 0;


int childState = 0;


for (int i = 0; i < count; i++) {


final View child = getChildAt(i);


if (mMeasureAllChildren || child.getVisibility() != GONE) {


// 遍历自己的子 View,只要不是 GONE 的都会参与测量,measureChildWithMargins 方法在最上面


// 的源码已经讲过了,如果忘了回头去看看,基本思想就是父 View 把自己当 MeasureSpec


// 传给子 View 结合子 View 自己的 LayoutParams 算出子 View 的 MeasureSpec,然后继续往下穿,


// 传递叶子节点,叶子节点没有子 View,只要负责测量自己就好了。


measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);


....


....


}


}


....


}


DecorView 测量 ViewRoot 的时候把自己的 widthMeasureSpec 和 heightMeasureSpec 传进去了,接下来你就要去看 measureChildWithMargins 的源码了


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


}


ViewRoot 是系统的 View,它的 LayoutParams 默认都是 match_parent,根据我们文章最开始 MeasureSpec 的计算规则,ViewRoot 的 MeasureSpec mode 应该等于 EXACTLY(DecorView MeasureSpec 的 mode 是 EXACTLY,ViewRoot 的 layoutparams 是 match_parent),size 也等于 DecorView 的 size,所以 ViewRoot 的 MeasureSpec 图如下:



算出 ViewRoot 的 MeasureSpec 之后,开始调用 ViewRoot.measure 方法去测量 ViewRoot 的大小,然而 ViewRoot 是一个 LinearLayout ,ViewRoot.measure 最终会执行的 LinearLayout 的 onMeasure 方法,LinearLayout 的 onMeasure 方法又开始逐个测量它的子 View,上面的 measureChildWithMargins 方法又会被调用,那么根据 View 的层级图,接下来测量的是 header(ViewStub),由于 header 的 Gone,所以直接跳过不做测量工作,所以接下来轮到 ViewRoot 的第二个 child content(android.R.id.content),我们要算出这个 content 的 MeasureSpec,所以又要拿 ViewRoot 的 MeasureSpec 和 android.R.id.content 的 LayoutParams 做计算了,计算过程就是调用 getChildMeasureSpec 的方法,



protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {


.....


final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,


mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + heightUsed, lp.height);


....


}


public static int getChildMeasureSpec(int spec, int padding, int childDimension) {


int specMode = MeasureSpec.getMode(spec); //获得父 View 的 mode


int specSize = MeasureSpec.getSize(spec); //获得父 View 的大小


int size = Math.max(0, specSize - padding); //父 View 的大小-自己的 Padding+子 View 的 Margin,得到值才是子 View 可能的最大值。


.....


}


由上面的代码


int size = Math.max(0, specSize - padding);


而 **padding=mPaddingTop + mPaddin


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


gBottom + lp.topMargin + lp.bottomMargin + heightUsed**


算出 android.R.id.content 的 MeasureSpec 的 size


由于 ViewRoot 的 mPaddingBottom=100px(这个可能和状态栏的高度有关,我们测量的最后会发现 id/statusBarBackground 的 View 的高度刚好等于 100px,ViewRoot 是系统的 View 的它的 Padding 我们没法改变,所以计算出来 Content(android.R.id.content) 的 MeasureSpec 的高度少了 100px ,它的宽高的 mode 根据算出来也是 EXACTLY(ViewRoot 是 EXACTLY 和 android.R.id.content 是 match_parent)。所以 Content(android.R.id.content)的 MeasureSpec 如下(高度少了 100px):



Content(android.R.id.content) 是 FrameLayout,递归调用开始准备计算 id/linear 的 MeasureSpec,我们先给出结果:



图中有两个要注意的地方:


1、id/linear 的 heightMeasureSpec 的 mode=AT_MOST,因为 id/linear 的 LayoutParams 的 layout_height="wrap_content"


2、id/linear 的 heightMeasureSpec 的 size 少了 200px, 由上面的代码


padding=mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + heightUsed;


int size = Math.max(0, specSize - padding);


由于 id/linear 的 android:layout_marginTop="50dp" 使得 lp.topMargin=200px (本设备的 density=4,px=4*pd),在计算后 id/linear 的 heightMeasureSpec 的 size 少了 200px。(布局代码前面已给出,可自行查看 id/linear 控件 xml 中设置的属性)


linear.measure 接着往下算 linear 的子 View 的的 MeasureSpec,看下 View 层级图,往下走应该是 id/text,接下来是计算 id/text 的 MeasureSpec,直接看图,mode=AT_MOST ,size 少了 280,别问我为什么 ...specSize - padding.



算出 id/text 的 MeasureSpec 后,接下来 text.measure(childWidthMeasureSpec, childHeightMeasureSpec);准备测量 id/text 的高宽,这时候已经到底了,id/text 是 TextView,已经没有子类了,这时候跳到 TextView 的 onMeasure 方法了。TextView 拿着刚才计算出来的 heightMeasureSpec(mode=AT_MOST,size=1980),这个就是对 TextView 的高度和宽度的约束,进到 TextView 的 onMeasure(widthMeasureSpec,heightMeasureSpec) 方法,在 onMeasure 方法执行调试过程中,我们发现下面的代码:



TextView 字符的高度(也就是 TextView 的 content 高度[wrap_content])测出来=107px,107px 并没有超过 1980px(允许的最大高度),所以实际测量出来 TextView 的高度是 107px。


最终算出 id/text 的 mMeasureWidth=1440px,mMeasureHeight=107px。


贴一下布局代码,免得你忘了具体布局。


<?xml version="1.0" encoding="utf-8"?>


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"


android:id="@+id/linear"


android:layout_width="match_parent"


android:layout_height="wrap_content"


android:layout_marginTop="50dp"


android:background="@android:color/holo_blue_dark"


android:paddingBottom="70dp"


android:orientation="vertical">


<TextView


android:id="@+id/text"


android:layout_width="match_parent"


android:layout_height="wrap_content"


android:background="@color/material_blue_grey_800"


android:text="TextView"


android:textColor="@android:color/white"


android:textSize="20sp" />


<View


android:id="@+id/view"


android:layout_width="match_parent"


android:layout_height="150dp"


android:background="@android:color/holo_green_dark" />


</LinearLayout>


TextView 的高度已经测量出来了,接下来测量 id/linear 的第二个 child(id/view),同样的原理测出 id/view 的 MeasureSpec.



id/view 的 MeasureSpec 计算出来后,调用 view.measure(childWidthMeasureSpec, childHeightMeasureSpec)的测量 id/view 的高宽,之前已经说过 View measure 的默认实现是


protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {


setMeasuredDimension(


getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),


getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));


}


最终算出 id/view 的 mMeasureWidth=1440px,mMeasureHeight=600px。


id/linear 的子 View 的高度都计算完毕了,接下来 id/linear 就通过所有子 View 的测量结果计算自己的高宽,id/linear 是 LinearLayout,所有它的高度计算简单理解就是子 View 的高度的累积+自己的 Padding.



最终算出 id/linear 的 mMeasureWidth=1440px,mMeasureHeight=987px。


最终算出 id/linear 出来后,id/content 就要根据它唯一的子 View id/linear 的测量结果和自己的之前算出的 MeasureSpec 一起来测量自己的结果,具体计算的逻辑去看 FrameLayout onMeasure 函数的计算过程。以此类推,接下来测量 ViewRoot,然后再测量 id/statusBarBackground,虽然不知道 id/statusBarBackground 是什么,但是调试的过程中,测出的它的高度=100px, 和 id/content 的 paddingTop 刚好相等。在最后测量 DecorView 的高宽,最终整个测量过程结束。所有的 View 的大小测量完毕。所有的 getMeasureWidth 和 getMeasureWidth 都已经有值了。Measure 分析到此为止,如有不懂,评论留言(简书:kelin)


layout 过程 #


============


mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);


......


mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());


performTraversals 方法执行完 mView.measure 计算出 mMeasuredXXX 后就开始执行 layout 函数来确定 View 具体放在哪个位置,我们计算出来的 View 目前只知道 view 矩阵的大小,具体这个矩阵放在哪里,这就是 layout 的工作了。layout 的主要作用 :根据子视图的大小以及布局参数将 View 树放到合适的位置上。


既然是通过 mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight()); 那我们来看下 layout 函数做了什么,mView 肯定是个 ViewGroup,不会是 View,我们直接看下 ViewGroup 的 layout 函数


public final void layout(int l, int t, int r, int b) {


if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {


if (mTransition != null) {


mTransition.layoutChange(this);


}


super.layout(l, t, r, b);


} else {


// record the fact that we noop'd it; request layout when transition finishes


mLayoutCalledWhileSuppressed = true;


}


}


代码可以看个大概,LayoutTransition 是用于处理 ViewGroup 增加和删除子视图的动画效果,也就是说如果当前 ViewGroup 未添加 LayoutTransition 动画,或者 LayoutTransition 动画此刻并未运行,那么调用 super.layout(l, t, r, b),继而调用到 ViewGroup 中的 onLayout,否则将 mLayoutSuppressed 设置为 true,等待动画完成时再调用 requestLayout()。


这个函数是 final 不能重写,所以 ViewGroup 的子类都会调用这个函数,layout 的具体实现是在 super.layout(l, t, r, b)里面做的,那么我接下来看一下 View 类的 layout 函数


public final void layout(int l, int t, int r, int b) {


.....


//设置 View 位于父视图的坐标轴


boolean changed = setFrame(l, t, r, b);


//判断 View 的位置是否发生过变化,看有必要进行重新 layout 吗


if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {


if (ViewDebug.TRACE_HIERARCHY) {


ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);


}


//调用 onLayout(changed, l, t, r, b); 函数


onLayout(changed, l, t, r, b);


mPrivateFlags &= ~LAYOUT_REQUIRED;


}


mPrivateFlags &= ~FORCE_LAYOUT;


.....


}


1、setFrame(l, t, r, b) 可以理解为给 mLeft 、mTop、mRight、mBottom 赋值,然后基本就能确定 View 自己在父视图的位置了,这几个值构成的矩形区域就是该 View 显示的位置,这里的具体位置都是相对与父视图的位置。


2、回调 onLayout,对于 View 来说,onLayout 只是一个空实现,一般情况下我们也不需要重载该函数,:


protected void onLayout(boolean changed, int left, int top, int right, int bottom) {


}


对于 ViewGroup 来说,唯一的差别就是 ViewGroup 中多了关键字 abstract 的修饰,要求其子类必须重载 onLayout 函数。


@Override


protected abstract void onLayout(boolean changed,

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
Android View的绘制流程,35岁技术人如何转型做管理