今天我们要把drawable下的selector的XML文件转换成Java代码。(打包进jar,不依赖apk)

在转换工具中的代码为:

https://github.com/SickWorm/AndroidXMLToJava/blob/master/src/com/excelsecu/androidx2j/SelectorTranslator.java

Selector是什么?就是给Button等控件使用的一个根据状态改变控件颜色或背景的状态器,它一般放在drawable目录下。

Selector分两种,一种是指定color和alpha的状态器,XML形式如下:

 <selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="true" android:color="@color/testcolor1"/>
<item android:state_pressed="true" android:state_enabled="false" android:color="@color/testcolor2" />
<item android:state_enabled="false" android:color="@color/testcolor3" />
<item android:color="@color/testcolor5" android:alpha="0.5" />
</selector>

另一种是指定drawable的状态器,XML形式如下:

 <selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="true" android:drawable="@drawable/testdrawable1"/>
<item android:state_pressed="true" android:state_enabled="false" android:drawable="@drawable/testdrawable2" />
<item android:state_enabled="false" android:drawable="@drawable/testdrawable3" />
<item android:drawable="@color/testdrawable5"/>
</selector>

知道了XML格式,我们如何去找到对应实现的类呢?这时候就应该上万能的http://developer.android.com/(科学上网,我用红杏我自豪)寻找selector。点击第一个链接:

http://developer.android.com/guide/topics/resources/color-list-resource.html

在这个文档上我们可以明显看到第一个XML对应的类就是ColorStateList(里面介绍的第一个就是)。而第二个XML,我一开始找了很久都没找到,直到我点开文档最下方的State List Drawable,发现赫然写着:

StateListDrawable(原谅我激动的心情)

点进这个类看到对应的XML布局,确定这就是第二个XML布局啦。

找到了对应的类,接下来要开始正式的工作了。我们先从ColorStateList下手。

所谓转换,其实就是构造一个对应类的对象,然后把XML的属性对应的内容用Java代码的方式“填充”进这个对象中便完成了。在使用到这个XML的地方,也用对应的设置方法把该成员设置进去就可以了。

那首先找到ColorStateL的构造函数:ColorStateList(int[][] states, int[] colors);

一开始便难倒我了。。这诡异的两个int数组是shenmegui?看看人家控件家族(Button, TextView等等)只需要一个context就可以构造了。根据名字猜测,这两个数组估计是对应的各种状态和颜色。那我们接下去认证这个猜想。

说时迟那时快,我发现了另一个函数:createFromXml(Resources r, XmlPullParser parser);

该函数就是给我们用Java代码动态解析XML用的方法。里面肯定有对XML各参数解析的过程,赶紧看一下源代码(作为一个开源操作系统的程序猿,一定要下一份源码)。

    /**
* Create a ColorStateList from an XML document, given a set of {@link Resources}.
*/
public static ColorStateList createFromXml(Resources r, XmlPullParser parser)
throws XmlPullParserException, IOException {
final AttributeSet attrs = Xml.asAttributeSet(parser); int type;
while ((type=parser.next()) != XmlPullParser.START_TAG
&& type != XmlPullParser.END_DOCUMENT) {
} if (type != XmlPullParser.START_TAG) {
throw new XmlPullParserException("No start tag found");
} return createFromXmlInner(r, parser, attrs);
}

我们跟着这个XmlPullparser走,因为他里面藏着XML信息。结果发现他调用了createFromXmlInner。继续跟,发现调用了colorStateList.inflate。看inflate就知道是解析函数啦!那我们来详细分析这个inflate:

