Android自定义 View 系列-MeasureSpec

Posted Q-CODER

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android自定义 View 系列-MeasureSpec相关的知识,希望对你有一定的参考价值。

【Android】自定义 View 系列- 绘制流程 一文中,在测量过程中,是通过 一定的规则 得出最后我们测量的宽高,然后通过 setMeasuredDimension() 保存结果。

那么这里说到的规则主要就是---MeasureSpec 和 LayoutParams

MeasureSpec 是??

//源码描述
/**
* A MeasureSpec encapsulates the layout requirements passed from parent to child.
* Each MeasureSpec represents a requirement for either the width or the height. 
* A MeasureSpec is comprised of a size and a mode. There are three possible modes:
* UNSPECIFIED
* The parent has not imposed any constraint on the child. It can be whatever size it wants.
* EXACTLY
* The parent has determined an exact size for the child. The child is going to be given  
* those bounds regardless of how big it wants to be.
* AT_MOST
* The child can be as large as it wants up to the specified size.
*/
提取主要内容:
1.MeasureSpec 父 View 对子 View 的宽高要求。
2.MeasureSpec 包含了两个信息 模式(mode)和大小(size)
3.一共有三种模式:
    UNSPECIFIED- 任意大小(It can be whatever size it wants )-例如 recyclerview 子View 的大小是超过父View 的大小的
    EXACTLY - 精确值(regardless of how big is wants to be, an exact size )、
    AT_MOST - 在父View 的限制范围中任意大小(as large as it wants up to the specified size).

但是,对于我们开发者来说,我们知道一个 View 的大小,不是应该由我们通过 layout_width 和 layout_height 决定的吗?

对,也不对。事实是,在对于任意一个View (包括ViewGroup),最终的大小是由父 View 的要求(MeasureSpec)和子View 自己的需求(LayoutParams-layout_width & layout_height)决定的。具体是如何决定的呢?

/**
* @param spec The requirements for this view
* @param padding The padding of this view for the current dimension and
*        margins, if applicable
* @param childDimension How big the child wants to be in the current
*        dimension
* @return a MeasureSpec integer for the child
*/
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 them 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);

(上面代码逻辑很简单,源码的注解也很贴心了,请各位耐心读完。然后再看下面的总结表格,切忌死记硬背下面的表格)

总结为表格,其中:childDimension(子View 自身大小)、size(父view剩余空间)

EXACTLY

AT_MOST

UNSPECIFIED

具体值

(即 childDimension ≥ 0)

模式:EXACTLY

大小:childDimension

模式:EXACTLY

大小:childDimension

模式:EXACTLY

大小:childDimension

match_parent

模式:EXACTLY

大小:size

模式:AT_MOST

大小:size

模式:UNSPECIFIED

大小:View.sUseZeroUnspecifiedMeasureSpec ? 0 : size

wrap_content

模式:AT_MOST

大小:size

模式:AT_MOST

大小:size

模式:UNSPECIFIED

大小:View.sUseZeroUnspecifiedMeasureSpec ? 0 : size


今日疑问:为什么在一次事件序列中 onInterceptTouchEvent() 返回 true 后就不会再次执行onInterceptTouchEvent() 了,而在下一次事件序列中又会再次触发吗?

这个问题,我暂时没有找到切确的答案。截止目前研究,暂时认为是 mGroupFlags 标志被改变后,导致进入 onInterceptTouchEvent() 不再触发,并且在 UP/CANCEL/HOVER_MOVE 和下次事件序列开始(即 DOWN) 会调用 resetTouchState() 重置状态。这样又可以触onInterceptTouchEvent()。

对于 MeasureSpec 的内容和 上述疑问欢迎大家评论区交流~

以上是关于Android自定义 View 系列-MeasureSpec的主要内容,如果未能解决你的问题,请参考以下文章

Android自定义 View 系列-View

Android自定义 View 系列-MeasureSpec

Android自定义 View 系列实战篇-View

Android自定义 View 系列实战篇-View

Android自定义 View 系列-MeasureSpec

Android自定义 View 系列-MeasureSpec