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;
时间: 2024-10-21 23:55:02

Android Layout Inflate分析(3) - 深入Layout XML属性的相关文章

Android Layout inflate分析(2) - ViewGroup对象的构造

UI控件 ViewGroup ViewGroup实现了ViewManager和ViewParent两个接口. @UiThread public abstract class ViewGroup extends View implements ViewParent, ViewManager { ViewManager public interface ViewManager { public void addView(View view, ViewGroup.LayoutParams params

Android 布局文件Layout XML属性_Android

Layout对于迅速的搭建界面和提高界面在不同分辨率的屏幕上的适应性具有很大的作用.这里简要介绍Android的Layout和研究 一下它的实现. Android有Layout:FrameLayout,LinearLayout,TableLayout,RelativeLayout,AbsoluteLayout. 放入Layout中进行排布的View的XML属性: 几种Layout中Item所共有的XML属性:  (1)layout_width  (2)layout_height  注: (1)和

Android 布局文件Layout XML属性

Layout对于迅速的搭建界面和提高界面在不同分辨率的屏幕上的适应性具有很大的作用.这里简要介绍Android的Layout和研究 一下它的实现. Android有Layout:FrameLayout,LinearLayout,TableLayout,RelativeLayout,AbsoluteLayout. 放入Layout中进行排布的View的XML属性: 几种Layout中Item所共有的XML属性: (1)layout_width (2)layout_height 注: (1)和(2)

Android Layout inflate过程分析(1)

综述 在aapt编译apk的过程中,aapt中的XMLNode类会将资源生成一个ResXMLTree对象,并将其序列化到apk文件中. Android系统中,首先用C++实现了ResXMLParser类,用来解析存储在apk中的ResXMLTree.然后用Java封装了一个XmlBlock对象,通过JNI方法调用ResXMLParser. XmlBlock.Parser类是一个XmlResourceParser接口的实现.XmlResourceParser接口继承自XmlPullParser接口

Android开发者指南(26) —— Resource Types - Layout

前言 本章内容为Android开发者指南的Framework Topics/Application Resources/Resource Types/Layout章节,译为"布局资源",版本为Android 3.2 r1,翻译来自:"呆呆大虾",欢迎访问他的微博:"http://weibo.com/popapa",再次感谢"呆呆大虾" !期待你一起参与翻译Android的相关资料,联系我over140@gmail.com.  

android layout-菜鸟的用Android创建的project中的layout文件夹是空的怎么回事啊?

问题描述 菜鸟的用Android创建的project中的layout文件夹是空的怎么回事啊? 菜鸟的用Android创建的project中的layout文件夹是空的怎么回事啊?而且src包也是空的,怎么回事啊? 解决方案 我最近也遇到了同样的问题,你的有解决了么 解决方案二: 要不要截个图看看...按照新建工程的向导来做,不应该出现此情况的.file->new->android application progect ->输入包名 progect名字..->next next...

Android自定义视图一:扩展现有的视图,添加新的XML属性

这个系列是老外写的,干货!翻译出来一起学习.如有不妥,不吝赐教! Android自定义视图一:扩展现有的视图,添加新的XML属性 Android自定义视图二:如何绘制内容 Android自定义视图三:给自定义视图添加"流畅"的动画 Android自定义视图四:定制onMeasure强制显示为方形 简介 这个系列详细的介绍了如何穿件Android自定义视图.主要涉及的内容有如何绘制内容,layout和measure的原理,如何继承实现view group以及如何给其子视图添加动画.第一篇

android 编译 inflate错误,很奇怪,求助!!!

问题描述 android 编译 inflate错误,很奇怪,求助!!! 部分出错代码 错误信息 不知道什么原因,求大神指点 解决方案 的确比较奇怪啊,后面还是加个参数,false吧 解决方案二: 你是用eclipse编辑的吗?看错误log是null.equals(obj)的crash 建议你先eclipse clean下在编 解决方案三: 我用了你的代码,没有出现问题.我一般使用View的inflate()方法,你可以试一下: convertView = View.inflate(mContex

Android Handler 原理分析及实例代码

Android Handler 原理分析 Handler一个让无数android开发者头疼的东西,希望我今天这边文章能为您彻底根治这个问题 今天就为大家详细剖析下Handler的原理 Handler使用的原因 1.多线程更新Ui会导致UI界面错乱 2.如果加锁会导致性能下降 3.只在主线程去更新UI,轮询处理 Handler使用简介 其实关键方法就2个一个sendMessage,用来接收消息 另一个是handleMessage,用来处理接收到的消息 下面是我参考疯狂android讲义,写的一个子