Android Layout Inflate分析(3) - 深入Layout XML属性

深入Android Layout XML属性

前面我们的XmlPullParser解析xml的简要教程中, 我们对于Android是如何解析Layout XML的过程有了直观的理解, 我们也分析了inflate的详细过程. 另外我们还开始研究控件的构造过程,大家对于AttributeSet, TypedArray等结构也有了一些了解. 不过有同学反映还是隔靴搔痒,还是缺少足够深入的理解. 所以我们继续做一个从摇篮到坟墓的教程.

XmlPullParser读取属性快餐教程

前面我们学习了XmlPullParser读取XML的过程, 在获取了TAG事件后,比如在处理XmlPullParser.START_TAG事件的时候,我们就可以将属性也一起处理掉了.

XmlPullParser接口属性支持相关的方法

主要用下面4个方法:

  • int getAttributeCount(); 获取一个有多少个属性
  • String getAttributeName (int index); 获取第index属性的名字
  • String getAttributeValue(int index); 获取第index属性的值
  • String getAttributeValue(String namespace,String name); 根据属性的名字获取值

但是XmlPullParser的API不足在于:一是还需要做类型转换,二是还跟资源相关的还需要另行处理.
于是Android就新引入了一个AttributeSet接口,专门处理属性相关的功能.

我们下面写一个小例子遍历这个标签的所有属性的代码来看一下:

    public static void logAttribute(XmlPullParser xmlPullParser) {
        AttributeSet a = Xml.asAttributeSet(xmlPullParser);
        logAttribute(a);
    }

    public static void logAttribute(AttributeSet a) {
        int attrCount = a.getAttributeCount();
        Log.d(TAG, "[Xulun]Attribute count=" + attrCount);

        if (attrCount > 0) {
            for (int i = 0; i < attrCount; i++) {
                Log.d(TAG, "[Xulun]Attribute[" + i + "]name=" + a.getAttributeName(i));
                Log.d(TAG, "[Xulun]Attribute[" + i + "]value=" + a.getAttributeValue(i));
            }
        }
    }

上面的代码我分成两个方法的原因在于正好分别说明要用到的两个步骤.

Xml.asAttributeSet

这个方法在前面我们曾经简要介绍过, 这里再重温一下:

175    public static AttributeSet asAttributeSet(XmlPullParser parser) {
176        return (parser instanceof AttributeSet)
177                ? (AttributeSet) parser
178                : new XmlPullAttributes(parser);
179    }

前面之所以没有展开讲, 是因为我们说过, 解析资源所用的是XmlResourceParser,同时继承了XmlPullParser和AttributeSet, 所以走177行那一支, 直接转换一下接口类型就可以用了. 而我们这次讲一下第二个分支, 通过XmlPullAttributes类来作为桥,转换一下.

XmlPullAttributes

我们选取一些关键点看一下,其余的以此类推.

构造

