一. 前言

自定义属性的使用步骤:

  1. 在在values目录下创建自定义属性的Xml文件。
  1. 在自定义View的构造方法中解析自定义属性的值。
  1. 在布局文件中使用自定义属性。

二. 在values目录下创建自定义属性的Xml文件

1. 小例子

例如:attrs_circle.xml

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--自定义属性集合:CircleView-->
<!--在该集合下,设置不同的自定义属性-->
<declare-styleable name="CircleView">

<!--在attr标签下设置需要的自定义属性-->
<!--此处定义了一个设置图形的颜色:circle_color属性,格式是color,代表颜色-->
<attr name="circle_color" format="color"/>

</declare-styleable>
</resources>

2. 自定义属性的Xml文件细节介绍

  • declare-styleable 标签的 name 值 没有硬性要求和自定义的类的名称相同,这个名称值使用来在构造方法中解析自定义的属性值的时候用到的。
  • declare-styleable 标签下可以嵌套一个个 attr标签,每一个 attr标签 都是一个个自定义的属性。需要指明自定属性的名称 name 以及 自定义属性的类型 format。

3. 自定义属性的类型

1
2
3
4
5
6
7
8
9
<-- 1. reference:使用某一资源ID -->
<declare-styleable name="名称">
<attr name="background" format="reference" />
</declare-styleable>

<ImageView
android:layout_width="42dip"
android:layout_height="42dip"
app:background="@drawable/图片ID" />
1
2
3
4
5
6
7
8
9
<--  2. color:颜色值 -->
<declare-styleable name="名称">
<attr name="textColor" format="color" />
</declare-styleable>

<TextView
android:layout_width="42dip"
android:layout_height="42dip"
android:textColor="#00FF00" />
1
2
3
4
5
6
7
8
9
<-- 3. boolean:布尔值 -->
<declare-styleable name="名称">
<attr name="focusable" format="boolean" />
</declare-styleable>

<Button
android:layout_width="42dip"
android:layout_height="42dip"
android:focusable="true" />
1
2
3
4
5
6
7
8
<-- 4. dimension:尺寸值 -->
<declare-styleable name="名称">
<attr name="layout_width" format="dimension" />
</declare-styleable>

<Button
android:layout_width="42dp"
android:layout_height="42dp" />
1
2
3
4
5
6
7
8
9
10
<-- 5. float:浮点值 -->
<declare-styleable name="AlphaAnimation">
<attr name="fromAlpha" format="float" />
<attr name="toAlpha" format="float" />
</declare-styleable>

<alpha
android:fromAlpha="1.0"
android:toAlpha="0.7" />

1
2
3
4
5
6
7
8
9
10
11
<-- 6. integer:整型值 -->
<declare-styleable name="AnimatedRotateDrawable">
<attr name="frameDuration" format="integer" />
<attr name="framesCount" format="integer" />
</declare-styleable>

<animated-rotate
xmlns:android="http://schemas.android.com/apk/res/android"
android:frameDuration="100"
android:framesCount="12"
/>
1
2
3
4
5
6
7
<-- 7. string:字符串 -->
<declare-styleable name="MapView">
<attr name="apiKey" format="string" />
</declare-styleable>

<com.google.android.maps.MapView
android:apiKey="0jOkQ80oD1JL9C6HAja99uGXCRiS2CGjKO_bc_g" />
1
2
3
4
5
6
7
8
9
10
11
<-- 8. fraction:百分数 -->
<declare-styleable name="RotateDrawable">
<attr name="pivotX" format="fraction" />
<attr name="pivotY" format="fraction" />
</declare-styleable>

<rotate
xmlns:android="http://schemas.android.com/apk/res/android"
android:pivotX="200%"
android:pivotY="300%"
/>
1
2
3
4
5
6
7
8
9
10
11
12
13
<-- 9. enum:枚举值 -->
<declare-styleable name="名称">
<attr name="orientation">
<enum name="horizontal" value="0" />
<enum name="vertical" value="1" />
</attr>
</declare-styleable>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
/>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<-- 10. flag:位或运算 -->
<declare-styleable name="名称">
<attr name="windowSoftInputMode">
<flag name="stateUnspecified" value="0" />
<flag name="stateUnchanged" value="1" />
<flag name="stateHidden" value="2" />
<flag name="stateAlwaysHidden" value="3" />
<flag name="stateVisible" value="4" />
<flag name="stateAlwaysVisible" value="5" />
<flag name="adjustUnspecified" value="0x00" />
<flag name="adjustResize" value="0x10" />
<flag name="adjustPan" value="0x20" />
<flag name="adjustNothing" value="0x30" />
</attr>
</declare-styleable>

<activity
android:name=".StyleAndThemeActivity"
android:label="@string/app_name"
android:windowSoftInputMode="stateUnspecified | stateUnchanged | stateHidden" >

<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
1
2
3
4
5
6
7
8
9
<-- 特别注意:属性定义时可以指定多种类型值 -->
<declare-styleable name="名称">
<attr name="background" format="reference|color" />
</declare-styleable>

<ImageView
android:layout_width="42dip"
android:layout_height="42dip"
android:background="@drawable/图片ID|#00FF00" />

三. 在自定义View的构造方法中解析自定义属性的值

我们需要在View的第二个构造方法中解析数据。

  • 自定义的属性值被封装在attrs中,context调用obtainStyledAttributes方法,传递 attrs 和 R.styleable.XXX参数,就可以得到自定义属性值的集合TypedArray。
  • 使用TypedArray类内定义的获取对应类型属性值的方法,获取属性值。一般情况下,除了传递要获取的属性名,还有给该属性赋一个默认值。
  • 千万要记住,取出属性一定要使用,不然的话是不会起作用的。
  • 使用完之后一定要调用 recycle 方法,释放资源。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public CircleView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);

//加载自定义属性集合
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleView);

//加载自定义的颜色属性
//第一个参数是设置的颜色
//第二个参数是默认的颜色
int color = typedArray.getColor(R.styleable.CircleView_circle_color, Color.GRAY);
mPaint.setColor(color);

//释放资源
typedArray.recycle();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public boolean getBoolean(@StyleableRes int index, boolean defValue)

public int getInt(@StyleableRes int index, int defValue)

public int getInteger(@StyleableRes int index, int defValue)

public float getFloat(@StyleableRes int index, float defValue)

public int getColor(@StyleableRes int index, @ColorInt int defValue)

public float getDimension(@StyleableRes int index, float defValue)

public int getDimensionPixelSize(@StyleableRes int index, int defValue)

public int getLayoutDimension(@StyleableRes int index, int defValue)

public float getFraction(@StyleableRes int index, int base, int pbase, float defValue)

public int getResourceId(@StyleableRes int index, int defValue)

public int getThemeAttributeId(@StyleableRes int index, int defValue)

public int getSourceResourceId(@StyleableRes int index, @AnyRes int defaultValue)

四. 解析多类型的属性

在自定义控件时有时会支持多种类型format,那么我们要怎么取值呢?

多种类型format取值的模板。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CompatTextView);

TypedValue value = new TypedValue();
boolean has = array.getValue(R.styleable.CompatTextView_ctv_drawableWidth, value);
if (has) {
if (value.type == TypedValue.TYPE_FLOAT) {
//浮点类型

} else if (value.type == TypedValue.TYPE_DIMENSION) {
//Dimen类型
}
} else {
//未传入
}

array.recycle();

参考文章

手把手教你写一个完整的自定义View

自定义属性多种format的取值方法