对于自定义属性,遵循如下几步,就能够实现:php
CustomView
(extends View 或者 ViewGroup )类values/attrs.xml
,在其中编写styleable
和attr
等标签元素CustomView
使用自定义的属性那么,我有几个问题,若是回答的很好,下面的文章就不用看了,能够跳过:java
AttributeSet
(eg: CustomView(Context context, AttributeSet attrs)
)这个参数看名字就知道包含的是参数的数组,那么我能不能经过它去获取个人自定义属性呢?TypedArray
是什么鬼?从哪冒出来的,就要我去使用?<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CustomViewTest">
<attr name="testText" format="string"/>
<attr name="testInteger" format="integer"/>
</declare-styleable>
</resources>
复制代码
public class CustomView extends View {
···
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomViewTest);
String testText = a.getString(R.styleable.CustomViewTest_testText);
int testInteger = a.getInteger(R.styleable.CustomViewTest_testInteger, 10);
Log.e(TAG, "testText =" + testText + " ,testInteger=" + testInteger);
a.recycle();
}
···
}
复制代码
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
···
<!-- 自动查找属性 -->
xmlns:app="http://schemas.android.com/apk/res-auto"
"> <com.zeroxuan.customviewtest.CustomView android:layout_width="match_parent" android:layout_height="match_parent" app:testInteger="10086" app:testText="zeroXuan" /> </android.support.constraint.ConstraintLayout> 复制代码
注意:个人styleable的name写的是CustomViewTest,因此说这里并不要求必定是自定义View的名字
。android
构造方法中的有个参数叫作
AttributeSet
(eg: MyTextView(Context context, AttributeSet attrs) )这个参数看名字就知道包含的是参数的集合,那么我能不能经过它去获取个人自定义属性呢?数组
首先AttributeSet
中的确保存的是该View
声明的全部的属性,而且外面的确能够经过它去获取(自定义的)属性,怎么作呢?以下:bash
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
final int count = attrs.getAttributeCount();
for (int i = 0; i < count; i++) {
String attrName = attrs.getAttributeName(i);
String attrValue = attrs.getAttributeValue(i);
Log.e(TAG, "attrName= " + attrName + " ,attrValue=" + attrValue);
}
}
复制代码
输出:app
咦,真的能够得到全部的属性。经过
AttributeSet
能够得到布局文件中定义的全部属性的key和value,那么是否是说TypedArray
就能够抛弃了呢?答案是:NO,NO,No
,重要的事,说三遍!。ide
<com.zeroxuan.customviewtest.CustomView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:testInteger="10086"
app:testText="@string/my_name" />
复制代码
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
final int count = attrs.getAttributeCount();
for (int i = 0; i < count; i++) {
String attrName = attrs.getAttributeName(i);
String attrValue = attrs.getAttributeValue(i);
Log.e(TAG, "attrName= " + attrName + " ,attrValue=" + attrValue);
}
Log.e(TAG, ">> Use TypedArray");
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomViewTest);
String testText = a.getString(R.styleable.CustomViewTest_testText);
int testInteger = a.getInteger(R.styleable.CustomViewTest_testInteger, 10);
Log.e(TAG, "testText =" + testText + " ,testInteger=" + testInteger);
a.recycle();
}
复制代码
经过运行结果能够看出,使用AttributeSet
获取的值,若是是引用都变成了@+数字
的字符串。你说,这玩意你能看懂么?那么你看看最后一行使用TypedArray获取的值,是否是瞬间明白了。函数
TypedArray
其实就是用来简化咱们解析自定义属性工做的。好比上例,若是布局中的属性的值是引用类型,若是使用AttributeSet去得到最终的testText
取值,那么须要第一步拿到id,第二步再去解析id。而TypedArray正是帮咱们简化了这个过程
。布局
若是经过AttributeSet获取最终的testText
取值的过程以下:post
//使用索引 3 ,是由于testText在CustomView中的索引是3
int resId=attrs.getAttributeResourceValue(3,-1);
Log.e(TAG, "attrName= "+getResources().getString(resId) );
复制代码
ok,如今别人问你TypedArray存在的意义,你就能够告诉他,
TypedArray
其实就是用来简化解析自定义属性工做流程的。
首先要明确一点,attr
不依赖于declare-styleable
,declare-styleable
只是为了方便attr
的使用。
咱们本身定义的属性彻底能够不放到declare-styleable
里面,好比直接在resources文件中定义一些属性:
<attr name="custom_attr1" format="string" />
<attr name="custom_attr2" format="string" />
复制代码
定义一个attr
就会在R文件里面生成一个attr
类型的资源Id
,那么咱们去获取这个属性时,必须调用以下代码:
int[] custom_attrs = {R.attr.custom_attr1,R.custom_attr2};
TypedArray typedArray = context.obtainStyledAttributes(set,custom_attrs);
复制代码
而经过定义一个declare-styleable
,咱们能够在R文件里自动生成一个int[]
,数组里面的int
就是定义在declare-styleable
里面的attr的id
。因此咱们在获取属性的时候就能够直接使用declare-styleable
数组来获取一系列的属性。
<declare-styleable name="custom_attrs">
<attr name="custom_attr1" format="string" />
<attr name="custom_attr2" format="string" />
</declare-styleable>
复制代码
获取:
TypedArray typedArray = context.obtainStyledAttributes(set,R.styleable.custom_attrs);
复制代码
若是系统中已经有了语义比较明确的属性,我能够直接使用嘛?
答案是确定的,可使用,使用方式以下:
<declare-styleable name="test"> <!-- 使用系统属性或者已经定义好的属性,不须要去添加format属性 --> <attr name="android:text" /> <attr name="testAttr" format="integer" /> </declare-styleable> 复制代码
而后在类中这么获取:
a.getString(R.styleable.CustomViewTest_android_text);
布局文件中直接android:text="zeroXuan is my name"
便可。
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 final TypedArray obtainStyledAttributes(
AttributeSet set, @StyleableRes int[] attrs) {
return getTheme().obtainStyledAttributes(set, attrs, 0, 0);
}
复制代码
public final TypedArray obtainStyledAttributes(
AttributeSet set, @StyleableRes int[] attrs, @AttrRes int defStyleAttr,
@StyleRes int defStyleRes) {
return getTheme().obtainStyledAttributes(
set, attrs, defStyleAttr, defStyleRes);
}
复制代码
能够看出最终都是调用方法4,如今主要分析方法obtainStyledAttributes(AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes)
,其中这个方法的四个参数解释以下:
一个指向style的引用
.当咱们没有给自定义View设置declare-styleable资源集合时,默认从这个集合里面查找布局文件中配置属性值.传入0表示不向该defStyleAttr中查找默认值.一个指向Style的资源ID
,可是仅在defStyleAttr为0或者defStyleAttr不为0但Theme中没有为defStyleAttr属性赋值时起做用.这么说可能有点迷糊,来一个例子但愿你能立马领悟!
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CustomView">
<attr name="testText1" format="string"/>
<attr name="testText2" format="string"/>
<attr name="testText3" format="string"/>
<attr name="testText4" format="string"/>
<attr name="testText5" format="string"/>
<attr name="attr_defStyle" format="reference"/>
</declare-styleable>
</resources>
复制代码
其中attr_defStyle
属性名,就是obtainStyledAttributes
中的第三个参数。Style
和Theme
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="attr_defStyle">@style/style_attr_defStyleAttr</item>
<item name="testText1">testText1-declare in Theme</item>
<item name="testText2">testText2-declare in Theme</item>
<item name="testText3">testText3-declare in Theme</item>
<item name="testText4">testText4-declare in Theme</item>
<item name="testText5">testText5-declare in Theme</item>
</style>
<!-- 用来表示 defStyleRes-->
<style name="style_defStyleRes">
<item name="testText1">testText1-declare in style_defStyleRes</item>
<item name="testText2">testText2-declare in style_defStyleRes</item>
<item name="testText3">testText3-declare in style_defStyleRes</item>
<item name="testText4">testText4-declare in style_defStyleRes</item>
</style>
<!-- 用来表示 attr_defStyleAttr 这个属性的值 -->
<style name="style_attr_defStyleAttr">
<item name="testText1">testText1-declare in style_attr_defStyleAttr</item>
<item name="testText2">testText2-declare in style_attr_defStyleAttr</item>
<item name="testText3">testText3-declare in style_attr_defStyleAttr</item>
</style>
<!-- 直接在布局中的 style 中使用 -->
<style name="style_CustomViewStyle">
<item name="testText1">testText1-declare in style_CustomViewStyle</item>
<item name="testText2">testText2-declare in style_CustomViewStyle</item>
</style>
</resources>
复制代码
public class CustomView extends View {
private static final String TAG = "CustomView";
public CustomView(Context context) {
this(context, null);
}
public CustomView(Context context, AttributeSet attrs) {
//R.attr.attr_defStyle 就是defStyleRes
this(context, attrs, R.attr.attr_defStyle);
}
public CustomView(Context context, AttributeSet attrs,
int defStyleAttr) {
this(context, attrs, defStyleAttr, R.style.style_defStyleRes);
}
@TargetApi(21)
public CustomView(Context context, AttributeSet attrs,
int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
TypedArray a = context
.obtainStyledAttributes(attrs, R.styleable.CustomView, defStyleAttr, defStyleRes);
String text1 = a.getString(R.styleable.CustomView_testText1);
String text2 = a.getString(R.styleable.CustomView_testText2);
String text3 = a.getString(R.styleable.CustomView_testText3);
String text4 = a.getString(R.styleable.CustomView_testText4);
String text5 = a.getString(R.styleable.CustomView_testText5);
Log.e(TAG, "text1== " + text1);
Log.e(TAG, "text2== " + text2);
Log.e(TAG, "text3== " + text3);
Log.e(TAG, "text4== " + text4);
Log.e(TAG, "text5== " + text5);
a.recycle();
}
}
复制代码
<com.zeroxuan.customviewtest.CustomView
style="@style/style_CustomViewStyle"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:testText1="Direct declare in XML" />
复制代码
调用顺序
优先
,取在布局中给定的值次之
,取在布局中设置的style中的值其次
,从defStyleAttr和defStyleRes中取值,注意若是 defStyleAttr有值,则再也不去defStyleResult中的值,就算defStyleAttr有的属性没有赋值。(具体看上面的打印结果)最后使
用,Theme中设置的属性注意 defStyleAttr的值必定要在Theme中设置才有效果,就拿上面的例子说,若是你没有在Theme中给R.attr.attr_defStyle赋值,而是直接在布局文件中赋值,这样作是没有效果的。
@
与 ?
符号的引用在使用时都有一个规范的格式:“@[+][package:]type:name”
,“?[package:][type:]name”
。能够看到,两者均包含引用符号、资源所属的包、资源类型和资源名称。
@
符号用于引用系统和咱们在项目中添加的一些固有资源
(drawable,string 等),或者定义的 style 样式。好比:
android:text="@string/app_name"
复制代码
这里的 app_name 就是咱们本身定义在项目文件 values/strings.xml 中的字符串资源。
android:text="@android:string/cancel"
复制代码
而这里的cancel
属于Android SDK
中的系统字符串资源,因此须要添加 @android:
来指明引用来源。android:
是 package:
的一个具体实例。
?
符号用于引用当前主题
中定义的一些属性值。
注意,? 符号经过属性名字间接引用当前主题中的对应属性值,而不是属性自己。
举个例子:
android:divider="?android:listDivider"
复制代码
这里的 ?
符号经过属性名 android:listDivider
间接获取当前主题赋予该属性的值。如同 @android:
通常,?android:
表示该值源自 Android SDK
系统属性。因为在当前主题中寻找对应属性名的值,因此没有指定属性类型,其实等同于:?android:attr/listDivider
。
咱们在 attrs.xml 中定义一个属性,如:
<declare-styleable name="CustomTextView">
<attr name="colorTextCustom" format="reference|color"/>
</declare-styleable>
复制代码
显然,此时咱们定义的 colorTextCustom
属性是没有值的,直接引用没有任何做用。须要在主题 style 中赋值:
<style name="BaseTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="colorTextCustom">#FF0000</item>
</style>
<style name="AppTheme" parent="BaseTheme">
<item name="android:textColor">?colorTextCustom</item>
</style>
复制代码
能够看到,这里在BaseTheme
中对colorTextCustom
属性赋值,并在 AppTheme
中经过“?colorTextCustom”
引用该属性值。因为是本地项目中定义的属性,因此没有添加 android:
命名空间。其实,这种作法的好处是,AppTheme
所覆盖的 View
都可经过构造函数获取当前主题中的 colorTextCustom
属性值。
引用自定义属性:?attr/colorPrimary 简写:?colorPrimary
引用系统属性:?android:attr/colorPrimary 简写:?android:colorPrimary
其中,自定义属性会复写系统属性