Android源码解析之在Activity中调用measure方法测量宽高的原理
来源:互联网 发布:win10 重置网络设置 编辑:程序博客网 时间:2024/06/01 18:33
一,写在前面
在文章自定义控件之测量一文中,已经很清楚描述了view,viewgroup的测量过程。本篇文章是基于自定义控件之测量的一个小小的使用,不会再详细介绍源码所有流程,直接上源码,分析在Activity中调用measure方法测量宽高的原理。那目的是什么呢?就是为了在activity的onCreate方法里获取控件的宽高值。
在onCreate()中获取控件宽高,调用view.getMeasuredWidth(),但是这个方法返回宽度值是有条件的。在测量TextView过程中,调用setMeasuredDimession(w,h)完成测量后,getMeasuredWidth()才有值。可以选择处理方式之一:先手动测量view,调用view.measure(w,h),才能正确获取宽度值。
二,示例展示
那么参数w,h应该传入些什么呢,先看下面这个例子:
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" > <TextView android:id="@+id/tv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#fff" android:background="#f00" android:layout_centerInParent="true" android:text="测量我丫" /></RelativeLayout>MainActivity.java
public class MainActivity extends Activity {private TextView tv;private static final String TAG = "MainActivity";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);tv = (TextView) findViewById(R.id.tv);//情况1 --------------------------int width = tv.getMeasuredWidth();Log.e(TAG, "width(in method onCreate):" + width);//情况2 --------------------------------int lpWidth = tv.getLayoutParams().width; //获取textview的布局参数对象LayoutParams,并获取宽度值widthint lpHeight = tv.getLayoutParams().height;int widthSpecSize = 0; //定义textview宽度测量值的大小int widthSpecMode = 0; //定义textview宽度测量值的模式int heightSpecSize = 0;int heightSpecMode = 0;//LayoutParams的宽度值width,分3种情况考虑if (lpWidth == LayoutParams.WRAP_CONTENT && lpHeight == LayoutParams.WRAP_CONTENT) { //值为wrap_content(-2)时widthSpecSize = Integer.MAX_VALUE;widthSpecMode = MeasureSpec.AT_MOST;heightSpecSize = Integer.MAX_VALUE;heightSpecMode = MeasureSpec.AT_MOST;Log.e(TAG, "宽高布局参数为wrap_content时,测量开始啦...");} else if (lpWidth == LayoutParams.MATCH_PARENT && lpHeight == LayoutParams.MATCH_PARENT) { //值为match_parent(-1)时Log.e(TAG, "宽高布局参数为match_parent时,需要知道父容器给的剩余空间,才可以得到TextView的MeasureSpec");} else if (lpWidth > 0 && lpHeight > 0){ //值为具体dp值时widthSpecSize = lpWidth;widthSpecMode = MeasureSpec.EXACTLY;heightSpecSize = lpHeight;heightSpecMode = MeasureSpec.EXACTLY;Log.e(TAG, "宽高布局参数为具体数值时,测量开始啦...");}//合成size and modeint tv_widthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSpecSize, widthSpecMode);int tv_heightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSpecSize, heightSpecMode);//传入measureSpec,开始测量tv.measure(tv_widthMeasureSpec, tv_heightMeasureSpec);//获取测量后的宽度值int measuredWidth = tv.getMeasuredWidth();Log.e(TAG, "measuredWidth(in method onCreate):" + measuredWidth);}}打印log如下:
从log可以看出:如果view还没有测量完成,宽度值为0;测量之后,宽度值为56px。
我们只分析宽度,因此在代码中并没有列出所有情况,只研究宽度,高度是类似的。接下来,从源码角度解析measure方法中参数MeasureSpec的取值问题。目前我们能知道的就是textview的布局参数:android:layout_width="wrap_content",因此根据布局宽度,分3种情况分析MeasureSpec的取值。
三,源码分析
查看ViewGroup中getChildMeasureSpec方法源码,分析父容器是如何给textview设置MeasureSpec的值,如下:
public static int getChildMeasureSpec(int spec, int padding, int childDimension) { int specMode = MeasureSpec.getMode(spec); int specSize = MeasureSpec.getSize(spec); int size = Math.max(0, specSize - padding); int resultSize = 0; int resultMode = 0; switch (specMode) { // Parent has imposed an exact size on us case MeasureSpec.EXACTLY: if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size. So be it. resultSize = size; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be // bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent has imposed a maximum size on us case MeasureSpec.AT_MOST: if (childDimension >= 0) { // Child wants a specific size... so be it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size, but our size is not fixed. // Constrain child to not be bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be // bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent asked to see how big we want to be case MeasureSpec.UNSPECIFIED: if (childDimension >= 0) { // Child wants a specific size... let him have it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size... find out how big it should // be resultSize = 0; resultMode = MeasureSpec.UNSPECIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size.... find out how // big it should be resultSize = 0; resultMode = MeasureSpec.UNSPECIFIED; } break; } return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }
上面有3*3=9中情况,参数spec:父容器的MeasureSpec;参数padding:textview的margin值+父容器的padding+父容器已经被使用的宽度值;参数childDimession:textview布局参数中宽度值。代码中,变量size是父容器剩余的宽度空间,当textview的childDimession有精确的dp值时,直接使用这个dp值作为测量大小resultSize;当为wrap_content,match_content时,使用size作为测量大小resultSize。
分析可知:
1,当childDimession >= 0,测量大小resultSize为childDimession,测量模式为EXACTLY;
2,当childDimession=wrap_content时,测量大小resultSize为父容器剩余宽度空间size,测量模式AT_MOST;(无需讨论specMode为UNSPECIFIED情况)
3,当childDimenssion=match_parent时,测量大小resultSize为父容器剩余宽度空间size,测量模式:specMode为EXACTLY时,为EXACTLY;specMode为AT_MOST时,为AT_MOST;
结论:那么调用textview.measure(w,h)时,
1,若布局参数为具体dp值,即childDimession >= 0,textview的measureSpec=childDimession+EXACTLY;
2,若布局参数为wrap_content,即childDimession=wrap_content时,textview的measureSpec=size+AT_MOST;
3,若布局参数为match_parent,即childDimenssion=match_parent时,textview的measureSpec=size+(EXACTLY或AT_MOST);
继续分析上面结论,第1种情况,我们可以计算出textview的MeasureSpec,调用textview.measure(w,h)完成测量;第2,3种情况需要知道size,也就是父容器留给textview宽度的剩余空间,不知道怎么办呢,于是就看看textview测量时如何使用measureSpec的,查看textview的onMeasure(w,h)源码:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int width; int height; BoringLayout.Metrics boring = UNKNOWN_BORING; BoringLayout.Metrics hintBoring = UNKNOWN_BORING; if (mTextDir == null) { mTextDir = getTextDirectionHeuristic(); } int des = -1; boolean fromexisting = false; if (widthMode == MeasureSpec.EXACTLY) { // Parent has told us how big to be. So be it. width = widthSize; } else { if (mLayout != null && mEllipsize == null) { des = desired(mLayout); } if (des < 0) { boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring); if (boring != null) { mBoring = boring; } } else { fromexisting = true; } if (boring == null || boring == UNKNOWN_BORING) { if (des < 0) { des = (int) FloatMath.ceil(Layout.getDesiredWidth(mTransformed, mTextPaint)); } width = des; } else { width = boring.width; } final Drawables dr = mDrawables; if (dr != null) { width = Math.max(width, dr.mDrawableWidthTop); width = Math.max(width, dr.mDrawableWidthBottom); } if (mHint != null) { int hintDes = -1; int hintWidth; if (mHintLayout != null && mEllipsize == null) { hintDes = desired(mHintLayout); } if (hintDes < 0) { hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring); if (hintBoring != null) { mHintBoring = hintBoring; } } if (hintBoring == null || hintBoring == UNKNOWN_BORING) { if (hintDes < 0) { hintDes = (int) FloatMath.ceil(Layout.getDesiredWidth(mHint, mTextPaint)); } hintWidth = hintDes; } else { hintWidth = hintBoring.width; } if (hintWidth > width) { width = hintWidth; } } width += getCompoundPaddingLeft() + getCompoundPaddingRight(); if (mMaxWidthMode == EMS) { width = Math.min(width, mMaxWidth * getLineHeight()); } else { width = Math.min(width, mMaxWidth); } if (mMinWidthMode == EMS) { width = Math.max(width, mMinWidth * getLineHeight()); } else { width = Math.max(width, mMinWidth); } // Check against our minimum width width = Math.max(width, getSuggestedMinimumWidth()); if (widthMode == MeasureSpec.AT_MOST) { width = Math.min(widthSize, width); } }//... codesetMeasuredDimension(width, height);}分析可知:
1,当widthMode为EXACTLY时,width为widthSize;而widthSize就是前面提到的,父控件留给textview剩余宽度空间大小size。这个width要作为setMeasuredDimession(width,height)的参数,完成textview的测量工作。
2,当widthMode为AT_MOST时,只在执行if (widthMode == MeasureSpec.AT_MOST) {width = Math.min(widthSize, width);}中用到了widthSize这个变量,其他代码计算width值并没有用到widthSize。可以看出width最终的值与widthSize没有关系,只是用来在计算出width之后,取width和widthSize中较小的值。于是,我们可以将widthSize->前面的size->最终的测量大小,设置为Integer.MAX_VALUE,完成textview的MeasureSpec构造,测量textview理论上是合理的。这里解释了前面demo中当布局参数为wrap_content时,如此构造textview的MeasureSpec的原因。
显然,当布局参数为match_parent时,一旦textview的测量模式为EXACTLY,就需要知道size的值,不知道就测量不了textview,而确实没法搞到这个值。
四,结论
结论:在调用view.measure(w,h)测量控件view时,
1,如果view的宽高布局参数为具体dp值,那么可以构造出view的MeasureSpec,示例见上面demo中代码;
2,如果view的宽高布局参数为wrap_content时,该view的specMode肯定为AT_MOST,那么specSize在view的onMeasure(w,h)中计算得出,只需在调用view.measure(w,h)时,象征性给specSize设置一个超大的int值,并不影响测量textview;
3,如果view的宽高布局参数是match_parent时,由于view的specMode可能为EXACTLY(这时,需知道父容器留给view的剩余空间大小),那么无法通过measure方法测量该view。
五,另外
附:获取view的宽高,可以选择先测量该控件,再获取;也可以选择在view自己测量完成后,再调用getMeasuredWidth方法。有几种方式判断view是否自己测量完成,如,在Activity中重写onWindowFucusChanged(boolean hasFocus),在该方法里面调用getMeasuredWidth方法也可以正确获取view的宽高。方式还有很多种,但不是本篇文章研究的重点,有兴趣的哥们自己去网上s吧!
- Android源码解析之在Activity中调用measure方法测量宽高的原理
- 在onCreate中测量View组件宽高的方法
- Android源码解析之应用程序在新的进程中启动新的Activity的方法和过程分析
- 源码解析Android中View的measure量算过程
- 源码解析Android中View的measure量算过程
- 源码解析Android中View的measure量算过程
- 源码解析Android中View的measure量算过程
- android中View.measure方法的源码注解
- android中view的宽高测量
- view工作流程解析之measure测量
- Android View 测量流程(Measure)完全解析
- Android View 测量流程(Measure)完全解析
- Android View 测量流程(Measure)完全解析
- 带着问题学习Android中View的measure测量
- Android中View的绘制原理之measure
- 关于Android 尝试在onCreate方法内测量view的宽高的测试
- Android应用程序窗口Activity的测量Measure布局Layout和绘制Draw过程分析
- 如何在onCreate中测量View的实际宽高
- Unity PlayerPrefs 游戏存档
- xss攻击解析
- 实用STM32的串口控制平台的实现(建议收藏)
- 07-定位 z-index
- html的name属性可以用来获取dom元素,表单提交数组name的写法
- Android源码解析之在Activity中调用measure方法测量宽高的原理
- join——实现线程顺序运行
- 字符串转UTF-8
- Android简单的加法计算器
- 线程的几种状态转换
- Hibernate框架总结
- MySQL5.7 大大降低了半同步复制-数据丢失的风险
- java兑现html网页的gzip解压
- linux常用命令