Android-onCreate时,measure真的能获取到view的尺寸吗

原创 2017年05月19日 15:24:11

环境 Android Studio 2.3.2 + API(24)

我们在布局时,如果某个view的width或height设置为MATCH_PARENT的话,如下

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/layout_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.example.administrator.myapplication.MyTextView
        android:id="@+id/tv_content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="dfdsfsd" />
</LinearLayout>
布局很简单,一个LinearLayout,内部有一个TextView(我自定义了一个,从TextView继承,重写了onMeasure,便于调试跟踪)

如果你需要在Activity或者fragment的onCreate事件中获取layout_main这个视图的尺寸,网上搜到的最常用的方法就是如下代码

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.test);
        int width = View.MeasureSpec.makeMeasureSpec(0,View.MeasureSpec.UNSPECIFIED);
        int height =View.MeasureSpec.makeMeasureSpec(0,View.MeasureSpec.UNSPECIFIED);
        final LinearLayout layout = (LinearLayout)findViewById(R.id.layout_main);
        layout.measure(width,height);
        Log.e("width:",layout.getMeasuredWidth()+"");
    }


这样真的能获得运行中的尺寸吗?不能!
因为layout里面有一个TextView,实际获取的是TextView的宽度(不是充满屏幕的宽度,是根据里面文字而适应的宽度)

看看代码吧,当执行下面代码时

layout.measure(width,height);

因为LinearLayout->ViewGroup->View,所以会调用View的measure方法,其中会调用视图的onMeasure方法测量

        if (forceLayout || needsLayout) {
            // first clears the measured dimension flag
            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

            resolveRtlPropertiesIfNeeded();

            int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                // 这里执行视图的onMeasure方法测量
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } else {
                ////////////////////////
            }

因此执行到LinearLayout的onMeasure

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		//因为是垂直布局,所以执行measureVertical
        if (mOrientation == VERTICAL) {
            measureVertical(widthMeasureSpec, heightMeasureSpec);
        } else {
            measureHorizontal(widthMeasureSpec, heightMeasureSpec);
        }
    }

measureVertical代码中会执行

measureChildBeforeLayout(child, i, widthMeasureSpec, 0,heightMeasureSpec, usedHeight);
看函数名也大概知道,是在布局前,先测量内部child的尺寸,然后根据结果调整自身尺寸,继续跟进

    void measureChildBeforeLayout(View child, int childIndex,
            int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
            int totalHeight) {
        measureChildWithMargins(child, widthMeasureSpec, totalWidth,
                heightMeasureSpec, totalHeight);
    }
measureChildWithMargins代码中,会考虑margin和padding,child就是LinearLayout里面的那个TextView

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

这段代码里,重点的是getChildMeasureSpec,里面会对不同的MeasureSpec类型进行不同的处理,我们这里是UNSPECIFIED,所以摘取先关片段,如下:

        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) {
                // 布局中是MATCH_PARENT,所以处理后,mode不变,依然是UNSPECIFIED
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
接着就是执行TextView的measure方法了(这是第一次执行)

            if (des < 0) {
			//这里是根据文字内容,字体大小等属性,及Pain来得到合适的能容纳文字的宽度
                boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
                if (boring != null) {
                    mBoring = boring;
                }
            } else {
                fromexisting = true;
            }
BoringLayout的代码这里就不展开了,isBoring后,就得到了TextView的尺寸(非充满屏幕的宽度),并保存

width = boring.width;

//保存
setMeasuredDimension(width, height);
代码继续执行,测量完TextView后,回到LinearLayout的measureVertical方法,并根据child的尺寸,来设置自身尺寸

maxWidth = Math.max(maxWidth, measuredWidth);

在代码最后,会执行下面代码,强制统一尺寸!

        if (matchWidth) {
            forceUniformWidth(count, heightMeasureSpec);
        }
    private void forceUniformWidth(int count, int heightMeasureSpec) {
        // Pretend that the linear layout has an exact size.
		//这里注意,SpecMode被改为了EXACTLY,不再是UNSPECIFIED
        int uniformMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(),
                MeasureSpec.EXACTLY);
        for (int i = 0; i< count; ++i) {
           final View child = getVirtualChildAt(i);
           if (child != null && child.getVisibility() != GONE) {
               LinearLayout.LayoutParams lp = ((LinearLayout.LayoutParams)child.getLayoutParams());
               //因为布局文件中是MATCH_PARENT
               if (lp.width == LayoutParams.MATCH_PARENT) {
                   ......
				   //这里又会根据uniformMeasureSpec测量子视图
                   measureChildWithMargins(child, uniformMeasureSpec, 0, heightMeasureSpec, 0);
                   ......
               }
           }
        }
    }
同样重复前面的步骤,进入getChildMeasureSpec,因Mode已被改为EXACTLY(实际尺寸),所以摘取下面片段

        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;
				//子视图的Mode也被修改,并据此来测量
                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;

最终会执行TextView的onMeasure,得到实际尺寸(非充满屏幕的宽度)

上面过程我这里得到的Layout和TextView的宽度都是96,而不是屏幕的宽度

当布局完成后,会从ViewRootImpl的performTraversals开始,从最上层容器开始逐个计算,来得到运行时的实际尺寸,onCreate时,单独调用measure,根本不会执行到ViewRootImpl中,所以, 运行时尺寸,是得不到的。

所以,onCreate中通过measure得到的不是你想要的尺寸,除非你设置了具体的宽度,那样可以通过LayoutParams获得






版权声明:本文为博主原创文章,未经博主允许不得转载。

一个公式告诉你为什么程序员要转算法工程师

今天(2017年6月11日)爬了某招聘网站的十大城市的算法工程师职位。算法工程师这里的算法工程师包括比较广泛。有做数据科学的,有做图形的,有做信号处理的。之后会统计更细分的领域。十大城市以下按照算法工...

20万、50万、100万的算法工程师,到底有什么区别?

https://zhuanlan.zhihu.com/p/27072134 公元七世纪,在车迟国国家气象局组织的一次求雨活动中,虎力、鹿力、羊力三位大仙成功地祈下甘霖,于水火中救了黎民。老国...

Android View工作原理(一)----子View的measure(即子View的尺寸确定)

1. 什么是MeasureSpec? android官方文档是这样描述的:A MeasureSpec encapsulates the layout requirements passed from ...
  • yzf0011
  • yzf0011
  • 2017年02月17日 20:16
  • 176

Android在onCreate方法中获取view的宽高

可能很多人认为获取view的宽高是很容易的事情,直接调用getWidth()和getHeight()方法不就行了嘛,但在onCreate方法中得到的永远是0,原因很简单,这两个方法是在view执行on...

android_View.post(Runnable)在onCreate获取控件宽高分析

在实际开发过程中,我们有时候需要在 activity 中去获取某一个 view 的高度,然后根据该获取的高度去设置其他 view 的高度来达到我们的目的,往往我们都会在 activity#onCrea...

android onCreate中获取view宽高为0的多种解决方法

这个问题大家肯定遇到过不止一次,其实很简单,解决它也很容易,但是咱们追求的毕竟不是解决它,而是找到几种方法去解决,并且这么解决的原理是什么。   这里列出4种解决方案:Activity/View#o...

android在onCreate()方法中获取View的宽度与高度的方法实战

大家好,这篇博文主要是教给大家一个方法,如何在onCreate()方法中获取我们所需的View对象的高度和宽度,大家应该都试验过,在onCreate()方法中通过view.getWidth()方法和v...

android onCreate中获取view宽高为0的多种解决方法

这个问题大家肯定遇到过不止一次,其实很简单,解决它也很容易,但是咱们追求的毕竟不是解决它,而是找到几种方法去解决,并且这么解决的原理是什么。   这里列出4种解决方案: Activity/Vi...

Android: 在onCreate()中获得对象尺寸

Android: 在onCreate()中获得对象尺寸 2014-08-28      0 个评论       收藏    我要投稿 onCreate() 中 View 尚未...

Android获取View的宽高与View.measure详解

原文地址:http://blog.csdn.net/canot/article/details/50430998在oncreate()中无论利用view.getWidth()或是view.getHei...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Android-onCreate时,measure真的能获取到view的尺寸吗
举报原因:
原因补充:

(最多只允许输入30个字)