您的位置:首页 > 其它

安卓自定义View进阶-自定义XML属性解析及R.attr和R.styleable的区别

2018-01-19 14:06 344 查看
自定义View中如果想通过XML文件指定参数,会直接在Res文件下新建的
attr.xml
,但是子节点有时候用
styleable
,有时候用
attr
。以至于对于两个一直有点傻傻分不清,今天搜索研究了几篇博客,算是有了一些眉目,所以在此记录下来,希望对看到博客的人有所帮助。——–本文内容部分参考自(http://blog.csdn.net/iispring/article/details/50708044

attr和styleable的关系

<resources>
<attr name="customColor" format="color"/>
<attr name="customText" format="string"/>
</resources>


<resources>
<declare-styleable name="customView">
<attr name="customColor" format="color"/>
<attr name="customText" format="string"/>
</declare-styleable>
</resources>


我们先捋清楚这两个节点的关系,这两个节点,无论使用哪个,所达到的效果都是一样的。
attr
不依赖于
styleable
,
styleable
只是为了方便
attr
的使用。

每定义一个
attr
,就会在R文件中生成一个id,我们去调用的时候会用
R.attr.customAttr
操作。

而通过定义一个
styleable
,可以在R文件里自动生成一个int[],数组的每个值对应的就是定义在
styleable
中的
attr
的id.

由此得知,定义一个
declare-styleable
,在获取属性的时候为我们自动提供了一个属性数组。此外,使用
declare-styleable
的方式有利于我们我们把相关的属性组织起来,有一个分组的概念,属性的使用范围更加明确。

为View添加自定义XML属性

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />


如上是一个TextView的XML属性,我们可以通过
android:text
为TextView的文本赋值。

我们有时在自定义View的时候也需要自定义View的XML属性。

假设我们有一个自定义的View,其类名是
com.cxx.demo.widget.MyTextView
,其中
com.cxx.demo.widget
是应用程序的包名。

我们想要自定义XML属性,总的来说包括三步:

1. 在XML资源文件中定义需要的
attr
,指定
attr
的数据类型;

2. 在自定
f447
义View的构造函数中解析这些从XML中第一段属性值,将其存放到自定义View对应的成员变量中;

3. 在XML布局文件中为自定义View的XML属性赋值。

我们需要在res/values目录下新建名字为
attrs.xml
文件(名字可以任意)。然后在该文件中定义MyTextView的XML属性。该文件的根节点为
<resources>
,我们在
<resources>
节点下可以添加多个
<attr>
子节点,通过
name
指定属性名称,通过
format
指定属性值的类型。如图所示:



当我们指定了XML属性的名称和属性值的类型后,还需要先在布局文件中声明命名空间,studio的命名空间一般都指定为
xmlns:app="http://schemas.android.com/apk/res-auto"
,这样定义的命名空间自动指向当前App的命名空间。

在定义app的命名空间后,我们就可以为MyTextView属性赋值了。如果
app:customColor
指定的format类型为color,那么对应的XML属性值必须为color类型。如图所示:



format支持的类型有enum、boolean、color、dimension、flag、float、fraction、integer、reference、string。

format值attr对应值类型
boolean布尔类型的值,取值只能是true或false。
color颜色类型的值,例如#ff0000,也可以使用一个指向Color的资源,
比如@android:color/background_dark,但是不能用0xffff0000这样的值。
string字符串类型。
integer整数类型,取值只能是整数,不能是浮点数。
float浮点数类型,取值只能是浮点数或整数。
fraction百分数类型,取值只能以%结尾,例如30%、120.5%等。
dimension尺寸类型,例如取值16px、16dp,也可以使用一个指向
<dimen>
类型的资源,
比如
@android:dimen/app_icon_size
reference只能指向某一资源的ID,例如取值@id/textView。
enum枚举类型,在定义enum类型的attr时,可以将attr的format设置为enum,
也可以不用设置attr的format属性,但是必须在attr节点下面添加一个或多个enum节点。
flagbit位标记,flag与enum有相似之处,定义了flag的attr,在设置值时,可以通过
|
设置多个值,而且每个值都对应一个bit位,这样通过按位或操作符
|
可以将多个值合成一个值,我们一般在用flag表示某个字段支持多个特性,需要注意的是,要想使用flag类型,不能在attr上设置format为flag,不要设置attr的format的属性,直接在attr节点下面添加flag节点即可。
<!--enum举例-->
<attr name="customAttr">
<enum name="man" value="0" />
<enum name="woman" value="1" />
</attr>


<!--flag举例-->
<attr name="customAttr">
<flag name="none" value="0" />
<flag name="bold" value="0x1" />
<flag name="italic" value="0x2" />
<flag name="underline" value="0x4" />
</attr>

在<attr>节点下通过定义多个<flag>表示其支持的值,value的值一般是0或者是2的N次方(N为大于等于0的整数),
对于上面的例子我们在实际设置值是可以设置单独的值,如none、bold、italic、underline,也可以通过|设置多个值,
例如app:customAttr="italic|underline"。


自定义View的代码为:

package com.cxx.demo.widget;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.text.TextPaint;
import android.text.TextUtils;
import android.util.AttributeSet;

import com.cxx.demo.R;

/**
* Created by ZHOU on 2018/1/18.
*/

public class MyTextView extends android.support.v7.widget.AppCompatTextView {

//存储要显示的文本
private String mCustomText;

/*** 存储文本的显示颜色*/

private int mCustomColor = 0xFF000000;
//画笔
private TextPaint mTextPaint;
//字体大小
private float fontSize = getResources().getDimension(R.dimen.textSize);

public MyTextView(Context context) {
this(context,null);
}

public MyTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs, defStyleAttr);
}

