安卓自定义View进阶-自定义XML属性解析及R.attr和R.styleable的区别
2018-01-19 14:06
344 查看
自定义View中如果想通过XML文件指定参数,会直接在Res文件下新建的
我们先捋清楚这两个节点的关系,这两个节点,无论使用哪个,所达到的效果都是一样的。
每定义一个
而通过定义一个
由此得知,定义一个
如上是一个TextView的XML属性,我们可以通过
我们有时在自定义View的时候也需要自定义View的XML属性。
假设我们有一个自定义的View,其类名是
我们想要自定义XML属性,总的来说包括三步:
1. 在XML资源文件中定义需要的
2. 在自定
f447
义View的构造函数中解析这些从XML中第一段属性值,将其存放到自定义View对应的成员变量中;
3. 在XML布局文件中为自定义View的XML属性赋值。
我们需要在res/values目录下新建名字为
当我们指定了XML属性的名称和属性值的类型后,还需要先在布局文件中声明命名空间,studio的命名空间一般都指定为
在定义app的命名空间后,我们就可以为MyTextView属性赋值了。如果
format支持的类型有enum、boolean、color、dimension、flag、float、fraction、integer、reference、string。
自定义View的代码为:
我们在MyTextView 中定义了两个成员变量mCustomText和mCustomColor。MyTextView无论调用哪个构造函数最终都会调用到init方法,我们重点看一下init方法。
传递给init方法的是一个AttributeSet对象,可以把它看成一个索引数组,这个数组里面存储着MyTextView在XML中定义属性的索引,通过索引可以得到XML属性名和属性值。
通过调用AttributeSet的getAttributeCount()方法可以获得XML属性的数量,然后我们就可以在for循环中通过索引遍历AttributeSet的属性名和属性值。AttributeSet中有很多getXXX方法,一般必须的参数都是索引号,说几个常用的方法:
如果index对应的XML属性的format是string,那么通过AttributeSet的
这样就可以将XML中定义的属性获取并赋值给成员变量以供使用了。
使用
我们上面定义的customText和customColor这两个
要想能够通过style或theme设置XML属性的值,需要在
在
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对象,这里我们用的是四个参数的方法。如下所示:
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正是帮我们简化了这个过程。
View的style属性对应的style资源中定义的XML属性值,其实是View直接在layou文件中定义XML属性值的替补值,是用于补漏的,AttributeSet(即在layout中直接定义XML属性)的优先级高于style属性中资源所定义的属性值。
obtainStyledAttributes方法中,优先级从高到低依次是:直接在layout中设置View的XML属性值(AttributeSet) > 设置View的style属性 > defStyleAttr > defStyleRes
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节点。 |
flag | bit位标记,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属性值 |
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
相关文章推荐
- Android中View自定义XML属性详解以及R.attr与R.styleable的区别
- Android中View自定义XML属性详解以及R.attr与R.styleable的区别
- Android解析自定义属性的XML实现底部导航栏TabSelectedView,实现灵活的配置扩展
- Android自定义View的自定义属性atrrs.xml解析
- Android中View自定义XML属性详解以及R.attr与R.styleable的区别
- Android自定义View中的自定义属性(attrs.xml,TypedArray的使用)
- 安卓自定义View进阶-事件分发机制原理
- 安卓 自定义 View 进阶:绘制基本形状
- 安卓自定义View进阶-Path之完结篇
- 安卓自定义View进阶-Matrix详解
- 安卓自定义View进阶-Matrix Camera
- 自定义View 属性解析
- 安卓自定义View进阶-Matrix原理
- 安卓自定义View进阶-Matrix原理
- 慢慢成长路——自定义view(2),关于自定义属性的深入解析
- 安卓自定义View进阶-Matrix详解
- 安卓自定义View进阶-流程与重要的函数
- 安卓自定义View进阶-Canvas之绘制图形
- 为自定义View在xml里面设置属性
- 安卓自定义View进阶-Canvas之画布操作