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有什么关联和区别呢?

我们先看下下面这张类图:

View绘制体系(三)——AttributeSet与TypedArray详解_第1张图片

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布局文件:


<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与自定义属性的相关内容,敬请关注!

你可能感兴趣的:(Android,View绘制)