private void init(AttributeSet attrs, int defStyleAttr) {
//首先判断attrs是否为null
if (attrs!=null){
//获取AttributeSet中所有的XML属性的数量
int count  = attrs.getAttributeCount();
//遍历AttributeSet中的XML属性
for (int i = 0; i < count; i++) {
//获取attr的资源ID
int attrsResId = attrs.getAttributeNameResource(i);
switch (attrsResId){
case R.attr.customColor:
//customColor属性
//如果读取不到对应的颜色值,那么就用黑色作为默认颜色
mCustomColor = attrs.getAttributeIntValue(i,0xff000000);
break;
case R.attr.customText:
//customText属性
mCustomText = attrs.getAttributeValue(i);
break;
default:
break;
}
}
}
//初始化画笔
mTextPaint = new TextPaint();
mTextPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
mTextPaint.setTextSize(fontSize);
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (!TextUtils.isEmpty(mCustomText)){
mTextPaint.setColor(mCustomColor);
//将文本绘制显示出来
canvas.drawText(mCustomText,0,fontSize,mTextPaint);
}
}
}


我们在MyTextView 中定义了两个成员变量mCustomText和mCustomColor。MyTextView无论调用哪个构造函数最终都会调用到init方法,我们重点看一下init方法。

传递给init方法的是一个AttributeSet对象,可以把它看成一个索引数组,这个数组里面存储着MyTextView在XML中定义属性的索引,通过索引可以得到XML属性名和属性值。

通过调用AttributeSet的getAttributeCount()方法可以获得XML属性的数量,然后我们就可以在for循环中通过索引遍历AttributeSet的属性名和属性值。AttributeSet中有很多getXXX方法,一般必须的参数都是索引号,说几个常用的方法:

方法解释
getAttributeName (int index)得到对应索引的XML属性名
getAttributeNameResource (int index)得到对应索引的XML属性在R.attr中的资源ID,例如R.attr.customText、R.attr.customColor。
getAttributeValue (int index)得到对应索引的XML属性值
如果index对应的XML属性的format是string,那么通过AttributeSet的
String getAttributeValue (int index)
方法,可以得到对应索引的XML属性的值,该方法返回的是String。除此之外,AttributeSet还有getAttributeIntValue、getAttributeFloatValue、getAttributeListValue等方法,返回不同类型的属性值。

这样就可以将XML中定义的属性获取并赋值给成员变量以供使用了。

使用
<declare-styleable>
和obtainStyledAttributes方法

我们上面定义的customText和customColor这两个
<attr>
属性都是直接在
<resources>
节点下定义的,这样定义
<attr>
属性存在一个问题:不能通过style或theme设置这两个属性的值。

要想能够通过style或theme设置XML属性的值,需要在
<resources>
节点下添加
<declare-styleable>
节点,并在
<declare-styleable>
节点下定义
<attr>
,如下所示:

<resources>
<declare-styleable name="MyTextView">
<attr name="customText" format="string" />
<attr name="customColor" format="color" />
</declare-styleable>
</resources>

<!-- 需要给<declare-styleable>设置name属性,一般name设置为自定义View的名字,我们此处设置为MyTextView。-->


R.styleable.MyTextView
是一个int数组,也就是R.styleable.MyTextView等价于数组[R.attr.customText, R.attr.customColor]。