(下面是我分析inflate函数时的笔记,应该算比较详细了)

    /**
* Fill in this object based on the contents of an XML "selector" element.
*/
//解析XmlPullparser, (剧透)最终得到 mColors 和 mStateSpecs, 这两个参数用于构造ColorStateList
private void inflate(Resources r, XmlPullParser parser, AttributeSet attrs)
throws XmlPullParserException, IOException {
int type; final int innerDepth = parser.getDepth()+1;
int depth; int[][] stateSpecList = ArrayUtils.newUnpaddedArray(int[].class, 20);
int[] colorList = new int[stateSpecList.length];
int listSize = 0; while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
&& ((depth=parser.getDepth()) >= innerDepth
|| type != XmlPullParser.END_TAG)) {
if (type != XmlPullParser.START_TAG) {
continue;
}
//只解析item项
if (depth > innerDepth || !parser.getName().equals("item")) {
continue;
} int alphaRes = 0;
float alpha = 1.0f;
int colorRes = 0;
int color = 0xffff0000;
boolean haveColor = false; int i;
int j = 0;
final int numAttrs = attrs.getAttributeCount();
int[] stateSpec = new int[numAttrs];
//对item项的各个属性做分析
for (i = 0; i < numAttrs; i++) {
//获取属性名对应的id, 如果没有则返回0, 其实就是把字符匹配的工作交出去, 提高代码灵活性
final int stateResId = attrs.getAttributeNameResource(i);
if (stateResId == 0) break;
//如果是alpha属性则赋值alpha. alpha是一个透明度的值
if (stateResId == com.android.internal.R.attr.alpha) {
//获取属性值对应的资源id, 就是我们熟悉的R.java里面的东西啦, 比如R.drawable.button1 = 0x7f040001这样的值
alphaRes = attrs.getAttributeResourceValue(i, 0);
//如果不是资源id, 是常数, 则直接翻译
if (alphaRes == 0) {
alpha = attrs.getAttributeFloatValue(i, 1.0f);
}
//如果是color属性, 也做类似的处理. color就是对应指定状态下的背景或颜色
} else if (stateResId == com.android.internal.R.attr.color) {
colorRes = attrs.getAttributeResourceValue(i, 0);
if (colorRes == 0) {
color = attrs.getAttributeIntValue(i, color);
haveColor = true;
}
//不是上面两个条件的话, 只能是设置状态的布尔值了, 如android:state_pressed="true"
} else {
//此时会把属性名对应的id存进数组, 并根据是否设置正负, 这样selector的各种状态条件就构建出来了
//具体的id值是多少, 是R.attr中的属性值吗(如state_pressed就是android.R.attr.state_pressed)?我们下面再仔细分析
stateSpec[j++] = attrs.getAttributeBooleanValue(i, false)
? stateResId : -stateResId;
}
}
//这个trimStateSet的用处是重新指定此int数组的长度, 也就是j, stateSpec初始化的时候是使用了属性数量作为长度, 在这里会被重新设置, 以节省空间.
//因为上一条语句会做j++, 所以永远空出一个位置给下一个属性添加
//数组很小, 所以不会很费时间, 牺牲一点性能换取简单的写法也是可取的. 如果是我我会在循环开始的时候先找出有多少个设置状态的属性.
stateSpec = StateSet.trimStateSet(stateSpec, j); //这里对刚才获取的资源值做获取, 并做一些结构完整性检查, 如果一个item连color都没有, 那真是坑爹了
if (colorRes != 0) {
color = r.getColor(colorRes);
} else if (!haveColor) {
throw new XmlPullParserException(
parser.getPositionDescription()
+ ": <item> tag requires a 'android:color' attribute.");
} //alpha就可有可无了, 如果没有, 默认是1 (初始化的值float alpha = 1.0f;), 也就是100%不透明
if (alphaRes != 0) {
alpha = r.getFloat(alphaRes);
} //稍微转换一下, 把color算出来
// Apply alpha modulation.
//算出alpha. Color.alpha(color)看源码, 就是 color >>> 24. 这个运算是算出color的alpha值 (int color 长度为4个字节, 3个字节分别代表RGB, 还有一个最高位字节的alpha喔)
//比如#AAFFFFFF的alpha值就是AA. 但要注意的是#FFFFFF的alpha值是FF, 因为Android的Color.parse会自动给没填写alpha的值的最高位填上FF(具体看源代码).
       //如果你使用int形式, 如0xaaaaaa, alpha值就是0, 这样写是会全透明的. 用int形式一定要写上alpha值, 如全黑是0xff000000.
//所以一个item真正的alpha值是由color自带的alpha和android:alpha的值共同决定的
//MathUtils.constrain这个函数不用看都能知道是百分比缩放的函数, 比如0.5, 0, 255, 的出来的就是(255 - 0) * 0.5
final int alphaMod = MathUtils.constrain((int) (Color.alpha(color) * alpha), 0, 255);
//再把这个0~255的值塞回color的最高字节里面去 (折腾啊)
color = (color & 0xFFFFFF) | (alphaMod << 24); //如果只有这个android:color这个属性, 没有表示状态的属性, 就认为他是默认色
if (listSize == 0 || stateSpec.length == 0) {
mDefaultColor = color;
} //把color 和 stateSpec塞进数组里面去. 比用ArrayList要节省点空间. 看来Android在节省内存方面上很卖力啊
//append能猜到又是重新定义数组长度这种事情, 不同的是会加一个数组进去
colorList = GrowingArrayUtils.append(colorList, listSize, color);
stateSpecList = GrowingArrayUtils.append(stateSpecList, listSize, stateSpec);
listSize++;
} //所有的item都跑完了, 可以把结果存给全局变量mColors 和 mStateSpecs了.
mColors = new int[listSize];
mStateSpecs = new int[listSize][];
System.arraycopy(colorList, 0, mColors, 0, listSize);
System.arraycopy(stateSpecList, 0, mStateSpecs, 0, listSize);
}

