源码分析:LayoutParams的wrap_content, match_parent, 和具体值

问题:

在慢慢熟悉android 的过程中,发现一个view 或者layout的初始化,或者构造的流程还是比较清楚的,也就是加到父控件中,然后就开始了对应的生命周期。但是整个界面的父控件,或者说系统的第一个view, 是怎么来的,如何初始化和绘制的呢?

概述:

带着困扰我的问题,本文试图分析理解view 的measure 的过程,在分析过程中重点分析了LayoutParams 中MATCH_PARENT和MATCH_PARENT 的对应关系;onMeasure 默认值的计算过程;解释了onMeasure 接口中的注释中的问题,并提出一个问题:ViewRootImpl 是怎么创建的? 留作下篇引子。最后,讨论如何重写onMeasure()方法。

LayoutParams 中MATCH_PARENT和MATCH_PARENT  的对应关系

为什么从perform 开始本文,请见Android 动画animation 深入分析

在android.view.ViewRootImpl.performTraversals() 中开始measure的过程

  1. childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);  
  2. childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);  
  3. host.measure(childWidthMeasureSpec, childHeightMeasureSpec);  

在android.view.ViewRootImpl  中可以看到其对应关系LayoutParams 中这三个值在内部有个对应关系,那就是

LayoutParams.MATCH_PARENT  对应 MeasureSpec.EXACTLY

.LayoutParams.WRAP_CONTENT对应  MeasureSpec.AT_MOST

默认值(也就是具体值) 对应 MeasureSpec.EXACTLY

也就是内部只有两种模式 EXACTLY 精确模式 和 AT_MOST 最大模式。

  1. private int getRootMeasureSpec(int windowSize, int rootDimension) {  
  2.     int measureSpec;  
  3.     switch (rootDimension) {  
  4.   
  5.     case ViewGroup.LayoutParams.MATCH_PARENT:  
  6.         // Window can't resize. Force root view to be windowSize.  
  7.         measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);  
  8.         break;  
  9.     case ViewGroup.LayoutParams.WRAP_CONTENT:  
  10.         // Window can resize. Set max size for root view.  
  11.         measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);  
  12.         break;  
  13.     default:  
  14.         // Window wants to be an exact size. Force root view to be that size.  
  15.         measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);  
  16.         break;  
  17.     }  
  18.     return measureSpec;  
  19. }  

在这个getRootMeasureSpe() 中调用了makeMeasureSpec() 这个函数,从实现来看,通过按位与 的方法,把模式值和 具体的大小合并成了一个值。因为屏幕尺寸大小目前远小于30位, 就用了最高两位来标识计算的模式。
  1. private static final int MODE_SHIFT = 30;  
  2. public static final int EXACTLY     = 1 << MODE_SHIFT;  
  3. public static final int AT_MOST     = 2 << MODE_SHIFT;  

  1. public static int makeMeasureSpec(int size, int mode) {  
  2.     return size + mode;  
  3. }  
得到了宽、高的measureSpec 后,调用View.measure(), 可以看到measure的过程就是调用了 view 的onMeasure()方法。 就是如果要自定义view的话需要重写的onMeasure()方法。
  1. public final void measure(int widthMeasureSpec, int heightMeasureSpec) {  
  2.     if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||  
  3.             widthMeasureSpec != mOldWidthMeasureSpec ||  
  4.             heightMeasureSpec != mOldHeightMeasureSpec) {  
  5.   
  6.         // measure ourselves, this should set the measured dimension flag back  
  7.         onMeasure(widthMeasureSpec, heightMeasureSpec);  
  8.     }  
  9.   
  10.     mOldWidthMeasureSpec = widthMeasureSpec;  
  11.     mOldHeightMeasureSpec = heightMeasureSpec;  
  12. }  

从onMeasure的注释中可以看到:

1. onMeasure() 方法是被用来计算宽高的, 子类需要重写这个方法来提供更加准确和高效的计算方法。

2. 如果重写这个方法的话,必须调用setMeasuredDimension(int, int) 来保存计算的宽高的结果,否则会抛出异常IllegalStateException;

3. 基类的实现默认是使用背景大小来计算宽高,

4. 如果重写这个方法, 应该确保计算宽高的结果应该不小于view 的最小的宽高, ({@link #getSuggestedMinimumHeight()} and   * {@link #getSuggestedMinimumWidth()})

  1. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  2.     setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),  
  3.             getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));  
  4. }  

onMeasure 默认值的计算过程

这里看一下默认值是怎么计算的,然后再讨论一下自定义的onMeasure()应该怎么写。

1 首先,得到建议的最小高度, getSuggestedMinimumWidth(),果然如注释所写, 在背景不为空的情况下,使用背景的最小宽高。这里的mMinWidth 是layout 属性中的minWidth 或者minHeight, 如果没有写,默认值为零。由此可见在layout过程中写的最小值在默认情况下的确可以保证view的最小大小。

思考一下,为什么没有提供最大值这个参数呢? 

  1. protected int getSuggestedMinimumWidth() {  
  2.     int suggestedMinWidth = mMinWidth;  
  3.   
  4.     if (mBGDrawable != null) {  
  5.         final int bgMinWidth = mBGDrawable.getMinimumWidth();  
  6.         if (suggestedMinWidth < bgMinWidth) {  
  7.             suggestedMinWidth = bgMinWidth;  
  8.         }  
  9.     }  
  10.   
  11.     return suggestedMinWidth;  
  1. }  
  1. <pre name="code" class="java"><span style="font-family: Arial, Helvetica, sans-serif;">android.view.View.View(Context, AttributeSet, int)  
  2. </span>  
  1. <span style="font-family: Arial, Helvetica, sans-serif;">                case R.styleable.View_minWidth:</span>  
  1. mMinWidth = a.getDimensionPixelSize(attr, 0);  
  2. break;  

2. 接下来看getDefaultSize();这里根据measureSpec 的模式,如果是EXACTLY 精确模式 和 AT_MOST 最大模式,则使用 specSize ,否则使用传入的参数,就是刚才计算的getSuggestedMinimumWidth(),max(设置的最小宽高值, 背景宽高)
  1. public static int getDefaultSize(int size, int measureSpec) {  
  2.     int result = size;  
  3.     int specMode = MeasureSpec.getMode(measureSpec);  
  4.     int specSize = MeasureSpec.getSize(measureSpec);  
  5.   
  6.     switch (specMode) {  
  7.     case MeasureSpec.UNSPECIFIED:  
  8.         result = size;  
  9.         break;  
  10.     case MeasureSpec.AT_MOST:  
  11.     case MeasureSpec.EXACTLY:  
  12.         result = specSize;  
  13.         break;  
  14.     }  
  15.     return result;  
  16. }  
3 所以在xml 文件中设置了宽高属性,默认的计算结果就是specSize。 对于rootViewImpl 来说,LayoutParams.MATCH_PARENT 和LayoutParams.WRAP_CONTENT 都是 windowSize, 否则就是lp 的宽高,也就是mWindowAttributes
  1. WindowManager.LayoutParams lp = mWindowAttributes;  
而mWindowAttributes的赋值是在android.view.ViewRootImpl.setView(View, LayoutParams, View) 中的copyfrom(); 也就是根view 的属性。就是初始view的属性。
  1. public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {  
  2.     synchronized (this) {  
  3.         if (mView == null) {  
  4.             mView = view;  
  5.             mFallbackEventHandler.setView(view);  
  6.             mWindowAttributes.copyFrom(attrs);  

那么,到底这个ViewRootImpl是哪里来的呢,又有什么用处? to be continued...

讨论如何重写onMeasure()方法

看完上面的系统默认的处理方式。以及google 攻城狮 处心积虑留下的注释来看。能做就是提供to provide better measurements of  their content. 因为内容是自己自定义的,所以就应该按照自己的需求来计算宽高喽。

并遵循系统的要求,1.确保大于min值;2. 在onMeasure()中调用setMeasuredDimension(int, int) 来保存计算的宽高的结果

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页