Android 自定义View:深入理解自定义属性(七)

栏目: IOS · Android · 发布时间: 5年前

内容简介:对于自定义属性,遵循以下几步,就可以实现:那么,我有几个问题,如果回答的很好,下面的文章就不用看了,可以跳过:构造方法中的有个参数叫做

对于自定义属性,遵循以下几步,就可以实现:

  1. 自定义一个 CustomView (extends View 或者 ViewGroup )类
  2. 编写 values/attrs.xml ,在其中编写 styleableattr 等标签元素
  3. 在布局文件中 CustomView 使用自定义的属性
  4. 在CustomView的构造方法中通过TypedArray获取

那么,我有几个问题,如果回答的很好,下面的文章就不用看了,可以跳过:

  • 以上步骤是如何奏效的?
  • styleable 的含义是什么?可以不写嘛?我自定义属性,我声明属性就好了,为什么一定要写个styleable呢?
  • 如果系统中已经有了语义比较明确的属性,我可以直接使用嘛?
  • 构造方法中的有个参数叫做 AttributeSet (eg: CustomView(Context context, AttributeSet attrs) )这个参数看名字就知道包含的是参数的数组,那么我能不能通过它去获取我的自定义属性呢?
  • TypedArray 是什么鬼?从哪冒出来的,就要我去使用?

自定义属性使用示例

  1. 自定义属性的声明文件如下:
    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <declare-styleable name="CustomViewTest">
            <attr name="testText" format="string"/>
            <attr name="testInteger" format="integer"/>
        </declare-styleable>
    </resources>
    复制代码
  2. 自定义CustomView
    public class CustomView extends View {
        ···
    
        public CustomView(Context context, AttributeSet attrs) {
            super(context, attrs);
            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomViewTest);
    
            String testText = a.getString(R.styleable.CustomViewTest_testText);
            int testInteger = a.getInteger(R.styleable.CustomViewTest_testInteger, 10);
    
            Log.e(TAG, "testText =" + testText + " ,testInteger=" + testInteger);
            a.recycle();
        }
    
       ···
    }
    复制代码
  3. 布局文件使用
    <?xml version="1.0" encoding="utf-8"?>
    <android.support.constraint.ConstraintLayout 
    ···
    <!-- 自动查找属性  -->
    xmlns:app="http://schemas.android.com/apk/res-auto"
    ">
    
    <com.zeroxuan.customviewtest.CustomView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:testInteger="10086"
        app:testText="zeroXuan" />
    </android.support.constraint.ConstraintLayout>
    复制代码
  4. 运行结果如下:
    Android 自定义View:深入理解自定义属性(七)

注意:我的styleable的name写的是CustomViewTest,所以说这里并不要求一定是自定义View的名字。

AttributeSet 与 TypedArray

构造方法中的有个参数叫做 AttributeSet (eg: MyTextView(Context context, AttributeSet attrs) )这个参数看名字就知道包含的是参数的集合,那么我能不能通过它去获取我的自定义属性呢?

首先 AttributeSet 中的确保存的是该 View 声明的所有的属性,并且外面的确可以通过它去获取(自定义的)属性,怎么做呢?如下:

public CustomView(Context context, AttributeSet attrs) {
        super(context, attrs);

        final int count = attrs.getAttributeCount();
        for (int i = 0; i < count; i++) {
            String attrName = attrs.getAttributeName(i);
            String attrValue = attrs.getAttributeValue(i);
            Log.e(TAG, "attrName= " + attrName + " ,attrValue=" + attrValue);
        }
    }
复制代码

输出:

Android 自定义View:深入理解自定义属性(七)

咦,真的可以获得所有的属性。通过 AttributeSet 可以获得布局文件中定义的所有属性的key和value,那么是不是说 TypedArray 就可以抛弃了呢?答案是: NO,NO,No ,重要的事,说三遍!。

TypedArray是什么?

  1. 现在简单修改一下布局文件为:
    <com.zeroxuan.customviewtest.CustomView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:testInteger="10086"
        app:testText="@string/my_name" />
    复制代码
  2. 解析过程
    public CustomView(Context context, AttributeSet attrs) {
        super(context, attrs);
    
        final int count = attrs.getAttributeCount();
        for (int i = 0; i < count; i++) {
            String attrName = attrs.getAttributeName(i);
            String attrValue = attrs.getAttributeValue(i);
            Log.e(TAG, "attrName= " + attrName + " ,attrValue=" + attrValue);
        }
    
        Log.e(TAG, ">> Use TypedArray");
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomViewTest);
    
        String testText = a.getString(R.styleable.CustomViewTest_testText);
        int testInteger = a.getInteger(R.styleable.CustomViewTest_testInteger, 10);
    
        Log.e(TAG, "testText =" + testText + " ,testInteger=" + testInteger);
        a.recycle();
    }
    复制代码
  3. 运行结果
    Android 自定义View:深入理解自定义属性(七)

