欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android obtainStyledAttributes获取属性值

程序员文章站 2022-05-29 21:06:25
...

obtainStyledAttributes是干什么的

有过自定义属性或者查看过系统View相关子类源码的人可能对这个方法都不会陌生。
该方法是Context类为我们提供的获取style中特定属性值的方法。通过这个方法,我们就可以获取在style中定义的各种属性值,然后根据获取到的不同的属性值实现差异化的效果。

一种典型的使用方式是:

        //TextView 构造方法片选代码

        //首先通过obtainStyledAttributes获取TypedArray
        TypedArray a = theme.obtainStyledAttributes(attrs,
                com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes); 
        //接着从TypedArray中提取相应的值,此处提取的是resourceId,对于的attr声明应为“reference”        
        //除此之外,TypedArray还有getInt,getDrawable等一系列方法用于提取其他类型的值。
        int ap = a.getResourceId(
                com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1);

        //回收TypedArray
        a.recycle();

从以上代码片看到调用obtainStyledAttributes的变量是theme,其实是因为context中的该方法最终也是调用其持有的theme对象的该方法,因此是一致的。如下:

    //Context obtainStyledAttributes实现
    public final TypedArray obtainStyledAttributes(
            AttributeSet set, @StyleableRes int[] attrs, @AttrRes int defStyleAttr,
            @StyleRes int defStyleRes) {
        return getTheme().obtainStyledAttributes(
            set, attrs, defStyleAttr, defStyleRes);
    }

既然如此,我们就直接分析Theme中的obtainStyledAttributes方法。

obtainStyledAttributes API解析

Android obtainStyledAttributes获取属性值

以上是文档中对该方法的说明。我们先从参数部分看。该方法总共有四个参数:

参数名 说明
set 直接内嵌在View中的属性集合,如: <Button textColor=”#ff000000”>。
同时还包括通过style=“@style/xxxx”引入的属性,如:<Button style=”@style/xxxx”>
attrs 想要获取的属性集合,通常我们在声明属性时可以将之作为styleable的孩子,那么此时就可以直接使用如TextView中的方式引用那个styleable资源就行,如:R.styleable.TextViewAppearance
但有时由只想获取某一个或者几个属性的话,就应该手动构建int数组了,如:new int[]{R.attr.xxx, ...}
defStyleAttr 指定默认引用的属性,该属性应是一个指向某个style的引用。注意注释中的说明,该属性的值是从theme中提取的,如果该属性是包含在第一个参数“set”中的(使用内联方式指定或者style嵌入方式指定到元素标签内),那么会获取不到该值,因此只能够在theme中定义该属性的值
当theme中存在该属性的定义时,系统会将该属性指向的style内容引入进当前的内容作为属性集提供检索,而当不提供该参数时,系统是不会将Theme中那些指向style的属性内容展开的。
defStyleRes 当defStyleAttr=0或者Theme中未定义defStyleAttr属性时使用该处指向的style提供默认的属性集,比起defStyleAttr,该参数因为指定确定的style而缺乏灵活性,但其保证了组件中某些必须要获取到的属性值不会不存在。



总的来说,这个方法就在从一堆属性中提取自己需要的属性。这一堆属性的来源依据说明文档中的说法是有序的从以下地方提取的:

When determining the final value of a particular attribute, there are four inputs that come into play:

  1. Any attribute values in the given AttributeSet. ——参数set
  2. The style resource specified in the AttributeSet (named “style”). ——参数set
  3. The default style specified by defStyleAttr and defStyleRes. ——参数defStyleAttr和defStyleRes
  4. The base values in this theme. ——theme

而我们需要获取的属性就由int[]型的参数attrs指定。

这样,弄清了系统组件是如何获取属性的,我们就可以有针对性的修改属性来定义不同的效果了。比如修改默认的EditText获得焦点是底部线条的颜色。

修改默认的EditText获得焦点时底部线条的颜色

有时候我们只是想简单的修改获得焦点是EditText的颜色(比如使用红色提示输入格式不正确)而不想全部将EditText的background替换成自己写的selector(背景选择器),有没有简单的方法呢?当然有!

首先,我们查看系统默认的EditText的背景是个什么,我们才能有针对性的修改。
查看EditText源码发现其构造方法中指定了默认style为com.android.internal.R.attr.editTextStyle,即为android:editTextStyle

    //在inflate xml布局文件过程中,一般使用该构造方法
    public EditText(Context context, AttributeSet attrs) {
        this(context, attrs, com.android.internal.R.attr.editTextStyle);
    }

既然如此,那么我们就看看我们使用的Theme中定义的android:editTextStyle属性值是什么吧。

SDK/platforms/android-25/data/res/values/themes.xml中发现如下:

<item name=”editTextStyle”>@style/Widget.EditText</item>

再查看style/Widget.EditText,发现其中background属性使用的是?attr/editTextBackground

    <style name="Widget.EditText">
        <item name="focusable">true</item>
        <item name="focusableInTouchMode">true</item>
        <item name="clickable">true</item>
        <item name="background">?attr/editTextBackground</item>
        <item name="textAppearance">?attr/textAppearanceMediumInverse</item>
        <item name="textColor">?attr/editTextColor</item>
        <item name="gravity">center_vertical</item>
        <item name="breakStrategy">simple</item>
        <item name="hyphenationFrequency">normal</item>
    </style>

于是折回theme中查找editTextBackground,找到为:

<item name=”editTextBackground”>@drawable/edit_text_material</item>

那么drawable/edit_text又是什么呢,继续……

<!--SDK/platforms/android-25/data/res/drawable/edit_text_material.xml-->

<inset xmlns:android="http://schemas.android.com/apk/res/android"
       android:insetLeft="@dimen/edit_text_inset_horizontal_material"
       android:insetRight="@dimen/edit_text_inset_horizontal_material"
       android:insetTop="@dimen/edit_text_inset_top_material"
       android:insetBottom="@dimen/edit_text_inset_bottom_material">
    <selector>
        <item android:state_enabled="false">
            <nine-patch android:src="@drawable/textfield_default_mtrl_alpha"
                android:tint="?attr/colorControlNormal" />
        </item>
        <item android:state_pressed="false" android:state_focused="false">
            <nine-patch android:src="@drawable/textfield_default_mtrl_alpha"
                android:tint="?attr/colorControlNormal" />
        </item>
        <item>
            <nine-patch android:src="@drawable/textfield_activated_mtrl_alpha"
                android:tint="?attr/colorControlActivated" />
        </item>
    </selector>
</inset>

可以看到其中默认状态时(也是选择**状态)的背景设置为一个nine-patch并设置了tint(染色)属性为?attr/colorControlActivated

于是乎,我们打可以使用android:colorControlActivated这个属性来设置选择时线条的颜色。因此在Theme中定义

<item name=”android:colorControlActivated”>#ff0000</item>

同理,可以设置未选中状态的颜色通过定义colorControlNormal

后记

注意!!!
以上edit_text_material中tint的使用方式为引用系统属性colorControlActivated,因此不能通过<EditText android:colorControlActivated="#ff0000">来实现,只能在Theme中指定该属性!

那这就引出另一个问题。我们在Theme中修改该系统属性值可能也在被其他组件使用。岂不是都会被影响到?确实是这样的,但是这是有解决办法的。可以使用ContextThemeWrapper来解决,使该属性的定义只影响某个或者某些元素。具体使用方式可以百度ContextThemeWrapper或参见本人博客Android ContextThemeWrapper应用