Android View属性设置优先级的一点总结

目录

一 自定义属性

1.在attrs.xml中声明属性

2.在View的构造函数中获取属性值

3.在布局文件中使用

二 属性赋值的优先级

1.自定义控件GridView中增加属性name

2.在GridView的初始化构造函数,通过下面的代码获取到name的值

3.在不同的地方设置属性值,验证优先级

三 总结


前面一篇博文,主要介绍的Android自定义View的几种方式,那么定义的这个控件,肯定有一些属性希望在布局文件中可以直接进行配置。像在使用Android的系统控件的时候,都是以android:开头的一些属性,可以在布局文件中简单的配置就去设置某些属性值,在Android源码中系统的values文件夹维护一个attrs.xml文件来定义了这些属性,例如经常使用的layout_width:

 <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>
........
    </declare-styleable>

所以在自定义View的时候,同样也需要自定义属性,可以在布局文件或者其他地方中进行配置。

一 自定义属性

1.在attrs.xml中声明属性

从上面提到的layout_width源码中,可以看到,在定义一个属性的时候,需要通过<attr name="xxx" format="xxx" /> 这种标签的形式在attrs.xml声明属性。通常有两种方式:

一种就是通过<declare-styleable>标签进行声明,那么这种方式声明的属性,系统会生成一个属性的数组 R.styleable.GridView,每一个属性的索引就是R.styleable.GridView_name,通过该索引找到对应的属性。

<resources>   
    <declare-styleable name="GridView">
        <attr name="verticalSpacing" format="dimension" />
        <attr name="horizontalSpacing" format="dimension" />
        <attr name="numColumns" format="integer" />
        <!-- 初始化的个数-->
        <attr name="initNum" format="integer" />
        <!--测试不同的构造函数调用周期-->
        <attr name="name" format="string" />

    </declare-styleable>
</resources>

另外一种就是可以直接通过 <attr>进行声明,那么这种方式声明的属性就是一个元素,直接通过 R.attr.GridViewStyle的方式找到对应的属性值

<resources>   
      <!--通过单独的属性来设置属性值-->
    <attr name="GridViewStyle" format="reference" />
</resources>

参数说明:

参数 备注 举例
name 很简单就是这个属性的名字  
format reference 该属性可以设置资源文件的ID <attr name="textAppearance" format="reference" />
color 该属性为颜色值 <attr name="titleTextColor" format="color" />
boolean 该属性可以设置为对应数据类型的数值 <attr name="hint" format="string" />

float

integer
string
dimension

该属性可以设置为尺寸值,可以是具体带单位的尺寸值,也可以为R.dimen的引用

<attr name="layout_margin" format="dimension" />

enum

该属性可以设置为枚举值,使用的时候只能使用一种枚举值

<attr name="visibility">
     <enum name="visible" value="0" />
     <enum name="invisible" value="1" />
     <enum name="gone" value="2" />
</attr>

flag 该属性可以设置为位或运算

<attr name="textStyle">

    <flag name="normal" value="0" />

    <flag name="bold" value="1" />

     <flag name="italic" value="2" />

</attr>

fraction 该属性可以设置为百分比的数值 <attr name="centerX" format="float|fraction" />
混合 当时属性指定的时候也可以使用多种类型值,可以通过|指定 <attr name="gradientRadius" format="float|fraction|dimension" />

2.在View的构造函数中获取属性值

通常在自定义View的构造函数中通过context.obtainStyledAttribute获取到布局文件中设置的属性。

  TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.GridView, defStyleAttr, defStyleRes)

主要将布局文件里面定义的在R.styleable.GridView下定义的属性的值读出来。后面会具体说明下属性值在不同的地方赋值的优先级。那么在attrs.xml中定义的属性值,就可以通过类似下面的方式读出赋值。

     mVerticalSpacing = array.getDimensionPixelSize(R.styleable.GridView_verticalSpacing, 0);
     mHorizontalSpacing = array.getDimensionPixelOffset(R.styleable.GridView_horizontalSpacing, 0);
     maxNumber = array.getInt(R.styleable.GridView_maxNumber, 0);
     mNumColumns = array.getInt(R.styleable.GridView_numColumns, 4);
     initNum = array.getInt(R.styleable.GridView_initNum, 1);
     name = array.getString(R.styleable.GridView_name);

