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

引言

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

自定义一个CustomView(extends View 或者 ViewGroup )类

编写values/attrs.xml,在其中编写styleable和attr等标签元素

在布局文件中CustomView使用自定义的属性

在CustomView的构造方法中通过TypedArray获取

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

以上步骤是如何奏效的?

styleable 的含义是什么?可以不写嘛?我自定义属性,我声明属性就好了,为什么一定要写个styleable呢?

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

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

TypedArray是什么鬼?从哪冒出来的,就要我去使用?

自定义属性使用示例

自定义属性的声明文件如下:<?xml version="1.0" encoding="utf-8"?>

复制代码

自定义CustomViewpublic 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();

}

···

}

复制代码

布局文件使用<?xml version="1.0" encoding="utf-8"?>

···

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

">

android:layout_width="match_parent"

android:layout_height="match_parent"

app:testInteger="10086"

app:testText="zeroXuan" />

复制代码

运行结果如下:

07f2b1d13394375a545c69f7e69fcf19.png

注意:我的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);

}

}

复制代码

输出:

9a01611ec3bd56cadcc168685f7fbae9.png

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

TypedArray是什么?

现在简单修改一下布局文件为:

android:layout_width="match_parent"

android:layout_height="match_parent"

app:testInteger="10086"

app:testText="@string/my_name" />

复制代码

解析过程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();

}

复制代码

运行结果

a49e5d6d5d56cabc5558f9d0bbbfcac1.png

通过运行结果可以看出,使用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就会在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数组来获取一系列的属性。

复制代码

获取:

TypedArray typedArray = context.obtainStyledAttributes(set,R.styleable.custom_attrs);

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

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

复制代码

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

obtainStyledAttributes的详细说明

obtainStyledAttributes(int[] attrs):从当前系统主题中获取 attrs 中的属性,最终调用是public TypedArray obtainStyledAttributes(@StyleableRes int[] attrs) {

return mThemeImpl.obtainStyledAttributes(this, null, attrs, 0, 0);

}

复制代码

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);

}

复制代码

obtainStyledAttributes(AttributeSet set, int[] attrs):从 layout 设置的属性中获取 attrs 中的属性。public final TypedArray obtainStyledAttributes(

AttributeSet set, @StyleableRes int[] attrs) {

return getTheme().obtainStyledAttributes(set, attrs, 0, 0);

}

复制代码

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属性赋值时起作用.

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

首先自定义属性<?xml version="1.0" encoding="utf-8"?>

复制代码其中attr_defStyle属性名,就是obtainStyledAttributes中的第三个参数。

定义Style和Theme

@color/colorPrimary

@color/colorPrimaryDark

@color/colorAccent

@style/style_attr_defStyleAttr

testText1-declare in Theme

testText2-declare in Theme

testText3-declare in Theme

testText4-declare in Theme

testText5-declare in Theme

testText1-declare in style_defStyleRes

testText2-declare in style_defStyleRes

testText3-declare in style_defStyleRes

testText4-declare in style_defStyleRes

testText1-declare in style_attr_defStyleAttr

testText2-declare in style_attr_defStyleAttr

testText3-declare in style_attr_defStyleAttr

testText1-declare in style_CustomViewStyle

testText2-declare in style_CustomViewStyle

复制代码

自定义Viewpublic 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();

}

}

复制代码

布局界面

style="@style/style_CustomViewStyle"

android:layout_width="match_parent"

android:layout_height="match_parent"

app:testText1="Direct declare in XML" />

复制代码

运行结果

404bac7b31920898a32a8059abb17b03.png

调用顺序

优先,取在布局中给定的值

次之,取在布局中设置的style中的值

其次,从defStyleAttr和defStyleRes中取值,注意如果 defStyleAttr有值,则不再去defStyleResult中的值,就算defStyleAttr有的属性没有赋值。(具体看上面的打印结果)

最后使用,Theme中设置的属性

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

@ 和?符号的引用区别

@ 与 ? 符号的引用在使用时都有一个规范的格式:“@[+][package:]type:name”,“?[package:][type:]name”。可以看到,二者均包含引用符号、资源所属的包、资源类型和资源名称。

@

@ 符号用于引用系统和我们在项目中添加的一些固有资源(drawable,string 等),或者定义的 style 样式。比如:

android:text="@string/app_name"

复制代码

这里的 app_name 就是我们自己定义在项目文件 values/strings.xml 中的字符串资源。

android:text="@android:string/cancel"

复制代码

而这里的cancel属于Android SDK中的系统字符串资源,所以需要添加 @android: 来指明引用来源。android: 是 package:的一个具体实例。

?

?符号用于引用当前主题中定义的一些属性值。

注意,? 符号通过属性名字间接引用当前主题中的对应属性值,而不是属性本身。

举个例子:

android:divider="?android:listDivider"

复制代码

这里的 ? 符号通过属性名 android:listDivider 间接获取当前主题赋予该属性的值。如同 @android: 一般,?android: 表示该值源自 Android SDK 系统属性。由于在当前主题中寻找对应属性名的值,所以没有指定属性类型,其实等同于:?android:attr/listDivider。

那如何引用项目中自定义的属性呢?

我们在 attrs.xml 中定义一个属性,如:

复制代码

显然,此时我们定义的 colorTextCustom 属性是没有值的,直接引用没有任何作用。需要在主题 style 中赋值:

#FF0000

?colorTextCustom

复制代码

可以看到,这里在BaseTheme中对colorTextCustom属性赋值,并在 AppTheme中通过“?colorTextCustom” 引用该属性值。由于是本地项目中定义的属性,所以没有添加 android: 命名空间。其实,这种做法的好处是,AppTheme所覆盖的 View 均可通过构造函数获取当前主题中的 colorTextCustom 属性值。

?引用属性写法:

引用自定义属性:?attr/colorPrimary 简写:?colorPrimary

引用系统属性:?android:attr/colorPrimary 简写:?android:colorPrimary

其中,自定义属性会复写系统属性

目录结构

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值