通过运行结果可以看出,使用 AttributeSet 获取的值,如果是引用都变成了 @+数字 的字符串。你说,这玩意你能看懂么?那么你看看最后一行使用TypedArray获取的值,是不是瞬间明白了。

TypedArray 其实就是用来简化我们解析自定义属性工作的 。比如上例,如果布局中的属性的值是引用类型,如果使用AttributeSet去获得最终的 testText 取值,那么需要第一步拿到id,第二步再去解析id。 而TypedArray正是帮我们简化了这个过程

如果通过AttributeSet获取最终的 testText 取值的过程如下:

//使用索引 3 ,是因为testText在CustomView中的索引是3
int resId=attrs.getAttributeResourceValue(3,-1);

Log.e(TAG, "attrName= "+getResources().getString(resId) );
复制代码

ok,现在别人问你TypedArray存在的意义,你就可以告诉他, TypedArray 其实就是用来简化解析自定义属性工作流程的

attr 和 declare-styleable的关系

首先要明确一点, attr 不依赖于 declare-styleable , declare-styleable 只是为了方便 attr 的使用。

我们自己定义的属性完全可以不放到 declare-styleable 里面,比如直接在resources文件中定义一些属性:

<attr name="custom_attr1" format="string" />
<attr name="custom_attr2" format="string" />
复制代码

定义一个 attr 就会在R文件里面生成一个 attr 类型的资源 Id ,那么我们去获取这个属性时,必须调用如下代码:

int[] custom_attrs = {R.attr.custom_attr1,R.custom_attr2};
TypedArray typedArray = context.obtainStyledAttributes(set,custom_attrs);
复制代码

而通过定义一个 declare-styleable ,我们可以在R文件里自动生成一个 int[] ,数组里面的 int 就是定义在 declare-styleable 里面的 attr的id 。所以我们在获取属性的时候就可以直接使用 declare-styleable 数组来获取一系列的属性。

<declare-styleable name="custom_attrs">   
    <attr name="custom_attr1" format="string" />
    <attr name="custom_attr2" format="string" />
</declare-styleable>
复制代码

获取:

TypedArray typedArray = context.obtainStyledAttributes(set,R.styleable.custom_attrs);
复制代码

如果系统中已经有了语义比较明确的属性,我可以直接使用嘛?

答案是肯定的,可以使用,使用方式如下:

<declare-styleable name="test">
  <!-- 使用系统属性或者已经定义好的属性,不需要去添加format属性 -->
  <attr name="android:text" />

  <attr name="testAttr" format="integer" />
</declare-styleable>
复制代码

然后在类中这么获取: a.getString(R.styleable.CustomViewTest_android_text); 布局文件中直接 android:text="zeroXuan is my name" 即可。

obtainStyledAttributes的详细说明

  1. obtainStyledAttributes(int[] attrs):从当前系统主题中获取 attrs 中的属性,最终调用是
    public TypedArray obtainStyledAttributes(@StyleableRes int[] attrs) {
            return mThemeImpl.obtainStyledAttributes(this, null, attrs, 0, 0);
        }
    复制代码
  2. obtainStyledAttributes(int resid, int[] attrs):从资源文件中获取 attrs 中的属性。
    public TypedArray obtainStyledAttributes(@StyleRes int resId, @StyleableRes int[] attrs)
                throws NotFoundException {
            return mThemeImpl.obtainStyledAttributes(this, null, attrs, 0, resId);
        }
    复制代码
  3. obtainStyledAttributes(AttributeSet set, int[] attrs):从 layout 设置的属性中获取 attrs 中的属性。
    public final TypedArray obtainStyledAttributes(
            AttributeSet set, @StyleableRes int[] attrs) {
        return getTheme().obtainStyledAttributes(set, attrs, 0, 0);
    }
    复制代码
  4. obtainStyledAttributes(AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes):下面细说。
    public final TypedArray obtainStyledAttributes(
            AttributeSet set, @StyleableRes int[] attrs, @AttrRes int defStyleAttr,
            @StyleRes int defStyleRes) {
        return getTheme().obtainStyledAttributes(
            set, attrs, defStyleAttr, defStyleRes);
    }
    复制代码

可以看出最终都是调用方法4,现在主要分析方法 obtainStyledAttributes(AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes) ,其中这个方法的四个参数解释如下:

  • AttributeSet set: 一个和xml中的标签关联的存放属性的集合.
  • int[] attrs: 我们要在xml中读取的属性.
  • int defStyleAttr: 这是当前Theme中的包含的 一个指向style的引用 .当我们没有给自定义View设置declare-styleable资源集合时,默认从这个集合里面查找布局文件中配置属性值.传入0表示不向该defStyleAttr中查找默认值.
  • int defStyleRes: 这个也是 一个指向Style的资源ID ,但是仅在defStyleAttr为0或者defStyleAttr不为0但Theme中没有为defStyleAttr属性赋值时起作用.

这么说可能有点迷糊,来一个例子希望你能立马领悟!

  1. 首先自定义属性
    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <declare-styleable name="CustomView">
            <attr name="testText1" format="string"/>
            <attr name="testText2" format="string"/>
            <attr name="testText3" format="string"/>
            <attr name="testText4" format="string"/>
            <attr name="testText5" format="string"/>
            <attr name="attr_defStyle" format="reference"/>
        </declare-styleable>
    </resources>
    复制代码
    其中 attr_defStyle 属性名,就是 obtainStyledAttributes 中的第三个参数。
  2. 定义 StyleTheme
    <resources>
    
        <!-- Base application theme. -->
        <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
            <!-- Customize your theme here. -->
            <item name="colorPrimary">@color/colorPrimary</item>
            <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
            <item name="colorAccent">@color/colorAccent</item>
            <item name="attr_defStyle">@style/style_attr_defStyleAttr</item>
            <item name="testText1">testText1-declare in Theme</item>
            <item name="testText2">testText2-declare in Theme</item>
            <item name="testText3">testText3-declare in Theme</item>
            <item name="testText4">testText4-declare in Theme</item>
            <item name="testText5">testText5-declare in Theme</item>
        </style>
    
        <!-- 用来表示 defStyleRes-->
        <style name="style_defStyleRes">
            <item name="testText1">testText1-declare in style_defStyleRes</item>
            <item name="testText2">testText2-declare in style_defStyleRes</item>
            <item name="testText3">testText3-declare in style_defStyleRes</item>
            <item name="testText4">testText4-declare in style_defStyleRes</item>
        </style>
    
        <!-- 用来表示 attr_defStyleAttr 这个属性的值 -->
        <style name="style_attr_defStyleAttr">
            <item name="testText1">testText1-declare in style_attr_defStyleAttr</item>
            <item name="testText2">testText2-declare in style_attr_defStyleAttr</item>
            <item name="testText3">testText3-declare in style_attr_defStyleAttr</item>
        </style>
    
        <!--  直接在布局中的 style 中使用  -->
        <style name="style_CustomViewStyle">
            <item name="testText1">testText1-declare in style_CustomViewStyle</item>
            <item name="testText2">testText2-declare in style_CustomViewStyle</item>
        </style>
    
    </resources>
    复制代码
  3. 自定义View
    public class CustomView extends View {
        private static final String TAG = "CustomView";
    
        public CustomView(Context context) {
            this(context, null);
        }
    
        public CustomView(Context context, AttributeSet attrs) {
            //R.attr.attr_defStyle 就是defStyleRes
            this(context, attrs, R.attr.attr_defStyle);
        }
    
        public CustomView(Context context, AttributeSet attrs,
                          int defStyleAttr) {
            this(context, attrs, defStyleAttr, R.style.style_defStyleRes);
        }
    
        @TargetApi(21)
        public CustomView(Context context, AttributeSet attrs,
                          int defStyleAttr, int defStyleRes) {
            super(context, attrs, defStyleAttr, defStyleRes);
            TypedArray a = context
                    .obtainStyledAttributes(attrs, R.styleable.CustomView, defStyleAttr, defStyleRes);
    
            String text1 = a.getString(R.styleable.CustomView_testText1);
            String text2 = a.getString(R.styleable.CustomView_testText2);
            String text3 = a.getString(R.styleable.CustomView_testText3);
            String text4 = a.getString(R.styleable.CustomView_testText4);
            String text5 = a.getString(R.styleable.CustomView_testText5);
    
            Log.e(TAG, "text1== " + text1);
            Log.e(TAG, "text2== " + text2);
            Log.e(TAG, "text3== " + text3);
            Log.e(TAG, "text4== " + text4);
            Log.e(TAG, "text5== " + text5);
            a.recycle();
        }
    }
    复制代码
  4. 布局界面
    <com.zeroxuan.customviewtest.CustomView
        style="@style/style_CustomViewStyle"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:testText1="Direct declare in XML" />
    复制代码
  5. 运行结果
    Android 自定义View:深入理解自定义属性(七)

调用顺序

优先
次之
其次
最后使

注意 defStyleAttr的值一定要在Theme中设置才有效果,就拿上面的例子说,如果你没有在Theme中给R.attr.attr_defStyle赋值,而是直接在布局文件中赋值,这样做是没有效果的。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

企业IT架构转型之道:阿里巴巴中台战略思想与架构实战

企业IT架构转型之道:阿里巴巴中台战略思想与架构实战

钟华 / 机械工业出版社 / 2017-4-1 / 79

在当今整个中国社会都处于互联网转型的浪潮中,不管是政府职能单位、业务规模庞大的央企,还是面临最激烈竞争的零售行业都处于一个重要的转折点,这个转折对企业业务模式带来了冲击,当然也给企业的信息中心部门带来了挑战:如何构建IT系统架构更好地满足互联网时代下企业业务发展的需要。阿里巴巴的共享服务理念以及企业级互联网架构建设的思路,给这些企业带来了不少新的思路,这也是我最终决定写这本书的最主要原因。本书从阿......一起来看看 《企业IT架构转型之道:阿里巴巴中台战略思想与架构实战》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

URL 编码/解码
URL 编码/解码

URL 编码/解码