3.在布局文件中使用

     <com.android.attrsetting.grid.GridView
            android:id="@+id/gv_test"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="50px"
            psv:horizontalSpacing="50px"
            psv:initNum="1"
            psv:name="直接定义在布局文件中 设置的名字 优先级为1"
            psv:verticalSpacing="50px" />

另外要在布局文件中增加新的命名才可以使用这些自定义的属性。

  xmlns:psv="http://schemas.android.com/apk/res-auto"

二 属性赋值的优先级

在第一部分定义的各个属性,可以在多个地方赋值:像“布局xml赋值”、“布局xml中引用style”、“由defStyleAttr指定的style”、“由defStyleRes指定的style”、“theme中直接赋值”。在自定义View中通过context.obtainStyledAttributes(attrs, R.styleable.GridView, defStyleAttr, defStyleRes)取值的时候,那么到底以哪个地方的赋值为准呢?

通过几个例子来说明下不同设置值的优先级。

1.自定义控件GridView中增加属性name

 <!--测试不同的构造函数调用周期-->
<attr name="name" format="string" />

2.在GridView的初始化构造函数,通过下面的代码获取到name的值

 private void initAttributes(AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        if (attrs == null) {
            return;
        }
         TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.GridView, defStyleAttr, defStyleRes);
         if (array == null) {
             return;
         }  
         name = array.getString(R.styleable.GridView_name);
    ........
  } 

其中 public final TypedArray obtainStyledAttributes(@Nullable AttributeSet set, @NonNull @StyleableRes int[] attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes)方法可以获取在R.styleable.GridView中定义的各种属性集合。

set:就是该控件定义在布局文件中设置的各种属性,包括系统属性和自定义属性

attrs:需要获取的属性的数组。也就是我们需要获取到布局文件中哪些属性。通常为我们自定义的R.styleable.xxx(如果xxx在定义的时候使用<declare-styleable>声明,则返回的就是一个数组集合,可参见“1.在attrs.xml中声明属性”的介绍)。

剩下的两个参数可与Android自定义View的四种方式中提到的构造函数的四个参数的后两个参数。

3.在不同的地方设置属性值,验证优先级

第一个实例:GridViewAttrTheme1SettingActivity

  • (1)将GridViewAttrTheme1SettingActivity注册在AndroidManifest的主题为:
    <activity
            android:name="com.android.attrsetting.GridViewAttrTheme1SettingActivity"
            android:theme="@style/GridViewTheme" />

该 @style/GridViewTheme代码如下:

    <!--在Activity定义Theme时使用defStyleAttr-->
    <style name="GridViewTheme">
        <item name="initNum">5</item>
<!--Theme中直接赋值-->
        <item name="name">通过在Theme的添加name属性 设置的名字 优先级5</item>
        <item name="GridViewStyle">@style/GridViewStyleInTheme</item>
    </style>

    <style name="GridViewStyleInTheme">
        <item name="initNum">3</item>
<!--在Theme中指定defStyleAttr-->
        <item name="name">通过Theme的defStyleAttr属性 设置的名字 优先级3</item>
    </style>

即在Themem中直接给name属性赋值,也在Theme指定defStyleAttr。该defStyleAttr定义在GridView中的代码如下:

    public GridView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, R.attr.GridViewStyle, 0);
    }
  • (2)在布局文件中添加下面几个GridView的控件

1)第一个GridView控件:在布局文件中设置name的属性值

        <!--优先级为1的情况:在布局文件中定义name属性-->
        <com.android.attrsetting.grid.GridView
            android:id="@+id/gv_test"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="50px"
            psv:horizontalSpacing="50px"
            psv:initNum="1"
            psv:name="直接定义在布局文件中 设置的名字 优先级为1"
            psv:verticalSpacing="50px" />

2)第二个GridView控件:在布局文件中引用style指向该name属性

     <!--优先级为2的情况:在布局文件的style属性中定义name属性-->
        <com.android.attrsetting.grid.GridView
            android:id="@+id/gv_test111"
            style="@style/GridViewInLayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="50px"
            psv:horizontalSpacing="50px"
            psv:initNum="2"
            psv:verticalSpacing="50px" />

对应的@style/GridViewInLayout的代码如下:


    <style name="GridViewInLayout">
        <item name="initNum">2</item>
        <item name="name">通过布局文件引入style,在该style中 设置的名字 优先级为2</item>
    </style>

 3)第三个GridView控件:不在布局文件中设置name属性和style引用指向name属性,仅在Activity的Theme指定defStyleAttr。

       <!--优先级为3的情况:在Theme中通过defStyleAttr属性定义name属性,在控件中使用android:theme不起作用-->
        <com.android.attrsetting.grid.GridView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="50px"
            android:theme="@style/GridViewThemeAttr0"
            psv:horizontalSpacing="50px"
            psv:verticalSpacing="50px" />

        <!--优先级为3的情况:在Theme中通过defStyleAttr属性定义name属性-->
        <com.android.attrsetting.grid.GridView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="50px"
            psv:horizontalSpacing="50px"
            psv:verticalSpacing="50px" />

这两个GridView控件的区别还在于验证在控件中设置android:theme将不起作用。 

  •  (3)在GridView控件给defStyleRes指定的style
       if (defStyleRes <= 0) {
            defStyleRes = R.style.DefaultGridViewStyleRes;
        }
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.GridView, defStyleAttr, defStyleRes);
        if (array == null) {
            return;
        }
        name = array.getString(R.styleable.GridView_name);

其中 R.style.DefaultGridViewStyleRes的代码如下:

    <style name="DefaultGridViewStyleRes">
        <item name="name">自定义UI的默认的styleRes样式中 设置的名字 优先级4</item>
        <item name="initNum">4</item>
    </style>

那么对于第一个GridView控件来说,上述的5种设置属性的方式都存在;第二个GridView控件来说,除去在布局文件中直接对name赋值之外,其他的四种赋值方式都存在;第三个和第四个GridView控件来说,只存在“由defStyleAttr指定的style”、“由defStyleRes指定的style”、“theme中直接赋值”三种赋值方式,那么看下在初始化GridViewAttrTheme1SettingActivity的时候,每个GridView的name属性值为:


2020-11-30 14:45:07.874 25978-25978/com.android.widgetplaceholder V/GridView.initAttributes(L:129):  name = 直接定义在布局文件中 设置的名字 优先级为1
2020-11-30 14:45:07.876 25978-25978/com.android.widgetplaceholder V/GridView.initAttributes(L:129):  name = 通过布局文件引入style,在该style中 设置的名字 优先级为2
2020-11-30 14:45:07.878 25978-25978/com.android.widgetplaceholder V/GridView.initAttributes(L:129):  name = 通过Theme的defStyleAttr属性 设置的名字 优先级3
2020-11-30 14:45:07.880 25978-25978/com.android.widgetplaceholder V/GridView.initAttributes(L:129):  name = 通过Theme的defStyleAttr属性 设置的名字 优先级3

从日志中可以看到:“布局xml赋值” > “布局xml中引用style” > “由defStyleAttr指定的style”。因为只有依次去掉优先级高的设置name属性的方式,才可以得到优先级低点的设置name属性方式的值。在看下另外另种方式的优先级。

第二个实例:GridViewAttrTheme2SettingActivity

  • (1)将GridViewAttrTheme2SettingActivity注册在AndroidManifest的主题为:
 <activity
            android:name="com.android.attrsetting.GridViewAttrTheme2SettingActivity"
            android:theme="@style/GridViewThemeAttr0" />

