MeasureSpec学习 - 转

本文详细解释了MeasureSpec的概念及其在自定义View和ViewGroup中的作用。介绍了MeasureSpec的三种模式:精确模式、最大模式和未指定模式,并展示了如何使用MeasureSpec工具类进行尺寸测量。此外,还提供了ScrollView嵌套ListView和GridView时解决冲突的示例代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在自定义ViewViewGroup的时候,我们经常会遇到int型的MeasureSpec来表示一个组件的大小,这个变量里面不仅有组件的尺寸大小,还有大小的模式。

这个大小的模式,有点难以理解。在系统中组件的大小模式有三种:

1.精确模式(MeasureSpec.EXACTLY

在这种模式下,尺寸的值是多少,那么这个组件的长或宽就是多少。

2.最大模式(MeasureSpec.AT_MOST

这个也就是父组件,能够给出的最大的空间,当前组件的长或宽最大只能为这么大,当然也可以比这个小。

3.未指定模式(MeasureSpec.UNSPECIFIED

这个就是说,当前组件,可以随便用空间,不受限制。

    可能有很多人想不通,一个int型整数怎么可以表示两个东西(大小模式和大小的值),一个int类型我们知道有32位。而模式有三种,要表示三种状  态,至少得2位二进制位。于是系统采用了最高的2位表示模式。如图:

最高两位是00的时候表示"未指定模式"。即MeasureSpec.UNSPECIFIED

最高两位是01的时候表示"'精确模式"。即MeasureSpec.EXACTLY

最高两位是11的时候表示"最大模式"。即MeasureSpec.AT_MOST

很多人一遇到位操作头就大了,为了操作简便,于是系统给我提供了一个MeasureSpec工具类。

这个工具类有四个方法和三个常量(上面所示)供我们使用:

 

//这个是由我们给出的尺寸大小和模式生成一个包含这两个信息的int变量,这里这个模式这个参数,传三个常量中的一个。

public static int makeMeasureSpec(int size, int mode)

 

//这个是得到这个变量中表示的模式信息,将得到的值与三个常量进行比较。

public static int getMode(int measureSpec)

 

//这个是得到这个变量中表示的尺寸大小的值。

public static int getSize(int measureSpec)

 

//把这个变量里面的模式和大小组成字符串返回来,方便打日志

 public static String toString(int measureSpec)

 

 

 

MeasureSpec.EXACTLY:当我们将控件的layout_width或layout_height指定为具体数值时如andorid:layout_width="50dip",或者为FILL_PARENT是,都是控件大小已经确定的情况,都是精确尺寸。

        MeasureSpec.AT_MOST是最大尺寸,当控件的layout_width或layout_height指定为WRAP_CONTENT时,控件大小一般随着控件的子空间或内容进行变化,此时控件尺寸只要不超过父控件允许的最大尺寸即可。因此,此时的mode是AT_MOST,size给出了父控件允许的最大尺寸。

        MeasureSpec.UNSPECIFIED是未指定尺寸,这种情况不多,一般都是父控件是AdapterView,通过measure方法传入的模式。

因此,在重写onMeasure方法时要根据模式不同进行尺寸计算。下面代码就是一种比较典型的方式:

@Override    
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    
    setMeasuredDimension(getMeasuredLength(widthMeasureSpec, true), getMeasuredLength(heightMeasureSpec, false));    
}    
    
    
private int getMeasuredLength(int length, boolean isWidth) {    
    int specMode = MeasureSpec.getMode(length);    
    int specSize = MeasureSpec.getSize(length);    
    int size;    
    int padding = isWidth ? getPaddingLeft() + getPaddingRight()    
            : getPaddingTop() + getPaddingBottom();    
    if (specMode == MeasureSpec.EXACTLY) {    
        size = specSize;    
    } else {    
        size = isWidth ? padding + mWave.length / 4 : DEFAULT_HEIGHT    
                + padding;    
        if (specMode == MeasureSpec.AT_MOST) {    
            size = Math.min(size, specSize);    
        }    
    }    
    return size;    
}  

 

 

解决ScrollView嵌套ListView和GridView冲突的方法

public class MyListView extends ListView {
        public MyListView(Context context) {
                super(context);
        }
        public MyListView(Context context, AttributeSet attrs) {
                super(context, attrs);
        }
        public MyListView(Context context, AttributeSet attrs, int defStyle) {
                super(context, attrs, defStyle);
        }
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
                int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
                                MeasureSpec.AT_MOST);
                super.onMeasure(widthMeasureSpec, expandSpec);
        }
}


 
public class MyGridView extends GridView {   
    private boolean haveScrollbar = true;   
    public MyGridView(Context context) {   
        super(context);   
    }   
    public MyGridView(Context context, AttributeSet attrs) {   
        super(context, attrs);   
    }   
    public MyGridView(Context context, AttributeSet attrs, int defStyle) {   
        super(context, attrs, defStyle);   
    }   
    /**  
     * 设置是否有ScrollBar,当要在ScollView中显示时,应当设置为false。 默认为 true  
     *   
     * @param haveScrollbars  
     */   
    public void setHaveScrollbar(boolean haveScrollbar) {   
        this.haveScrollbar = haveScrollbar;   
    }   
    @Override   
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {   
        if (haveScrollbars == false) {   
            int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);  
            super.onMeasure(widthMeasureSpec, expandSpec);   
        } else {   
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);   
        }   
    }   
}


 

 

### Android 中 `getChildMeasureSpec` 的使用说明 在 Android 开发中,`getChildMeasureSpec` 是一个非常重要的工具方法,用于计算子 View 的 MeasureSpec。它通常被用来帮助父容器根据自己的 MeasureSpec 和子 View 的 LayoutParams 来推导出适合的 MeasureSpec。 #### 什么是 MeasureSpecMeasureSpec 是一种封装了布局需求的数据结构,包含了两个部分:模式(Mode)和大小(Size)。模式有三种可能取值: - **UNSPECIFIED**: 父容器不对子控件施加任何约束,子控件可以任意想要的尺寸。 - **EXACTLY**: 子控件确切知道自己的尺寸是多少,通常是通过指定具体数值或 match_parent 实现。 - **AT_MOST**: 子控件最多能使用的空间是由父容器决定的,通常是 wrap_content 表达的方式。 #### `getChildMeasureSpec` 的作用 `getChildMeasureSpec` 方法的作用是从父级的 MeasureSpec 和子 View 的 LayoutParams 推导出适用于子 View 的 MeasureSpec。它的签名如下: ```java public static int getChildMeasureSpec(int spec, int padding, int childDimension) ``` 参数解释: - `spec`: 父容器的 MeasureSpec- `padding`: 父容器内部的填充区域(Padding),会影响可用的空间。 - `childDimension`: 子 View 的宽度或高度属性,来自其 LayoutParams。 返回值是一个新的 MeasureSpec 值,表示子 View 应该遵循的规格。 --- #### 使用场景与案例分析 ##### 场景一:解决 ViewPager 设置 `wrap_content` 失效问题 当我们在 XML 文件中给 `ViewPager` 设置 `android:layout_height="wrap_content"` 时,可能会发现实际效果并没有按照预期工作。这是因为默认情况下,`ViewPager` 的高度会被强制设置为 EXACTLY 模式下的固定值[^3]。 解决方案之一是重写 `onMeasure` 方法并调整子 View 的 MeasureSpec。以下是实现方式的一个例子: ```java @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int maxChildHeight = 0; // 遍历所有的子页面 for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); // 测量子 View 并记录最大高度 measureChild(child, widthMeasureSpec, heightMeasureSpec); maxChildHeight = Math.max(maxChildHeight, child.getMeasuredHeight()); } // 创建一个新的 MeasureSpec,基于最大的子 View 高度 int newHeightMeasureSpec = MeasureSpec.makeMeasureSpec( maxChildHeight, MeasureSpec.EXACTLY); super.onMeasure(widthMeasureSpec, newHeightMeasureSpec); } ``` 在这个过程中,`measureChild` 函数会调用 `getChildMeasureSpec` 自动处理子 View 的 MeasureSpec 换逻辑[^4]。 --- ##### 场景二:自定义 ViewGroup 的测量流程 如果开发者正在创建一个自定义的 ViewGroup,则需要手动管理子 View 的测量过程。此时可以通过 `getChildMeasureSpec` 计算每个子 View 的 MeasureSpec。 例如,假设我们有一个自定义的垂直线性布局,希望支持动态分配子项的高度比例。我们可以这样做: ```java @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int totalWeight = 0; // 统计总权重 for (int i = 0; i < getChildCount(); i++) { LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) getChildAt(i).getLayoutParams(); totalWeight += lp.weight; } // 如果存在权重分布,则重新分配剩余空间 if (totalWeight > 0) { int availableSpace = MeasureSpec.getSize(heightMeasureSpec); float spacePerUnit = availableSpace / totalWeight; for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams(); int childHeight = (int)(lp.weight * spacePerUnit); // 构造子 View 的 MeasureSpec int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec( childHeight, MeasureSpec.EXACTLY); // 对子 View 进行测量 child.measure(getChildMeasureSpec(widthMeasureSpec, getPaddingLeft() + getPaddingRight(), lp.width), childHeightMeasureSpec); } } else { // 默认情况直接传递父容器的 MeasureSpec super.onMeasure(widthMeasureSpec, heightMeasureSpec); } } ``` 这里的关键在于如何利用 `getChildMeasureSpec` 将父容器的 MeasureSpec 换成合适的子 View MeasureSpec[^2]。 --- ##### 场景三:RecyclerView 的 StaggeredGridLayoutManager 动态适配 当我们使用 `StaggeredGridLayoutManager` 作为 RecyclerView 的布局管理器时,有时也需要考虑子 View 的 MeasureSpec 是否合理配置。比如下面的例子展示了如何初始化一个瀑布流布局,并确保间隙策略正常生效[^5]: ```java // 定义一个两列的竖直方向瀑布流布局 StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL); layoutManager.setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_NONE); recyclerView.setLayoutManager(layoutManager); recyclerView.setAdapter(adapter); ``` 在这种情况下,虽然不需要显式调用 `getChildMeasureSpec`,但仍然依赖于框架底层对该函数的支持来完成子 View 的正确测量。 --- ### 总结 通过对 `getChildMeasureSpec` 的深入理解以及上述多个典型应用场景的学习,可以看出此 API 在复杂 UI 结构中的重要地位。无论是修复常见的组件行为异常还是构建全新的交互体验,掌握这一技术都能显著提升开发效率和产品质量。
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值