转载请标明出处: http://blog.csdn.net/airsaid/article/details/54564120
本文出自:周游的博客
前言
和自定义 View 打交道,肯定是难免要写自定义属性的。虽然我们可以直接使用 Android 本身一些系统控件定义的属性,但是在实际开发中,由于我们所自定义 View 的多样性,所以我们就需要自己来定义属于我们所编写自定义控件的属性了。
定义
定义自定义属性非常简单,只需在 res/values/ 下新建 attrs.xml 文件(默认新建项目没有这个文件),并在该文件中定义自定义属性,如:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="custom_color" format="color"/>
<declare-styleable name="CustomAttribute">
<attr name="custom_radius" format="dimension"/>
<attr name="custom_color"/>
</declare-styleable>
</resources>
其中 attr 和 declare-styleable 节点分别代表的意思如下:
attr: 定义了一个属性,属性名为 custom_color 这个是可以随意起的,但是要注意不要和其他控件所冲突, format 所定义的是属性的格式,其中格式又分为好多种,下面会细说,这里定义的是颜色 color。
declare-styleable:定义了一个属性组,在里面我们可以单独写 attr 属性,也可以引用直接在 resources 下定义的 attr,其中的区别就是引用的不用写 format。
需要注意的是,attr 并不依赖与 declare-styleable,declare-styleable 只是方便了 attr 的使用,使属性的使用更加明确。两者在代码中的获取方式并不相同,下面会细说。
在实际开发中,我们一般是采用 declare-styleable 方式,直接定义一组自己所编写的自定义控件所需要用到的属性。
格式
上面说到了 format 指定自定义属性的格式,那么到底一共有多少种格式呢?答案是十种,分别如下:
- reference:参考某一资源 ID。
定义:
<declare-styleable name="名称">
<attr name="src" format="reference"/>
</declare-styleable>
使用:
app:src="@drawable/图片ID"
- color:颜色值。
定义:
<declare-styleable name="名称">
<attr name="color" format="color"/>
</declare-styleable>
使用:
app:color="#FFFFFF"
app:color="@color/颜色ID"
- boolean:布尔值。
定义:
<declare-styleable name="名称">
<attr name="clickable" format="boolean"/>
</declare-styleable>
使用:
app:clickable="true"
- dimension:尺寸值。
定义:
<declare-styleable name="名称">
<attr name="layout_width" format="dimension"/>
</declare-styleable>
使用:
app:layout_width="50dp"
- float:浮点值。
定义:
<declare-styleable name="名称">
<attr name="radius" format="float"/>
</declare-styleable>
使用:
app:radius="10.5"
- integer:整型值。
定义:
<declare-styleable name="名称">
<attr name="duration" format="integer"/>
</declare-styleable>
使用:
app:duration="1000"
- fraction:百分数。
定义:
<declare-styleable name="名称">
<attr name="pivotX" format="fraction"/>
</declare-styleable>
使用:
app:pivotX="50%"
- string:字符串。
定义:
<declare-styleable name="名称">
<attr name="text" format="string"/>
</declare-styleable>
使用:
app:text="Hello World!"
- enum:枚举值。
定义:
<declare-styleable name="名称">
<attr name="orientation">
<enum name="horizontal" value="0"/>
<enum name="vertical" value="1"/>
</attr>
</declare-styleable>
使用:
app:orientation="horizontal"
app:orientation="vertical"
- flag:位或运算。
定义:
<declare-styleable name="名称">
<attr name="gravity">
<flag name="top" value="0"/>
<flag name="center" value="1"/>
<flag name="bottom" value="2"/>
</attr>
</declare-styleable>
使用:
app:gravity="top"
app:gravity="center|bottom"
- 组合定义:
属性定义时也可以指定多种类型值,如:
<declare-styleable name="名称">
<attr name="background" format="reference|color"/>
</declare-styleable>
使用:
app:background="@drawable/图片ID"
app:background="#FFFFFF"
注意事项:
在 XML 布局中使用自定义属性的时候,请务必在布局的根节点加上命名空间:
xmlns:app="http://schemas.android.com/apk/res-auto"
添加该命名空间的作用是用于区分 Android 的命名空间:xmlns:android=""
,其中 app 是命名空间的名字,可以随意取,一般我都是写 app。
在 Eclipse 中还可以这样定义命名空间:xmlns:app="http://schemas.android.com/apk/包名"
,不过个人还是推荐写 res-auto,让其自动去指向。
获取
上面知道了如何定义自定义属性,现在该了解下如何获取了,获取的话,可以分为两种,一种是直接获取 resources 节点下定义的 attr,还有就是 declare-styleable,首先来看下第一种是如何获取的吧。
首先我们来定义好自定义属性:
<resources>
<attr name="custom_color" format="color"/>
</resources>
当我们每定义一个 attr 时就会在 R 文件中生成一个对应的 ID,所以我们想获取 attr 的话,就可以根据这个 ID 来获取:
public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
int[] customAttrs = {R.attr.custom_color};
TypedArray a = context.obtainStyledAttributes(attrs, customAttrs);
int color = a.getColor(0, Color.WHITE);
a.recycle();
}
Android 系统中其实也定义了很多的属性,就在sdk\platforms\android-23\data\res\values
目录下,我们同样也可以获取系统中原本定义的属性:
int[] customAttrs = {android.R.attr.color};
TypedArray a = context.obtainStyledAttributes(attrs, customAttrs);
mColor = a.getColor(0, mColor);
a.recycle();
如果你要是觉得上面这种获取方法太麻烦了,懒得写 int 数组的话,就来试试获取 declare-styleable 吧!
依然首先来定义好属性:
<declare-styleable name="CustomAttribute">
<attr name="custom_color" format="color"/>
<attr name="custom_radius" format="dimension"/>
</declare-styleable>
当我们定义 declare-styleable 时,R 文件里就自动给我们生成了一个 int[],其中的 attr 就是每一个元素,所以获取时,就不用再单独定义 int[] 了:
public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomAttribute);
mColor = a.getColor(R.styleable.CustomAttribute_custom_color, mColor);
mRadius = a.getDimension(R.styleable.CustomAttribute_custom_radius, mRadius);
a.recycle();
}
嗯,得确方便了那么一丢丢。使用这种方式,我们也可以获取系统中的属性,只需把系统的属性引用过来:
<declare-styleable name="CustomAttribute">
<attr name="custom_color" format="color"/>
<attr name="custom_radius" format="dimension"/>
<attr name="android:color"/>
</declare-styleable>
获取:
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomAttribute);
// mColor = a.getColor(R.styleable.CustomAttribute_custom_color, mColor);
mColor = a.getColor(R.styleable.CustomAttribute_android_color, mColor);
mRadius = a.getDimension(R.styleable.CustomAttribute_custom_radius, mRadius);
a.recycle();
有实践的朋友,可能会发现 obtainStyledAttributes 方法可是有好几种重载方法呢!虽然上面这种能用,但是不知道其他几种的意思,心里感觉总是少点什么,不得劲,所以还是赶紧来了解下剩下几种重载方法的意思吧~
obtainStyledAttributes
obtainStyledAttributes 方法是 context 的,该方法一共有四个重载方法,分别如下
- obtainStyledAttributes(int[] attrs):从系统主题中获取 attrs 中的属性。
- obtainStyledAttributes(int resid, int[] attrs):从资源文件中获取 attrs 中的属性。
- obtainStyledAttributes(AttributeSet set, int[] attrs):从 layout 设置的属性中获取 attrs 中的属性。
- obtainStyledAttributes(AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes):稍复杂,下面再细说。
猛一看,很蒙B,都是啥啊?具体我们先不用管,只需先在脑海中有个概念:”我们需要获取哪些属性,从哪里获取“ 即可。
obtainStyledAttributes(int[] attrs)
首先来看下第一个方法,该方法是从系统主题中,也就是 theme 中获取属性,那么首先就需要在 manifest 指定好 theme:
android:theme="@style/AppTheme"
接下来在 attrs 文件中定义好,我们需要获取的属性:
<declare-styleable name="CustomAttribute">
<attr name="custom_color" format="color"/>
<attr name="custom_radius" format="dimension"/>
</declare-styleable>
需要获取的属性定义好了,那么从哪里获取呢? 当时是我们刚开始指定的 theme 了,在 styles 文件中指定好数据源:
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!--直接在主题中指定-->
<item name="custom_color">#FF0000</item>
<item name="custom_radius">100dp</item>
</style>
在代码中获取:
TypedArray a = context.obtainStyledAttributes(R.styleable.CustomAttribute);
mColor = a.getColor(R.styleable.CustomAttribute_custom_color, mColor);
mRadius = a.getDimension(R.styleable.CustomAttribute_custom_radius, mRadius);
a.recycle();
obtainStyledAttributes(int resid, int[] attrs)
该方法是从资源文件中获取 attrs 中的属性,首先依然还是需要在 attrs 文件中定义好我们需要获取的属性:
<declare-styleable name="CustomAttribute">
<attr name="custom_color" format="color"/>
<attr name="custom_radius" format="dimension"/>
</declare-styleable>
知道要获取哪些了,那么从哪里获取呢? 在 styles 文件指定好数据源:
<style name="CustomTheme">
<item name="custom_color">#00FF00</item>
<item name="custom_radius">10dp</item>
</style>
在代码中获取:
TypedArray a = context.getTheme().obtainStyledAttributes(R.style.CustomTheme, R.styleable.CustomAttribute);
mColor = a.getColor(R.styleable.CustomAttribute_custom_color, mColor);
mRadius = a.getDimension(R.styleable.CustomAttribute_custom_radius, mRadius);
a.recycle();
obtainStyledAttributes(AttributeSet set, int[] attrs)
该方法从 layout 设置的属性中获取 attrs 中的属性。我们在 layout 中设置的属性,如:
<com.github.airsaid.customattributedemo.widget.CircleView
app:custom_color="#0000FF"
style="@style/CircleView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
最终都会包含在 AttributeSet 中,View 会在构造中传递过来,需要注意的是,AttributeSet 也同时包含了 setyle 中设置的属性。
这也就说明了,为什么我们在自定义 View 的时候,都要重写:public CircleView(Context context, AttributeSet attrs)
构造参数。因为如果不重写的话,我们将无法获取到在 layout 中设置的属性。
接下来还是通过该方法来获取我们定义的属性,首先依然是定义好需要需要获取的属性:
<declare-styleable name="CustomAttribute">
<attr name="custom_color" format="color"/>
<attr name="custom_radius" format="dimension"/>
</declare-styleable>
这时的数据源,就需要在 layout 布局中设置了:
<com.github.airsaid.customattributedemo.widget.CircleView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:custom_color="#0000FF"
app:custom_radius="50dp"/>
在代码中获取:
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomAttribute);
mColor = a.getColor(R.styleable.CustomAttribute_custom_color, mColor);
mRadius = a.getDimension(R.styleable.CustomAttribute_custom_radius, mRadius);
a.recycle();
obtainStyledAttributes(AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes)
该方法是最后的一个方法,上面的三个重载方法,其实最终都是走了该方法。
下面来演示下用该方法获取属性,老规矩,首先依然是定义我们需要获取的属性:
<declare-styleable name="CustomAttribute">
<attr name="custom_type" format="string"/>
<attr name="customStyle" format="reference"/>
</declare-styleable>
XML 布局中(这里指定的是 set 参数中要获取的属性):
<com.github.airsaid.customattributedemo.widget.CircleView
app:custom_type="1:在 XML 布局中声明属性"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
Styles 文件中(这里指定的是 defStyleAttr 参数用到的属性):
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="customStyle">@style/DefaultCustomStyle</item>
</style>
<style name="DefaultCustomStyle">
<item name="custom_type">2:主题中声明的默认样式属性</item>
</style>
Styles 文件中(这里指定的是 defStyleRes 参数要用到的属性):
<style name="MyCustomStyle">
<item name="custom_type">3:默认资源样式属性</item>
</style>
注意,要在 manifest 文件中指定当前 theme:
android:theme="@style/AppTheme"
在代码中获取:
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomAttribute, R.attr.customStyle, R.style.MyCustomStyle);
String type = a.getString(R.styleable.CustomAttribute_custom_type);
Log.e("test", "type: " + type);
a.recycle();
打印结果:
01-15 11:04:10.008 7921-7921/com.github.airsaid.customattributedemo E/test: type: 1:在 XML 布局中声明属性
通过结果可以看出,因为我们已经在 XML 中声明好属性了,此时的 attrs 参数中是有值的,所以就会优先获取 attrs 中的属性值,这时,如果我们把 attrs 改为 null 试试:
TypedArray a = context.obtainStyledAttributes(null, R.styleable.CustomAttribute, R.attr.customStyle, R.style.MyCustomStyle);
重新运行,打印结果:
01-15 11:05:58.377 7921-7921/com.github.airsaid.customattributedemo E/test: type: 2:主题中声明的默认样式属性
通过上面的打印结果可以发现,当 attrs 是 null 时,会在当前主题中寻找默认声明的属性值。那么如果我们此时把该参数改为 0 呢? 试试看:
TypedArray a = context.obtainStyledAttributes(null, R.styleable.CustomAttribute, 0, R.style.MyCustomStyle);
重新运行,打印结果:
01-15 11:09:25.142 7921-7921/com.github.airsaid.customattributedemo E/test: type: 3:默认资源样式属性
果不其然,当 defStyleAttr 参数为 0 或者无法在对应的主题下找到资源文件时,那么会再去寻找 defStyleRes 中指定的资源文件中声明的属性。如果此时再把 defStyleRes 也改为 0 的话,那么不用说,获取时肯定是 null 的,因为已经没有数据源可以获取了。
实例
自定义属性已经了解的差不多了,接下来该写一个完整的自定义 View 练练手了,这里我们把上篇中 自定义 View 基础实例 中的第一个实例给搬过来吧,毕竟还说过要完善它的!
- 第一步:定义自定义 View 需要的属性。
<declare-styleable name="CustomAttribute">
<attr name="custom_color" format="color"/>
<attr name="custom_radius" format="dimension"/>
</declare-styleable>
- 第二步:编写自定义 View。
public class CircleView extends View {
private Paint mPaint;
private int mColor = Color.WHITE;
/** 圆半径 */
private float mRadius = 0;
public CircleView(Context context) {
this(context, null);
}
public CircleView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomAttribute);
mColor = a.getColor(R.styleable.CustomAttribute_custom_color, mColor);
mRadius = a.getDimension(R.styleable.CustomAttribute_custom_radius, mRadius);
a.recycle();
init();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if(widthMode == MeasureSpec.AT_MOST){
widthSize = (int) (mRadius * 2 + getPaddingLeft() + getPaddingRight());
}
if(heightMode == MeasureSpec.AT_MOST){
heightSize = (int) (mRadius * 2 + getPaddingTop() + getPaddingBottom());
}
setMeasuredDimension(widthSize, heightSize);
}
private void init() {
// 初始化画笔
mPaint = new Paint();
mPaint.setColor(mColor);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int paddingLeft = getPaddingLeft();
int paddingTop = getPaddingTop();
int paddingRight = getPaddingRight();
int paddingBottom = getPaddingBottom();
int width = getWidth() - paddingLeft - paddingRight;
int height = getHeight() - paddingTop - paddingBottom;
canvas.drawCircle(width / 2 + paddingLeft, height / 2 + paddingTop, mRadius, mPaint);
}
}
- 第三步:在 XML 布局中声明自定义 View:
<com.github.airsaid.customattributedemo.widget.CircleView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#999999"
app:custom_color="#FF0000"
app:custom_radius="20dp"/>
注意事项:
别忘记,加上命令空间:
xmlns:app="http://schemas.android.com/apk/res-auto"
运行结果: