measure之UNSPECIFIED的用途

measure之UNSPECIFIED的用途

一. 前言

对于MeasureSpec很多人很陌生,而对于MeasureSpec.UNSPECIFIED就更陌生了,在这篇文章里讲UNSPECIFIED,因为很多人对它很陌生,本人通过一个demo场景,并跟踪代码,来坦述UNSPECIFIED的用途

二. demo代码

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:app="http://schemas.android.com/apk/res-auto"
            xmlns:tools="http://schemas.android.com/tools"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:orientation="vertical">


    <TextView
        android:layout_width="match_parent"
        android:layout_height="300dp"
        android:background="@color/gray"
        android:hint="haha"
        />
</ScrollView>
import android.app.Activity;
import android.os.Bundle;
import android.support.annotation.Nullable;

public class ScrollViewEditTextActivity extends Activity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.scroll_view_edit_text);
    }
}

三. 截图

事情并不是你想像的那样,为什么我给定高度不好使,TextView的高度为什么只有一行的高度
截图

四. 源码分析

4.1 先看ScrollView的measure源码:

ScrollView.java

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        if (!mFillViewport) {
            return;
        }
        //...ignore code

这里mFillViewport为false,也就是说它走的是super.onMeasure,super是FrameLayout

FrameLayout.java


@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int count = getChildCount();

    //...ignore code

    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) {
            measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
            //...ignore code

遍历子view,然后调用measureChildWithMargins,而这个方法被ScrllView覆盖了

ScrollView.java

@Override
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 = MeasureSpec.makeSafeMeasureSpec(
            MeasureSpec.getSize(parentHeightMeasureSpec), MeasureSpec.UNSPECIFIED);

    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

这里重点看第10行,它强行把size和mode=MeasureSpec.UNSPECIFIED传给子View

makeSafeMeasureSpec原理可以参考 MeasureSpec存储信息

4.2 再来看TextView源码

而TextView的Measure

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  //...ignore code
if (heightMode == MeasureSpec.EXACTLY) {
            // Parent has told us how big to be. So be it.
            height = heightSize;
            mDesiredHeightAtMeasure = -1;
        } else {
            int desired = getDesiredHeight();

            height = desired;
            mDesiredHeightAtMeasure = desired;

            if (heightMode == MeasureSpec.AT_MOST) {
                height = Math.min(desired, heightSize);
            }
        }

        int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
        if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
            unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
        }

        /*
         * We didn't let makeNewLayout() register to bring the cursor into view,
         * so do it here if there is any possibility that it is needed.
         */
        if (mMovement != null ||
            mLayout.getWidth() > unpaddedWidth ||
            mLayout.getHeight() > unpaddedHeight) {
            registerForPreDraw();
        } else {
            scrollTo(0, 0);
        }

        setMeasuredDimension(width, height);
    }
  1. 如果父容器是ScrollView,那mode=UNSPECIFIED,就会走第8行
  2. 第9行是根据内容算出行数再确定高度,也就是TextView的实际内容高度
  3. 然后这个高度就传给了setMeasuredDimension
  4. 导致xml里配的高度不生效,这也是有些程序员会说我设了高度为啥没生效的原因
  5. 抛开ScrollView不说,如果父容器是atMost,它会根据父容器能支持的最大高度和desired对比,取最小的;因此at_most就是wrap_content,至于为什么at_most对应的是wrap_content,有兴趣可以看源码。

五. 总结

  1. UNSPECIFIED会在ScrollView的measure方法里传给子View
  2. 子View收到UNSPECIFIED,会根据自己的实际内容大小来决定高度
  3. UNSPECIFIED与AT_MOST的区别就是,它没有最大size限定这也说明UNSPECIFIED在ScrollView里很实用,因为ScrllView不需要限定子View的大小,它可以滚动嘛
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值