前言
自定义属性大多数都是在自定义View的时候使用到。同时自定义属性为自定义View带来很大的便利,在使用自定义View时,只需在xml配置相应View属性,那么就无需使用过多的方法去出来,更加安全,更加有效率。自定义属性顾名思义,就是在自定义View所拥有的基础上开发者自行拓展。
属性作用
Android属性是一种属性约束,主要用来约束具体属性字段的数据类型(共有11种)。简而言之Android属性就是用来约束属性的数据类型。如下图表11种类型,具体使用方式将在下文format属性介绍将会依依解答。
属性数据类型 | 类型说明 |
---|---|
reference | 引用资源类型,即参考某一资源ID |
string | 字符串类型,主要用于字符内容 |
color | 颜色值类型,主要用户与颜色相关数据 |
boolean | 布尔值 |
dimension | 尺寸值 |
float | 浮点值 |
integer | 整型值 |
fraction | 百分数 |
enum | 枚举值(枚举类型的属性在使用的过程中只能同时使用其中一个 ) |
flag | 位或运算(位运算类型的属性在使用的过程中可以使用多个值 ) |
混合类型 | 属性定义时可以指定多种类型值 |
属性声明方式
在介绍Android属性能够约束的数据类型前,我们先简单阐述一下属性的声明方式,因为接下来在说明数据类型的同时将会严格按照声明方式的形式例举。说道Android属性的声明方式主要有两种,这两种方式主要依赖attr标签后面是否有format属性,如果标签后有format的就是在定义属性,相反没有format的就是在使用已有的属性。
属性命名规则
<resources>
//此处声明declare-styleable的名称,该名称规则与变量名规则一样。
<declare-styleable name="StylName">
//一下为需要定义的具体属性名称和属性的数据类型,在一个declare-styleable中可以定多个属性;
//1、对于属性的名称没有具体要求,但明规则同变量名规则一样;
//2、虽然属性明没有具体要求,但对于数据数据类型只能使用允许的11种类型
<attr name="attrName1" format="string" />
<attr name="attrName2" format="color" />
<attr name="attrName3" format="boolean" />
...
<attr name="attrNameN" format="float" />
</declare-styleable>
</resources>
属性声明方式
//第一种属性声明方式,attr标签后有format
//大多数是因为需要重新定义一个全新的属性,才会使用该方式
<resources>
<declare-styleable name="CustView">
<attr name="text" format="string" />
</declare-styleable>
</resources>
//第二种属性声明方式,attr标签后无format,采用系统已定义的属性
//使用该方式,前提是一定要系统已经定义过该名称为text的属性,我们无需再定义,只需要在自定义属性中申明;
//使用系统这个text属性,注意一定要加上android命名空间,这样才知道使用的是系统的text属性;
<resources>
<declare-styleable name="CustView">
<attr name="android:text"/>
</declare-styleable>
</resources>
//第三种属性声明方式,attr标签后无format,采用自定义的属性
//也就是想定义一个公共属性供多个declare-styleable使用
//这种方式,需要我们在declare-stylable之前,提前申明要多次使用的属性,然后在declare-stylable内部,即可直接使用
<resources>
...
<attr name="attrName1" format="string" />
<attr name="attrName2" format="boolean"/>
...
<declare-styleable name="CustView1">
<attr name="attrName1"/>
<attr name="attrName2"/>
</declare-styleable>
<declare-styleable name="CustView2">
<attr name="attrName1"/>
<attr name="attrName2"/>
</declare-styleable>
</resources>
注意,大家可能会觉的很奇怪,命名前面说声明方式只有两种,为什么到了这里就变成三种方式了,其实第二种和第三种同属一种
方式,只是第二种和第三种对于数据源获取方式不同,一个是采用系统的,另一个是采用自定义的。所以合并为一种方式,此处为了怕
混淆,所以分开来说明。
format数据类型介绍
以下将依次介绍format的数据类型:
1、reference:引用资源类型,即参考某一资源ID
属性定义:
<declare-styleable name="名称">
<attr name="background" format="reference" />
</declare-styleable>
使用
<ImageView
android:background="@drawable/图片ID"/>
2、string:字符串类型
属性定义:
<declare-styleable name="名称">
<attr name="text" format="string" />
</declare-styleable>
使用
<TextView
android:text="字符串类型"/>
3、color:颜色值类型,主要用户与颜色相关数据
属性定义:
<declare-styleable name="名称">
<attr name="textColor" format="color" />
</declare-styleable>
使用
<TextView
android:textColor="#FFFFFF" />
4、boolean:布尔值
属性定义:
<declare-styleable name="名称">
<attr name="state" format="boolean" />
</declare-styleable>
使用
<Button
android:state="true"/>
5、dimension:尺寸值
属性定义:
<declare-styleable name="名称">
<attr name="layout_width" format="dimension" />
</declare-styleable>
使用
<Button
android:layout_width="42dp"/>
6、float:浮点值
属性定义:
<declare-styleable name="名称">
<attr name="fromAlpha" format="float" />
</declare-styleable>
使用
<alpha
android:fromAlpha = "1.0"/>
7、integer:整型值
属性定义:
<declare-styleable name="名称">
<attr name="framesCount" format="integer" />
</declare-styleable>
使用
<animated-rotate
android:framesCount = "12"/>
8、fraction:百分数
属性定义:
<declare-styleable name="名称">
<attr name="pivotX" format="fraction" />
</declare-styleable>
使用
<rotate
android:pivotX = "200%"/>
9、enum:枚举值
属性定义:
<declare-styleable name="名称">
<attr name="orientation">
<enum name="horizontal" value="0" />
<enum name="vertical" value="1" />
</attr>
</declare-styleable>
使用
<LinearLayout
android:orientation = "vertical" />
注意:枚举类型的属性在使用的过程中只能同时使用其中一个
10、flag:位或运算
属性定义:
<declare-styleable name="名称">
<attr name="gravity">
<flag name="top" value="0x01" />
<flag name="bottom" value="0x02" />
<flag name="left" value="0x04" />
<flag name="right" value="0x08" />
<flag name="center_vertical" value="0x16" />
...
</attr>
</declare-styleable>
使用
<TextView
android:gravity="bottom|left"/>
注意:位运算类型的属性在使用的过程中可以使用多个值
11、混合类型:属性定义时可以指定多种类型值
属性定义:
<declare-styleable name="名称">
<attr name="background" format="reference|color" />
</declare-styleable>
使用
<ImageView
android:background = "@drawable/图片ID" />
或者:
<ImageView
android:background = "#00FF00" />
xml的命名空间
xmlns的定义:它是 XML 文档中的一个概念,即命名空间 。
xmlns的作用:xmlns也是为了解决 XML 中元素和属性命名冲突。(在 XML 中,元素名称是由开发者定义的,当两个不同的文档使用相同的元素名时,就会发生命名冲突),xml的命名空间主要在布局(xml)中使用中使用到。
XML命名空间定义语法为:xmlns:namespace-prefix="namespaceURI"
xmlns:声明命名空间的保留字,即XML中元素的一个属性;
namespace-prefix:命名空间的前缀,这个前缀与某个命名空间相关联;
namespaceURI:命名空间的唯一标识符,一般就是一个URI引用。
方式一:
XML命名规则为:xmlns:前缀=http://schemas.android.com/apk/res/你的应用程序包路径
方式二:
XML命名规则为:xmlns:前缀=http://schemas.android.com/apk/res-auto
前缀:该名称没有特定限制,名称定义严格遵守java变量名格式
URL:在Android中该部分是http://schemas.android.com/+类别+资源路径
备注: 1、当我们需要访问app中res下所有资源的能力时,就必须使用方式二;
2、 如果只需访问app中某个res资源,那么方式一和方式二都可以,但从安全来说,方式一更为稳妥;
注意:如果你当前工程是做为lib使用,那么你还是照"http://schemas.android.com/apk/res/你的应用程序包路径"写 ,会出现找不到自定义属性的错误 ,这时URL需要使用"http://schemas.android.com/apk/res-auto"
AttributeSet
通过Attributeset名字就知道是一个属性的集合,实际上它内部就是一个XML解析器,这一点可以通过查看源码中的解释证实,通过XmlPullParser将布局文件中该控件的所有属性解析出来,且以键值对(key-value)的形式保存起来。通过AttributeSet获取属性值的几个方法:
属性例子:
<resources>
<declare-styleable name="CustViewStye">
<attr name="attr1" format="string"
<attr name="android:text" />
</declare-styleable>
</resources>
方法一:
既然Attributeset本身就是一个键值对的属性集合,那么理应就可以一个一个将里面的值取出来
public CustView(Context context, AttributeSet attrs) {
super(context, attrs, defStyleAttr);
int count = attrs.getAttributeCount();
for (int i = 0; i < count; i++) {
String attrName = attrs.getAttributeName(i);//此处为属性名
String attrVal = attrs.getAttributeValue(i);//此处为属性名对于的属性值;
}
}
注意:通过该方式,只能够获取String、int、boolean、float数据类型,如果在布局中使用了资源ID,那么获取到的值将是这个ID值,
所以如果是除String、int、boolean、float数据类型此方法都将不适用,而采用资源ID的形式还需进一步处理,带来了极大的不便,因此该
方法要慎用。
方法二:
该方法通过ResourcesImpl类的obtainStyledAttributes将属性集AttributeSet转为TypedArray对象,然后通过TypedArray对象获取
相应的属性值
public CustView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs);
TypedArray typeArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustViewStye,
defStyleAttr, 0);
String attr1 = typeArray.getString(R.styleable.CustViewStye_attr1);
String text = typeArray.getString(R.styleable.CustViewStye_android_text);
}
TypedArray
从AttributeSet的第二个获取属性值的方法来看,TypedArray提供了获取不同类型属性的方法,可直接得到我们想要的数据类型,而无需再Attributeset获取属性后还要一个个处理才能得到具体的数据。因此TypedArray是为我们获取属性值提供了方便
,简化了获取属性值的流程。如下TypedArray获取方式的源码:
方式一:
/**
* Obtains styled attributes from the theme, if available, or unstyled
* resources if the theme is null.
*
* @hide
*/
public static TypedArray obtainAttributes(
Resources res, Theme theme, AttributeSet set, int[] attrs) {
if (theme == null) {
return res.obtainAttributes(set, attrs);
}
return theme.obtainStyledAttributes(set, attrs, 0, 0);
}
方式二:
public TypedArray obtainStyledAttributes(@StyleableRes int[] attrs) {
return mThemeImpl.obtainStyledAttributes(this, null, attrs, 0, 0);
}
方式三:
public TypedArray obtainStyledAttributes(@StyleRes int resId, @StyleableRes int[] attrs)
throws NotFoundException {
return mThemeImpl.obtainStyledAttributes(this, null, attrs, 0, resId);
}
方式四:
public TypedArray obtainStyledAttributes(AttributeSet set,@StyleableRes int[] attrs,
@AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
return mThemeImpl.obtainStyledAttributes(this, set, attrs, defStyleAttr, defStyleRes);
}
set:属性值的集合。
attrs:我们自定义属性集合在R类中生成的int型数组,这个数组中包含了自定义属性的资源ID。
defStyleAttr:这是当前Theme中的包含的一个指向style的引用.当我们没有给自定义View设置declare-styleable资源集合时,
默认从这个集合里面查找布局文件中配置属性值,传入0表示不向该defStyleAttr中查找默认值。
defStyleRes:这个也是一个指向Style的资源ID,但是仅在defStyleAttr为0或者defStyleAttr不为0但Theme中没有为
defStyleAttr属性赋值时起作用。
TypedArray obtainStyledAttributes(@NonNull Resources.Theme wrapper,
AttributeSet set,
@StyleableRes int[] attrs,
@AttrRes int defStyleAttr,
@StyleRes int defStyleRes)
public TypedArray obtainAttributes(AttributeSet set, @StyleableRes int[] attrs)
因此,在某种程度上TypedArray对于属性获取属性值来说是并不可缺少的。同时一定要注意TypedArray使用完一定要回收,否则会造成内存泄漏。
declare-styleable
declare-styleable的作用就是想每个控件的属性分组,并记录属性的索引值(注意,实际上在定义属性的时候是可以不需要declare-styleable的
)。
1、有declare-styleable方式
<resources>
<declare-styleable name="CustViewStye">
<attr name="attr1" format="string" />
</declare-styleable>
</resources>
获取属性值:
public CustView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs);
TypedArray typeArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustViewStye,
defStyleAttr, 0);
String attr1 = typeArray.getString(R.styleable.CustViewStye_attr1);
typeArray.recycle();
}
2、无declare-styleable方式
<resources>
<attr name="attr1" format="string" />
</resources>
获取属性值:
private static final int[] mAttr = { android.R.attr.attr1}; //定义属性组
private static final int ATTR_1 = 0;//定义属性索引
public MyTextView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray typeArray = context.obtainStyledAttributes(attrs, mAttr);
String text = typeArray.getString(ATTR_1);
typeArray.recycle();
}
通过以上例子,可以发现,当简化了属性定义,那么获取属性值的方式将会变繁琐。
如何自定义属性
前面已经简述了自定义属性相关内容,现在将讲述自定义属性的使用。自定义属性的使用大致分为以下几个步骤:
1、自定义一个View类
2、创建attrs并编写自定义属性内容;
3、布局文件(xml)声明属性
4、View的构造方法中读取相关属性值;
自定义一个View类
package com.example.main;
import android.content.Context;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
public class CustView extends View {
public CustView(Context context) {
super(context);
}
public CustView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public CustView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public CustView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
}
创建attrs并编写自定义属性内容
在value文件下创建attrs.xml,并在attrs.xml文件中编写属性内容
<resources>
<declare-styleable name="CustViewStye">
<attr name="attr1" format="string" />
</declare-styleable>
</resources>
布局文件(xml)声明属性
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:cust="http://schemas.android.com/apk/res-auto"//此处次声明自定属性需要用到的命名空间
android:layout_width="match_parent"
android:layout_height="match_parent" >
在布局中声明属性
<com.example.main.CustView
android:layout_width="100dp"
android:layout_height="100dp"
cust:attr1="@string/hello_world" />//此处为attr1属性赋值
</RelativeLayout>
View的构造方法中读取相关属性值
public CustView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
TypedArray typeArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustViewStye,
0, 0);
String attr1 = typeArray.getString(R.styleable.CustViewStye_attr1);
typeArray.recycle();
}
总结
前面讲述了那么多,其实自定义属性并没有想象中的那么复杂,最后使用起来也就那么简单得四个步骤。
最后强调一下,在使用TypedArray之后一定记得释放,否则会造成内存泄漏。且自定义属性在自定义View中还是非常重要的。