我们现在来看下ColorStateList的构造函数:

    public ColorStateList(int[][] states, int[] colors) {
mStateSpecs = states;
mColors = colors; if (states.length > 0) {
mDefaultColor = colors[0]; for (int i = 0; i < states.length; i++) {
if (states[i].length == 0) {
mDefaultColor = colors[i];
}
}
}
}

inflate得到的两个最终产物 mStateSpecs 和 mColors 就是我们想要的构造函数!我们只要照葫芦画瓢,照inflate画数组,就可以把这两个数组写出来了。ColorStateList被降服了!

喔,忘了还有一个遗留问题:

穿山甲到底说了什么?

stateResId到底是什么?

再看分析:

//首先这个id是从AttributeSet中得到的,AttributeSet是一个interface,实现它的是XmlPullAttributes(为什么?因为搜遍了源码就他一个...),但可惜的是XmlPullAttributes没有实现getAttributeNameResource()
//继续找儿子。BridgeXmlPullAttributes 继承了XmlPullAttributes,而且他实现了getAttributeNameResource()
BridgeXmlPullAttributes extends XmlPullAttributes
//This methods must return com.android.internal.R.attr.<name> matching the name of the attribute.
BridgeXmlPullAttributes.getAttributeNameResource(int index)
-> Bridge.getResourceId(ResourceType.ATTR, name);
->-> Integer v = Bridge.getResourceId(ResourceType.ATTR, name);
->->-> sRevRMap.get(type);
if (v != null) {
return v.intValue();
}
//这里写的有点乱,“->”的意思是深入一层,同样的梯度表示在同一个函数中,也就是说getAttributeNameResource调用了Bridge.getResourceId调用了sRevRMap.get,最后Bridge.getResourceId返回了sRevRMap.get的int值。
//这个sRevRMap是在哪初始化的呢?搜源码发现在init中()
init(...) {
...
Class<?> r = com.android.internal.R.class;
//这里这里!init函数把这个class里面的所有子类声明的值put进去sRevRMap了。
//这个com.android.internal.R.class是在Android ROM编译的时候生成的一个类,就类似我们写应用时自动生成的R.java,里面全部是声明的值。
//而我们getResourceId的时候传的type是ResourceType.ATTR。所以就是ATTR的id值。这个值在http://developer.android.com/reference/android/R.attr.html中有详细列表。
//实际上我们使用只需要给对应的属性名加上前缀android.R.attr即可。如android:state_pressed对应android.R.attr.state_pressed。
//至于为什么初始化的时候是com.android.internal.R,使用的时候是android.R,应该是涉及到Android系统编译的东西,我没有深入了解。 for (Class<?> inner : r.getDeclaredClasses()) {
String resTypeName = inner.getSimpleName();
ResourceType resType = ResourceType.getEnum(resTypeName);
if (resType != null) {
Map<String, Integer> fullMap = new HashMap<String, Integer>();
sRevRMap.put(resType, fullMap); for (Field f : inner.getDeclaredFields()) {
// only process static final fields. Since the final attribute may have
// been altered by layoutlib_create, we only check static
int modifiers = f.getModifiers();
if (Modifier.isStatic(modifiers)) {
Class<?> type = f.getType();
if (type.isArray() && type.getComponentType() == int.class) {
// if the object is an int[] we put it in sRArrayMap using an IntArray
// wrapper that properly implements equals and hashcode for the array
// objects, as required by the map contract.
sRArrayMap.put(new IntArray((int[]) f.get(null)), f.getName());
} else if (type == int.class) {
Integer value = (Integer) f.get(null);
sRMap.put(value, Pair.of(resType, f.getName()));
fullMap.put(f.getName(), value);
} else {
assert false;
}
}
}
}
}
...
}

