自定义属性--你真的了解match_parent吗

解析XML布局中的Enum与Dimension
本文探讨了在Android XML布局文件中使用enum与dimension类型的属性设置方式,特别是在layout_width和layout_height属性上的应用。文章通过自定义ViewGroup示例,深入分析了这两种属性类型在实际开发中的使用差异及注意事项。

问题

我们都知道在xml写 android:layout_width 的时候,内容可以是类似100px,100dp,wrap_content,match_parent这几种类型,前面2种是dimension类型,后面2种是enum类型,我们可以翻看下android源码的attr可以找到如下代码.从下边的注释可以看出,layout_width必定是一个dimension信息或者特定常数信息。在layout_width内部有3个enum,fill_parent,match_parent,wrap_content,他们分别表示-1,-1,-2三个常数。

    <declare-styleable name="ViewGroup_Layout">
        <!-- Specifies the basic width of the view.  This is a required attribute
             for any view inside of a containing layout manager.  Its value may
             be a dimension (such as "12dip") for a constant width or one of
             the special constants. -->
        <attr name="layout_width" format="dimension">
            <!-- The view should be as big as its parent (minus padding).
                 This constant is deprecated starting from API Level 8 and
                 is replaced by {@code match_parent}. -->
            <enum name="fill_parent" value="-1" />
            <!-- The view should be as big as its parent (minus padding).
                 Introduced in API Level 8. -->
            <enum name="match_parent" value="-1" />
            <!-- The view should be only big enough to enclose its content (plus padding). -->
            <enum name="wrap_content" value="-2" />
        </attr>

        <!-- Specifies the basic height of the view.  This is a required attribute
             for any view inside of a containing layout manager.  Its value may
             be a dimension (such as "12dip") for a constant height or one of
             the special constants. -->
        <attr name="layout_height" format="dimension">
            <!-- The view should be as big as its parent (minus padding).
                 This constant is deprecated starting from API Level 8 and
                 is replaced by {@code match_parent}. -->
            <enum name="fill_parent" value="-1" />
            <!-- The view should be as big as its parent (minus padding).
                 Introduced in API Level 8. -->
            <enum name="match_parent" value="-1" />
            <!-- The view should be only big enough to enclose its content (plus padding). -->
            <enum name="wrap_content" value="-2" />
        </attr>
    </declare-styleable>

这么看起来layout_width支持2种写法,一种是dimension写法,比如100px,200dp。还有一种是enum写法,比如match_parent,wrap_content。
看到这种写法,蛮有意思的,刚好有个控件也有类似的需求,于是我就写了一个试试,简单写了个demo

public class FormLayout extends ViewGroup {
    public FormLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);

    }

    public FormLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FormLayout, defStyleAttr, 0);
        int intervalHor = a.getDimensionPixelSize(R.styleable.FormLayout_interval_hor, 0);
        int intervalVer = a.getDimensionPixelSize(R.styleable.FormLayout_interval_ver, 0);
        int itemPerRow = a.getInteger(R.styleable.FormLayout_item_per_row, 1);
        a.recycle();
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

    }
}

attrs.xml里面这么写

    <declare-styleable name="FormLayout">
        <attr name="interval_hor" format="dimension">
            <enum name="average" value="-1" />
            <enum name="max" value="-2" />
        </attr>

        <attr name="interval_ver" format="dimension"></attr>
        <attr name="item_per_row" format="integer"></attr>
        <attr name="mode" format="integer"></attr>
    </declare-styleable>

layout如下

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    tools:context="com.fish.a2.MainActivity">

    <com.fish.a2.FormLayout
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:text="Hello World!"
        app:interval_hor="average"
        app:interval_ver="10dp"
        app:item_per_row="4" />

</LinearLayout>

结果不幸的是crash了,crash日志为,看起来是getDimensionPixelSize的时候类型不支持类型0x10,看来enum的类型是0x10.

        Caused by: java.lang.UnsupportedOperationException: Can't convert to dimension: type=0x10
                                                               at android.content.res.TypedArray.getDimensionPixelSize(TypedArray.java:666)
                                                               at com.fish.a2.FormLayout.<init>(FormLayout.java:24)
                                                               at com.fish.a2.FormLayout.<init>(FormLayout.java:17)

查一下getDimensionPixelSize源码,发现只支持TYPE_NULL(0x00)TypedValue.TYPE_DIMENSION(0x05)TYPE_ATTRIBUTE(0x02)三种,并不支持0x10,所以crash了。

    public int getDimensionPixelSize(int index, int defValue) {
        if (mRecycled) {
            throw new RuntimeException("Cannot make calls to a recycled instance!");
        }

        index *= AssetManager.STYLE_NUM_ENTRIES;
        final int[] data = mData;
        final int type = data[index+AssetManager.STYLE_TYPE];
        if (type == TypedValue.TYPE_NULL) {
            return defValue;
        } else if (type == TypedValue.TYPE_DIMENSION) {
            return TypedValue.complexToDimensionPixelSize(
                data[index+AssetManager.STYLE_DATA], mMetrics);
        } else if (type == TypedValue.TYPE_ATTRIBUTE) {
            final TypedValue value = mValue;
            getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value);
            throw new UnsupportedOperationException(
                    "Failed to resolve attribute at index " + index + ": " + value);
        }

        throw new UnsupportedOperationException("Can't convert to dimension: type=0x"
                + Integer.toHexString(type));
    }

那么凭什么ViewGroup的layout_width就不会crash呢?看ViewGroup的LayoutParam代码,可以看到用的是getLayoutDimension而非getDimensionPixelSize,看来玄机在这里,

        public LayoutParams(Context c, AttributeSet attrs) {
            TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_Layout);
            setBaseAttributes(a,
                    R.styleable.ViewGroup_Layout_layout_width,
                    R.styleable.ViewGroup_Layout_layout_height);
            a.recycle();
        }

           protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
            width = a.getLayoutDimension(widthAttr, "layout_width");
            height = a.getLayoutDimension(heightAttr, "layout_height");
        }

我们再看看getLayoutDimension的代码,
支持TypedValue.TYPE_DIMENSION(0x05)TYPE_ATTRIBUTE(0x02)以及TYPE_FIRST_INT(0x10)-TYPE_LAST_INT(0x1f),所以0x10在他的势力范围之内。

//TypedArray
   public int getLayoutDimension(int index, String name) {
        if (mRecycled) {
            throw new RuntimeException("Cannot make calls to a recycled instance!");
        }

        index *= AssetManager.STYLE_NUM_ENTRIES;
        final int[] data = mData;
        final int type = data[index+AssetManager.STYLE_TYPE];
        if (type >= TypedValue.TYPE_FIRST_INT
                && type <= TypedValue.TYPE_LAST_INT) {
            return data[index+AssetManager.STYLE_DATA];
        } else if (type == TypedValue.TYPE_DIMENSION) {
            return TypedValue.complexToDimensionPixelSize(
                data[index+AssetManager.STYLE_DATA], mMetrics);
        } else if (type == TypedValue.TYPE_ATTRIBUTE) {
            final TypedValue value = mValue;
            getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value);
            throw new UnsupportedOperationException(
                    "Failed to resolve attribute at index " + index + ": " + value);
        }

        throw new UnsupportedOperationException(getPositionDescription()
                + ": You must supply a " + name + " attribute.");
    }

看起来我们只要把getDimensionPixelSize缓存getLayoutDimension即可,实验证明,的确如此,对应工程AttrEnumDimension。

总结

如果自定义属性格式为dimension又支持enum,那么我们在获取属性值的时候,必须使用getLayoutDimension,不可使用getDimensionPixelSize.

<think> 我们正在讨论如何通过代码设置MarginLayoutParams的高度为match_parent。在Android中,match_parent(在旧版本中称为fill_parent)是一个常量值,表示视图应该尽可能与其父容器一样大。 根据引用[3],我们可以看到在XML中设置高度为match_parent的写法。现在我们希望在代码中实现同样的效果。 在代码中,我们可以通过设置LayoutParams的高度为LayoutParams.MATCH_PARENT来实现。由于我们需要的是MarginLayoutParams(因为引用[1]和引用[2]提到MarginLayoutParams用于设置margin,并且我们可能需要保留margin设置),因此我们使用MarginLayoutParams。 步骤: 1. 创建一个MarginLayoutParams实例。 2. 将其高度设置为LayoutParams.MATCH_PARENT。 3. 如果需要,可以设置其他参数(如宽度、边距等)。 4. 将LayoutParams设置给对应的视图。 示例代码: ```java // 假设我们要设置一个视图(例如一个TextView)的布局参数 View view = findViewById(R.id.my_view); // 创建MarginLayoutParams,宽度设置为MATCH_PARENT,高度也设置为MATCH_PARENT MarginLayoutParams params = new MarginLayoutParams( MarginLayoutParams.MATCH_PARENT, MarginLayoutParams.MATCH_PARENT ); // 如果需要设置边距,可以这样做 params.setMargins(leftMargin, topMargin, rightMargin, bottomMargin); // 注意:在某些容器中,可能需要使用该容器特定的LayoutParams类型,但MarginLayoutParams是通用的,可以用于所有支持边距的容器。 // 将参数设置给视图 view.setLayoutParams(params); ``` 注意:如果该视图的父容器是特定的布局(如LinearLayout),则也可以使用LinearLayout.LayoutParams,但通常使用ViewGroup.MarginLayoutParams即可,因为它是通用的。不过,在创建LayoutParams时,最好使用与父容器匹配的LayoutParams类型。但是,由于我们使用了MarginLayoutParams(它继承自ViewGroup.LayoutParams,并添加了边距),并且大多数布局都支持它,所以通常可以这样用。 但是,如果父容器是FrameLayout、LinearLayout等,它们都支持MarginLayoutParams(因为它们继承自ViewGroup.MarginLayoutParams),所以上述代码是安全的。 另外,引用[1]中展示了如何自定义ViewGroup时生成MarginLayoutParams,这说明了在自定义布局中支持margin的方法。因此,使用MarginLayoutParams是通用的设置边距的方式。 最后,引用[4]提到了一些关于margin在wrap_content情况下的注意事项,但这里我们设置的是match_parent,所以不会遇到引用[4]中的问题。 所以,设置高度为match_parent的关键是使用`MarginLayoutParams.MATCH_PARENT`作为高度值。 相关问题: 1. 如何通过代码设置视图的宽度为match_parent? 2. 在代码中如何设置视图的边距(margin)? 3. 如果父布局是ConstraintLayout,如何设置视图的布局参数? 4. 使用MarginLayoutParams设置边距时有哪些注意事项? </think>要使用代码设置 `MarginLayoutParams` 的高度为 `match_parent`,可以通过以下方法实现: ```java // 获取视图的布局参数 View view = findViewById(R.id.your_view); ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) view.getLayoutParams(); // 设置高度为 match_parent params.height = ViewGroup.LayoutParams.MATCH_PARENT; // 或使用 LayoutParams.MATCH_PARENT // 应用新的布局参数 view.setLayoutParams(params); ``` ### 关键说明: 1. **`MATCH_PARENT` 常量** `ViewGroup.LayoutParams.MATCH_PARENT` 对应 XML 中的 `match_parent` 属性[^3] 2. **`MarginLayoutParams` 继承关系** `MarginLayoutParams` 是 `ViewGroup.LayoutParams` 的子类,专门用于处理边距设置[^2]: ```mermaid classDiagram ViewGroup.LayoutParams <|-- MarginLayoutParams class ViewGroup.LayoutParams{ +int width +int height } class MarginLayoutParams{ +int leftMargin +int topMargin +int rightMargin +int bottomMargin } ``` 3. **完整示例(包含宽度设置)**: ```java // 创建新参数(宽度设为 wrap_content,高度设为 match_parent) ViewGroup.MarginLayoutParams newParams = new ViewGroup.MarginLayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, // 宽度 ViewGroup.LayoutParams.MATCH_PARENT // 高度 ); // 设置边距(可选) newParams.setMargins(20, 10, 20, 10); // 左,上,右,下 // 应用参数 view.setLayoutParams(newParams); ``` ### 注意事项: 1. **布局容器兼容性** 此方法适用于所有支持 `MarginLayoutParams` 的容器,包括: - `LinearLayout` - `RelativeLayout` - `FrameLayout` - `ConstraintLayout`(需使用特定实现) 2. **XML 等价写法** 上述代码等效于 XML 中的: ```xml <View android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_marginLeft="20dp" android:layout_marginTop="10dp" android:layout_marginRight="20dp" android:layout_marginBottom="10dp"/> ``` 3. **特殊场景处理** 当父容器是 `RelativeLayout` 时,需注意某些依赖定位的属性(如 `layout_above`)可能影响 `match_parent` 的效果[^4] ### 相关问题: 1. 如何动态设置视图宽度为 `wrap_content`? 2. `match_parent` 和 `fill_parent` 有什么区别? 3. 在自定义 ViewGroup 中如何正确处理 `MarginLayoutParams`[^1]? 4. 为什么在 `RelativeLayout` 中设置 `match_parent` 有时会失效[^4]?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值