28class XmlPullAttributes implements AttributeSet {
29    public XmlPullAttributes(XmlPullParser parser) {
30        mParser = parser;
31    }

因为就是做一个中间转换的工具,其核心还是一个XmlPullParser接口的对象.

方法的封装

上面我们介绍的4个方法,AttributeSet是同样的接口,所以原封不动地调用就好了.

33    public int getAttributeCount() {
34        return mParser.getAttributeCount();
35    }
36
37    public String getAttributeName(int index) {
38        return mParser.getAttributeName(index);
39    }
40
41    public String getAttributeValue(int index) {
42        return mParser.getAttributeValue(index);
43    }
44
45    public String getAttributeValue(String namespace, String name) {
46        return mParser.getAttributeValue(namespace, name);
47    }

另外还有一个有用的方法,是在出错时可以看到是在xml的哪一行:

49    public String getPositionDescription() {
50        return mParser.getPositionDescription();
51    }

带类型转换的方法

先把值读出来,然后再通过XmlUtils或者其他方法实现类型的转换. 这样可以方便调用者,省得像直接用XmlPullParser接口,还得自己转一次.
例:

102    public boolean getAttributeBooleanValue(int index, boolean defaultValue) {
103        return XmlUtils.convertValueToBoolean(
104            getAttributeValue(index), defaultValue);
105    }

XmlUtils是一个Android的内部类,类型转换这些没什么值得多说的,例行公事.

74    public static final boolean
75    convertValueToBoolean(CharSequence value, boolean defaultValue)
76    {
77        boolean result = false;
78
79        if (null == value)
80            return defaultValue;
81
82        if (value.equals("1")
83        ||  value.equals("true")
84        ||  value.equals("TRUE"))
85            result = true;
86
87        return result;
88    }

资源相关的方法

由于id, class, style三种属性被用得太多了, 于是专门为它们定义了命名的方法.

130    public String getIdAttribute() {
131        return getAttributeValue(null, "id");
132    }
133
134    public String getClassAttribute() {
135        return getAttributeValue(null, "class");
136    }
137
138    public int getIdAttributeResourceValue(int defaultValue) {
139        return getAttributeResourceValue(null, "id", defaultValue);
140    }
141
142    public int getStyleAttribute() {
143        return getAttributeResourceValue(null, "style", 0);
144    }

后面两个方法是对getAttributeResourceValue方法的封装,也只是将资源ID转换成int型.

69    public int getAttributeResourceValue(String namespace, String attribute,
70            int defaultValue) {
71        return XmlUtils.convertValueToInt(
72            getAttributeValue(namespace, attribute), defaultValue);
73    }

最后还有一个方法通过XmlPullParser无法实现的, 这个要求的是编译后的属性对应的属性名字. 这个没办法,不是通用接口可以处理的问题, 需要去查询ResXMLTree了.

53    public int getAttributeNameResource(int index) {
54        return 0;
55    }
56

TypedArray

资源值存储到TypedArray中

下面我们回到真实的资源世界,再重温一下我们之前看过的View的构造时对AttributeSet的使用:

3897    public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
3898        this(context);
3899
3900        final TypedArray a = context.obtainStyledAttributes(
3901                attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
3902
...
3949        final int N = a.getIndexCount();
3950        for (int i = 0; i < N; i++) {
3951            int attr = a.getIndex(i);
3952            switch (attr) {
3953                case com.android.internal.R.styleable.View_background:
3954                    background = a.getDrawable(attr);
3955                    break;
...

Context.obtainStyledAttributes我们上一节已经分析过了, 复习一下,它会调用Themer的obtainStyledAttributes. 我们看看针对编译后的资源,这些属性是如何被使用的:

1593        public TypedArray obtainStyledAttributes(AttributeSet set,
1594                @StyleableRes int[] attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
1595            final int len = attrs.length;
1596            final TypedArray array = TypedArray.obtain(Resources.this, len);

被编译之后,XML文件已经成为一些二进制的块, 我们先把这些属性复制到TypedArray对象中. len是这个标签所对应的属性有多少项,我们不关心都只什么,知道有多少项就交给TypedArray的obtain方法去构造一个TypedArray对象.
需要特别注意的是, TypedArray对象是从对象池中申请的, 用完一定要释放掉,方法是通过TypedArray对象的recycle()方法.
一句话描述,obtain这一步仅仅是分配一个空的TypedArray对象.

43    static TypedArray obtain(Resources res, int len) {
44        final TypedArray attrs = res.mTypedArrayPool.acquire();
45        if (attrs != null) {
46            attrs.mLength = len;
47            attrs.mRecycled = false;
48
49            final int fullLen = len * AssetManager.STYLE_NUM_ENTRIES;
50            if (attrs.mData.length >= fullLen) {
51                return attrs;
52            }
53
54            attrs.mData = new int[fullLen];
55            attrs.mIndices = new int[1 + len];
56            return attrs;
57        }
58
59        return new TypedArray(res,
60                new int[len*AssetManager.STYLE_NUM_ENTRIES],
61                new int[1+len], len);
62    }

我们回到obtainStyledAttributes中, 现在开始填充这个TypedArray对象的值了,返回值填充到array.mData和array.mIndices中.

...
1602            final XmlBlock.Parser parser = (XmlBlock.Parser)set;
1603            AssetManager.applyStyle(mTheme, defStyleAttr, defStyleRes,
1604                    parser != null ? parser.mParseState : 0, attrs, array.mData, array.mIndices);
1605
1606            array.mTheme = this;
1607            array.mXml = parser;
...

不出意外地,这是个native方法, 又要去读ResXMLTree了.

2136    { "applyStyle","(JIIJ[I[I[I)Z",
2137        (void*) android_content_AssetManager_applyStyle },

这个240多行的大函数我们后面再详细分析.

1265static jboolean android_content_AssetManager_applyStyle(JNIEnv* env, jobject clazz,
1266                                                        jlong themeToken,
1267                                                        jint defStyleAttr,
1268                                                        jint defStyleRes,
1269                                                        jlong xmlParserToken,
1270                                                        jintArray attrs,
1271                                                        jintArray outValues,
1272                                                        jintArray outIndices)

到这一步为止,值就填充完毕了.

使用TypedArray

我们还是回到前面不只一次看到的View类的初始化:
a是TypedArray对象.

...
3949        final int N = a.getIndexCount();
3950        for (int i = 0; i < N; i++) {
3951            int attr = a.getIndex(i);
3952            switch (attr) {
3953                case com.android.internal.R.styleable.View_background:
3954                    background = a.getDrawable(attr);
3955                    break;
...

TypedArray是容器, 通过getIndexCount()进行遍历. 下面就看业务需求了, 比如我们需要ID,就去读ID:

4038                case com.android.internal.R.styleable.View_id:
4039                    mID = a.getResourceId(attr, NO_ID);
4040                    break;

你可能感兴趣的:(android,xml,布局)