其中@style/GridViewThemeAttr0的代码如下:

    <!--在Activity定义Theme时无默认的style应用-->
    <style name="GridViewThemeAttr0">
        <item name="initNum">5</item>
        <item name="name">通过在Theme的添加name属性并且没有defStyleAttr 设置的名字 优先级为5</item>
    </style>

该主题中仅有在Theme中直接给name属性赋值。

  • (2)在布局文件中添加GridView控件
    <!--优先级为4/5的情况:在Theme中不定义defStyleAttr属性定义name属性,默认的会使用在控件中定义的defStyleRes中属性-->
    <com.android.attrsetting.grid.GridView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="50px"
        psv:horizontalSpacing="50px"
        psv:verticalSpacing="50px" />

那么对于该控件来说,仅有 “由defStyleRes指定的style”、“theme中直接赋值”两种设置name属性的方式。初始化GridViewAttrTheme2SettingActivity,看下打印出来的日志:

2020-11-30 15:03:05.950 25978-25978/com.android.widgetplaceholder V/GridView.initAttributes(L:129):  name = 自定义UI的默认的styleRes样式中 设置的名字 优先级4

从日志中可以看到  “由defStyleRes指定的style”>“theme中直接赋值”,并且只有在没有设置第一个实例中的三种设置name属性方式的情况下,“由defStyleRes指定的style”才会起作用。

第三个实例: GridViewAttrTheme3SettingActivity

(1)将GridViewAttrTheme3SettingActivity注册在AndroidManifest的主题为:

        <activity
            android:name="com.android.attrsetting.GridViewAttrTheme3SettingActivity"
            android:theme="@style/GridViewThemeAttr1" />

其中@style/GridViewThemeAttr1的代码如下:

    <!--验证优先级5-->
    <style name="GridViewThemeAttr1">
        <item name="initNum">5</item>
        <item name="name">通过在Theme的添加name属性并且没有defStyleAttr 设置的名字 优先级为5</item>
        <item name="GridViewStyle">@style/GridViewStyleNoInTheme</item>
    </style>

    <style name="GridViewStyleNoInTheme">
        <item name="initNum">5</item>
    </style>

 可以看到该Theme中对name直接赋值,同时也设置了由defStyleAttr指定的style,但是style中并没有name属性。

(2)在布局文件中添加GridView控件

    <!--优先级为4/5的情况:在Theme中不定义defStyleAttr属性定义name属性,默认的会使用在控件中定义的defStyleRes中属性-->
    <com.android.attrsetting.grid.GridView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="50px"
        psv:horizontalSpacing="50px"
        psv:verticalSpacing="50px" />

初始化GridViewAttrTheme2SettingActivity,看下打印出来的日志:

2020-11-30 15:03:32.184 25978-25978/com.android.widgetplaceholder V/GridView.initAttributes(L:129):  name = 通过在Theme的添加name属性并且没有defStyleAttr 设置的名字 优先级为5

从打印出来的日志可以看出,如果在自定义控件的时候,如果设置了 defStyleRes,那么要让Theme中设置的name属性起作用,必须在Theme中设置defStyleAttr指定的style,但是style并不对name属性赋值。

三 总结

这种自定义属性的方式可以方便的将控件的属性通过像布局文件、style等方式进行赋值,并且可以随着主题的不同来设置不同的值。那么设置属性的优先级为“布局xml赋值”>“布局xml中引用style”>“由defStyleAttr指定的style”>“由defStyleRes指定的style”>“theme中直接赋值”。另外如果设置了defStyleRes,那么只有在Theme中设置name的属性起作用,必须在Theme中设置defStyleAttr指向style,并且该style中不对属性进行赋值。

另外在本文中涉及的代码已经上传github。相关代码主要为com/android/attrsetting相关代码。

猜你喜欢

转载自blog.csdn.net/nihaomabmt/article/details/109800839
今日推荐