<declare-styleable>
中定义的
<attr>
在MyTextView中需要通过调用
obtainStyledAttributes()
方法来读取解析属性值,其中,Resources.Theme.obtainStyledAttributes有三个重载方法,如下所示:

public TypedArray obtainStyledAttributes (int[] attrs)

public TypedArray obtainStyledAttributes (int resid, int[] attrs)

public TypedArray obtainStyledAttributes (AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes)

Resources有一个重载方法

public TypedArray obtainStyledAttributes(AttributeSet set, int[] attrs)

这几个方法都是返回一个TypedArray对象,这里我们用的是四个参数的方法。如下所示:

private void init(AttributeSet attributeSet, int defStyle) {
//首先判断attributeSet是否为null
if(attributeSet != null){
//获取当前MyView所在的Activity的theme
Resources.Theme theme = getContext().getTheme();
//通过theme的obtainStyledAttributes方法获取TypedArray对象
TypedArray typedArray = theme.obtainStyledAttributes(attributeSet, R.styleable.MyTextView, 0, 0);
//获取typedArray的长度
int count = typedArray.getIndexCount();
//通过for循环遍历typedArray
for(int i = 0; i < count; i++){
//通过typedArray的getIndex方法获取指向R.styleable中对应的属性ID
int styledAttr = typedArray.getIndex(i);
switch (styledAttr){
case R.styleable.MyView_customText:
//如果是R.styleable.MyView_customText,表示属性是customText
//通过typedArray的getString方法获取字符串值
mCustomText = typedArray.getString(i);
break;
case R.styleable.MyView_customColor:
//如果是R.styleable.MyView_customColor,表示属性是customColor
//通过typedArray的getColor方法获取整数类型的颜色值
mCustomColor = typedArray.getColor(i, 0xFF000000);
break;
}
}
//在使用完typedArray之后,要调用recycle方法回收资源
typedArray.recycle();
}

...
}


TypedArray是一个数组,通过该数组可以获取应用了style和theme的XML属性值。我们看上面的obtainStyledAttributes()方法,后面两个参数暂且忽略不计,后面会介绍。第一个参数还是AttributeSet对象,第二个参数是一个int类型的数组,该数组表示想要获取的属性值的属性的R.attr中的ID,此处我们传入的是R.styleable.MyTextView,在上面我们已经提到其值等价于[R.attr.customText, R.attr.customColor],表示我们此处想获取customText和customColor这两个属性的值。

注:TypedArray其实是用来简化我们的工作的,比如上例,如果布局中的属性的值是引用类型(比如:@dimen/dp100),如果使用AttributeSet去获得最终的像素值,那么需要第一步拿到id,第二步再去解析id。而TypedArray会直接去dp100的值,TypedArray正是帮我们简化了这个过程。

<com.cxx.demo.widget.MyTextView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:customText="customText in AttributeSet"
style="@style/RedStyle"
/>


<style name="RedStyle">
<item name="customText">customText in RedStyle</item>
<!-- 红色 -->
<item name="customColor">#FFFF0000</item>
</style>


View的style属性对应的style资源中定义的XML属性值,其实是View直接在layou文件中定义XML属性值的替补值,是用于补漏的,AttributeSet(即在layout中直接定义XML属性)的优先级高于style属性中资源所定义的属性值。

obtainStyledAttributes方法之defStyleAttr

方法
obtainStyledAttributes (AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes)
中的第三个参数
defStyleAttr
,这个参数表示的是一个
<style>
中某个属性的ID(
R.attr.***
),当Android在AttributeSet和style属性所定义的style资源中都没有找到XML属性值时,就会尝试查找当前theme(theme其实就是一个
<style>
资源)中属性为defStyleAttr的值,如果其值是一个style资源,那么Android就会去该资源中再去查找XML属性值。

obtainStyledAttributes方法之defStyleRes

方法
obtainStyledAttributes (AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes)
中的第四个参数defStyleRes。这个参数表示的是一个
<style>
(R.style.***)
。与defStyleAttr类似,defStyleRes是前面几项的替补值,defStyleRes的优先级最低。与defStyleAttr不同的是,defStyleRes本身直接表示一个style资源,而theme要通过属性defStyleAttr间接找到style资源。

总结

可以不通过
<declare-styleable>
节点定义XML属性,不过还是建议将XML属性定义在
<declare-styleable>
节点下,因为这样Android会在R.styleable下面帮我们生成很多有用的常量供我们直接使用。

obtainStyledAttributes方法中,优先级从高到低依次是:直接在layout中设置View的XML属性值(AttributeSet) > 设置View的style属性 > defStyleAttr > defStyleRes
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: