View繪製體系(三)——AttributeSet與TypedArray詳解

View繪製體系(三)——AttributeSet與TypedArray詳解


前言

上篇博客中講了LayoutInflater.inflate機制,其中提到了AttributeSetXmlPullParser兩個接口,這裏我們來詳細的瞭解一下Android中提供的AttributeSet接口和它與XmlPullParser的區別,以及如何使用TypedArray獲取AttributeSet中對應的屬性。


AttributeSet

AttributeSet是xml文件中元素屬性的一個集合。其中提供了各種Api,供我們從已編譯好的xml文件獲取屬性值,如getAttributeIntValuegetAttributeBooleanValuegetAttributeFloatValue等,會返回對應類型的屬性值,傳入的參數一般有兩種形式,如下:

  • 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

XmlPullParser和AttributeSet都能從Xml文件中獲取數據,它們的相同點爲:

  • 它們有一些重複的方法,如getAttributeNamegetAttributeValue等。

區別在於:

  • 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等)不同。

我們從類圖中可以看到實現類有兩個,分別是ParserXmlPullAttributes,我們可以看下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,如getIntegergetStringgetDimension等方法來獲取屬性值,這些方法都需要傳入對應屬性名在obtainStyledAttributes中的int數組的位置索引。

對於TypedArray的分析,這裏就只講從AttributeSet中挑選屬性的用法,從defStyleAttr和defStyleRes中挑選屬性的用法詳見[View繪製體系(四)——TypedArray與自定義屬性]。

ps:對於AttributeSet與TypedArray的分析就到這裏,後續將會介紹TypedArray與自定義屬性的相關內容,敬請關注!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章