View繪製體系(三)——AttributeSet與TypedArray詳解
前言
上篇博客中講了LayoutInflater.inflate
機制,其中提到了AttributeSet
和XmlPullParser
兩個接口,這裏我們來詳細的瞭解一下Android中提供的AttributeSet
接口和它與XmlPullParser
的區別,以及如何使用TypedArray
獲取AttributeSet
中對應的屬性。
AttributeSet
AttributeSet
是xml文件中元素屬性的一個集合。其中提供了各種Api,供我們從已編譯好的xml文件獲取屬性值,如getAttributeIntValue
,getAttributeBooleanValue
,getAttributeFloatValue
等,會返回對應類型的屬性值,傳入的參數一般有兩種形式,如下:
getAttributeXXXValue(int index, XXX defaultValue)
:根據對應屬性的索引獲取對應的屬性值,index取值範圍在0~count-1之間,找不到返回defaultValue
getAttributeXXXValue(String namespace, String attribute, XXX defaultValue)
:根據指定命名空間的屬性名獲取對應的屬性值,找不到返回defaultValue
AttributeSet與XmlPullParser
我們現在知道了AttributeSet也是獲取xml文件中屬性值用的接口,那麼它和XmlPullParser有什麼關聯和區別呢?
我們先看下下面這張類圖:
XmlPullParser和AttributeSet都能從Xml文件中獲取數據,它們的相同點爲:
- 它們有一些重複的方法,如
getAttributeName
,getAttributeValue
等。
區別在於:
- AttributeSet提供了額外的一系列
getAttributeXXXValue
的方法(如getAttributeIntValue),這些方法能返回對應XXX的類型值(如int值),而XmlPullParser獲取屬性值只能通過getAttributeValue
方法,返回值只能是String類型 - AttributeSet接口從XmlPullParser接口中只保留了必要的方法,去除了next()等方法,並且新增了一些Android獨有的方法,可以說AttributeSet是Android獨有的用來獲取xml文件屬性的一個接口
需要注意的是,在Android中,對於AttributeSet接口和XmlPullParser接口,其實現都是結合已有的編譯好的xml資源,這些資源是編譯時經過aapt生成的高度優化的資源,而不是通過pull方式解析原有的Xml字符串。這與我們常見的一般的XmlPullParser接口的實現機制(kXml,WbXml等)不同。
我們從類圖中可以看到實現類有兩個,分別是Parser
和XmlPullAttributes
,我們可以看下XmlPullAttributes
的具體實現:
class XmlPullAttributes implements AttributeSet {
XmlPullParser mParser;
public XmlPullAttributes(XmlPullParser parser) {
mParser = parser;
}
public int getAttributeCount() {
return mParser.getAttributeCount();
}
public String getAttributeNamespace (int index) {
return mParser.getAttributeNamespace(index);
}
public String getAttributeName(int index) {
return mParser.getAttributeName(index);
}
public String getAttributeValue(int index) {
return mParser.getAttributeValue(index);
}
public String getAttributeValue(String namespace, String name) {
return mParser.getAttributeValue(namespace, name);
}
public String getPositionDescription() {
return mParser.getPositionDescription();
}
public int getAttributeNameResource(int index) {
return 0;
}
public int getAttributeListValue(String namespace, String attribute,
String[] options, int defaultValue) {
return XmlUtils.convertValueToList(
getAttributeValue(namespace, attribute), options, defaultValue);
}
//...其餘AttributeSet中的接口都是類似getAttributeListValue通過XmlUtils轉換類型來實現的
}
可以看到XmlPullAttributes對於AttributeSet中和XmlPullParser相同的接口都是通過內部的XmlPullParser變量來實現的。
XmlPullParser還可以通過以下方式生成對應的AttributeSet:
public static AttributeSet asAttributeSet(XmlPullParser parser) {
return (parser instanceof AttributeSet)
? (AttributeSet) parser
: new XmlPullAttributes(parser);
}
TypedArray
然而,我們很少用到AttributeSet類提供的方法來獲取它的屬性,常常都是通過Theme.obtainStyledAttributes方法來獲取其中的屬性的,方法如下:
public TypedArray obtainStyledAttributes(AttributeSet set, @StyleableRes int[] attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes)
獲取到TypedArray之後,再去調用TypedArray的Api來處理。
爲什麼不直接用AttributeSet來解析屬性值呢?
我們可以看下面這個xml佈局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="@dimen/w_50dp"
android:layout_height="@dimen/w_50dp"
android:orientation="vertical"/>
我們通過AttributeSet
直接去獲取屬性的話:
final XmlResourceParser parser = getResources().getLayout(R.layout.ll);
int type;
while ((type = parser.getEventType()) != XmlPullParser.START_TAG
&& type != XmlPullParser.END_DOCUMENT) {
parser.next();
}
int count = parser.getAttributeCount();
Log.d(TAG, "count: " + parser.getAttributeCount());
AttributeSet attributeSet = Xml.asAttributeSet(parser);
for (int i = 0; i < count; i++) {
String name = attributeSet.getAttributeName(i);
String value = attributeSet.getAttributeValue(i);
Log.d(TAG, "attribute: " + name + " value: " + value);
}
int resourceId = attributeSet.getAttributeResourceValue("http://schemas.android.com/apk/res/android", "layout_height", 0);
float dimension = getResources().getDimension(resourceId);
Log.d(TAG, "layout_height: " + dimension);
得到的結果如下:
count: 3
attribute: orientation value: 1
attribute: layout_width value: @2131034226
attribute: layout_height value: @2131034226
layout_height: 150.0 #得到的是px
可以看到如果直接使用AttributeSet
來獲取屬性值有以下缺點:
- 通過索引index獲取元素屬性頗爲麻煩,而且對於屬性的順序不熟悉的人很難將將index和屬性值一一對應;而通過命名空間來獲取,又要必須填入android的命名空間"http://schemas.android.com/apk/res/android",編寫起來都是不太方便的
- 對於"@String/hello_world"這一類通過索引資源文件獲取屬性值的,直接通過
getAttributeValue
獲得的只是資源的id,雖然可以通過getAttributeResourceValue
獲取到id後,通過Resources
類來獲取對應的屬性值,但是這樣會有多步的操作。
我們來看看使用TypedArray是如何獲取屬性值的。TypedArray是一個存放屬性值數組的一個容器,通常通過如下方式來獲得TypedArray的:
Resources.Theme.obtainStyledAttributes(AttributeSet set,
@StyleableRes int[] attrs,
@AttrRes int defStyleAttr,
@StyleRes int defStyleRes)
我們來分別看下每個參數的含義:
AttributeSet set
:表示從AttributeSet中挑選屬性,可以爲空int[] attrs
:表示你想挑選的屬性,你想得到哪些屬性,你就可以將其寫到這個int數組中int defStyleAttr
:表示從defStyleAttr中挑選屬性,可以爲空int defStyleRes
:表示從defStyleRes中挑選屬性,可以爲空
這裏我們先來演示下從AttributeSet
中挑選屬性:
TypedArray a = obtainStyledAttributes(attributeSet,
new int[] { android.R.attr.layout_width, android.R.attr.layout_height});
float width = a.getDimension(0, 0f); //第一個參數表示在int數組中的索引,第二個參數爲默認值
Log.d(TAG, "typedarray layout_width: " + width);
float height = a.getDimension(1, 0f); //第一個參數表示在int數組中的索引,第二個參數爲默認值
Log.d(TAG, "typedarray layout_width: " + height);
可以得到對應的值:
typedarray layout_width: 150.0
typedarray layout_width: 150.0
TypedArray提供了各種Api,如getInteger
,getString
,getDimension
等方法來獲取屬性值,這些方法都需要傳入對應屬性名在obtainStyledAttributes
中的int數組的位置索引。
對於TypedArray的分析,這裏就只講從AttributeSet中挑選屬性的用法,從defStyleAttr和defStyleRes中挑選屬性的用法詳見[View繪製體系(四)——TypedArray與自定義屬性]。
ps:對於AttributeSet與TypedArray的分析就到這裏,後續將會介紹TypedArray與自定義屬性的相關內容,敬請關注!