关闭

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

221人阅读 评论(0) 收藏 举报
分类:

环境 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获得






0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:570373次
    • 积分:5900
    • 等级:
    • 排名:第4514名
    • 原创:92篇
    • 转载:7篇
    • 译文:0篇
    • 评论:268条
    文章分类