對於自定義屬性,遵循如下幾步,就能夠實現: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
其中,自定義屬性會複寫系統屬性