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

问题

我们都知道在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.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值