这里“com.android.internal.R,使用的时候是android.R”,说服力可能不足,但这个表http://developer.android.com/reference/android/R.attr.html应该是可靠的,Google不会做两个表出来吧。。

是com.android.internal.R,使用的时候是android.R,

接下来还有一个

StateListDrawable(怎么还是这么大)

这个东西就简单的多了,构造器不需要参数,直接new StateListDrawable()即可。添加状态使用addState(int[] stateSet, Drawable drawable)。stateSet和ColorStateList的规则是一样的,drawable就是XML文件里面指定的drawable资源。这里就不多赘述了。

注意:

虽然ColorStateList对应的selector XML放在drawable文件夹里面,但他本身是一个自定义类!父类是Object!和

StateListDrawable(啊啊啊啊吓死我了)

不一样,这货父类是Drawable,可以用getDrawable()获取。如果你用getDrawable(R.drawable.colorStateList)获取对应资源的话是会崩溃的。为什么要提到这点呢?因为转换工具(详细介绍看第一篇文章)有一个仿照getResources()专门管理资源的类(叫AXMLResources),因为这个ColorStateList的特殊原因,就要另外用函数处理了。

版权所有,转载请注明出处:

http://www.cnblogs.com/sickworm/p/4245210.html

 

【Android XML】Android XML 转 Java Code 系列之 Selector(2)的更多相关文章

  1. 【Android XML】Android XML 转 Java Code 系列之 style(3)

    最近一个月把代码重构了一遍, 感觉舒服多了, 但总体开发进度没有变化.. 今天聊聊把style属性转换成Java代码的办法 先说结论: 引用系统style是无法完美的实现的, 我们如果有写成Java代 ...

  2. 【Android XML】Android XML 转 Java Code 系列之 介绍(1)

    最近在公司做一个项目,需要把Android界面打包进jar包给客户使用.对绝大部分开发者来说,Android界面的布局以XML文件为主,并辅以少量Java代码进行动态调整.而打包进jar包的代码,意味 ...

  3. [置顶] Android学习系列-Android中解析xml(7)

    Android学习系列-Android中解析xml(7) 一,概述 1,一个是DOM,它是生成一个树,有了树以后你搜索.查找都可以做. 2,另一种是基于流的,就是解析器从头到尾解析一遍xml文件.   ...

  4. Android(java)学习笔记187:Android中操作XML数据(使用Pull解析器)

    1. Pull解析器的运行方式与 SAX 解析器相似.它提供了类似的事件,如:开始元素和结束元素事件,使用parser.next()可以进入下一个元素并触发相应事件.跟SAX不同的是, Pull解析器 ...

  5. [Android] Android Build 时报错: java.io.IOException: Could not parse XML from android/accounts/annotations.xml

    Android构建时报错: app:lintVitalRelease[Fatal Error] :3:214: 与元素类型 “item” 相关联的 “name” 属性值不能包含 ‘<’ 字符. ...

  6. Android(java)学习笔记130:Android中操作XML数据(使用Pull解析器)

    1. Pull解析器的运行方式与 SAX 解析器相似.它提供了类似的事件,如:开始元素和结束元素事件,使用parser.next()可以进入下一个元素并触发相应事件.跟SAX不同的是, Pull解析器 ...

  7. 【Android】实现XML解析的几种技术

    本文介绍在Android平台中实现对XML的三种解析方式. XML在各种开发中都广泛应用,Android也不例外.作为承载数据的一个重要角色,如何读写XML成为Android开发中一项重要的技能. 在 ...

  8. android关于AndroidManifest.xml详细分析

    http://my.eoe.cn/1087692/archive/5927.html 一.关于AndroidManifest.xmlAndroidManifest.xml 是每个android程序中必 ...

  9. Android动画解析--XML

    动画类型 Android的animation由四种类型组成 XML中 alpha 渐变透明度动画效果 scale 渐变尺寸伸缩动画效果 translate 画面转换位置移动动画效果 rotate 画面 ...

