View测量流程与困惑

周末重新温习了一下《Android开发艺术探索》的View测量部分,View的尺寸由父容器和控件本身的LayoutParams决定的。ViewGroup中测量测量子View的方法分别是measureChildren方法和measureChildWithMargins方法,二者的区别 是是否考虑子View的margins属性。 不同的ViewGroup实现会调用二者中的一个,比如FrameLayout和LinearLayout通过measureChildWithMargins方法测量子View, NumPadKey和AbsoluteLayout调用measureChildren方法测量子View。二者最终都是通过getChildMeasureSpec 方法获取子View的MeasureSpec,然后获取的MeasureSpec作为参数去调用子View的 measure方法,这里以measureChildWithMargins方法作为起点来剖析下。
下面的代码那是相当精炼和清楚,首先获取子View的MarginLayoutParams参数,然后将父容器的MeasureSpec,子View的上下左右参数Padding、Margin参数和父容器已经使用的宽高,子View的宽高作为参数带入到getChildMeasureSpec方法中。通过getChildMeasureSpec方法获取子View的MeasureSpec后带入到子View的measure方法中。

//ViewGroup.java
/**
 * 触发子View去测量自身, 考虑父容器的MeasureSpec以及子View的padding和margins。 
 * 子View必须有MarginLayoutParams,重要实现在getChildMeasureSpec方法中。
 * @param child 需要测量的子View
 * @param parentWidthMeasureSpec 父容器的宽度MeasureSpec
 * @param 父容器已经被使用的宽度
 * @param parentHeightMeasureSpec 父容器的高度MeasureSpec
 * @param heightUsed 父容器已经被使用的高度
 */
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方法根据父容器spec、间距padding以及子View的宽高参数来确定子View的mode和size最后封装成子View的MeasureSpec返回。

//ViewGroup.java
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);//父容器留给子View分配的剩余空间

    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 = 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;
    }
    //noinspection ResourceType
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

View的measure方法接着调用onMeasure方法,在onMeasure方法确定测量宽高后通过setMeasuredDimension方法设置宽高,整个测量流程是measure->onMeasure->setMeasuredDimension。

//View.java
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
     boolean optical = isLayoutModeOptical(this);
     ……
	 onMeasure(widthMeasureSpec, heightMeasureSpec);
	 ……	 
 }
……
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
……

针对getChildMeasureSpec方法, 刚哥总结了下面的表格:
在这里插入图片描述
针对View以及父容器都是EXACTLY的情况,这里View的尺寸就是子View设置的尺寸,这里我有个疑问,如果子View设置的尺寸大于父容器的尺寸怎么办呢?难道还是按照子View的尺寸吗?于是我做了个实验,xml中设置LinearLayout(父容器)的宽度是400px,TextView(子View)的宽度是800px,原谅我这里用了px而非dp,这是为了方便后面在activity中获取尺寸比较明显。

<LinearLayout        
    android:orientation="vertical"
    android:background="#000"
    android:layout_width="400px"
    android:layout_height="wrap_content">
    <TextView
        android:onClick="go"
        android:layout_marginTop="20dp"
        android:background="#f00"
        android:layout_width="800px"
        android:layout_height="wrap_content"
        android:text="Hello World!" />
</LinearLayout>

然后在activity的go方法中获取View的宽度以及其父容器的宽度

public void go(View view) {
    LogUtil.d("textview is clicked,textview width="+view.getWidth()+",,LinearLayout width="+((View)view.getParent()).getWidth());
}

log打印如下:

textview is clicked,textview width=800,,LinearLayout width=400

顿时我裂开了,子View宽度确实就是之前设置的800,并且比父容器宽度还大。但是这里虽然TextView宽度比较大,但是由于父容器就这么大,所以给TextView更多的字符串长度,还是会截断到400px,并且超出400px的区域TextView点击事件也不生效,没卵用。
此时就涉及到clipChildren属性了

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值