随机推荐

  1. JDK各个版本比较 JDK5~JDK9

    JDK5 自动装箱与拆箱: 枚举 静态导入,如:import staticjava.lang.System.out 可变参数(Varargs) 内省(Introspector),主要用于操作JavaB ...

  2. C#下载网页

    System.Net.WebClient wc = new System.Net.WebClient(); Byte[] pageData = wc.DownloadData("网页地址&q ...

  3. BZOJ 2303 方格染色(带权并查集)

    要使得每个2*2的矩形有奇数个红色,如果我们把红色记为1,蓝色记为0,那么我们得到了这2*2的矩形里的数字异或和为1. 对于每个方格则有a(i,j)^a(i-1,j)^a(i,j-1)^a(i-1,j ...

  4. android应用打前需要准备些啥?

    发布之前我们需要准备的东西 参考了google官方和结合国内开发经验 1) 用户协议(本地.网络.API) 2) 签名文件(用于为APP加密,唯一标签) 3) 程序图标(第一个面对用户,准备不同的但合 ...

  5. Codeforces ECR52 div2翻车记

    A:签到. #include<iostream> #include<cstdio> #include<cmath> #include<cstdlib> ...

  6. Necklace - CF613C

    Ivan wants to make a necklace as a present to his beloved girl. A necklace is a cyclic sequence of b ...

  7. [CF1083B]The Fair Nut and Strings

    题目大意:在给定的长度为$n(n\leqslant5\times10^5)$的字符串$A$和字符串$B$中找到最多$k$个字符串,使得这$k$个字符串不同的前缀字符串的数量最多(只包含字符$a$和$b ...

  8. SPOJ - PHRASES

    题意: 给n个字符串,求出最长的子串.使得子串在每个字符串中不重叠地至少出现2次.输出子串长度. 题解: 用后缀数组求出height数组,之后二分答案.check时对height数组进行分组,并维护每 ...

  9. POJ2942:Knights of the Round Table——题解

    http://poj.org/problem?id=2942 所写的tarjan练习题最难的一道. 说白了难在考得不是纯tarjan. 首先我们把仇恨关系处理成非仇恨关系的图,然后找双连通分量,在双连 ...

  10. CF449C:Jzzhu and Apples——题解

    https://vjudge.net/problem/CodeForces-449C 题目大意:1-n编号的苹果两两一对,他们的最大公约数不为1,求这些对的最大匹配. ———————————————— ...