【Android XML】Android XML 转 Java Code 系列之 Selector(2)
今天我们要把drawable下的selector的XML文件转换成Java代码。(打包进jar,不依赖apk)
在转换工具中的代码为:
Selector是什么?就是给Button等控件使用的一个根据状态改变控件颜色或背景的状态器,它一般放在drawable目录下。
Selector分两种,一种是指定color和alpha的状态器,XML形式如下:
<span style="color: #0000ff;"><</span><span style="color: #800000;">selector </span><span style="color: #ff0000;">xmlns:android</span><span style="color: #0000ff;">="http://schemas.android.com/apk/res/android"</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">item </span><span style="color: #ff0000;">android:state_focused</span><span style="color: #0000ff;">="true"</span><span style="color: #ff0000;"> android:color</span><span style="color: #0000ff;">="@color/testcolor1"</span><span style="color: #0000ff;">/></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">item </span><span style="color: #ff0000;">android:state_pressed</span><span style="color: #0000ff;">="true"</span><span style="color: #ff0000;"> android:state_enabled</span><span style="color: #0000ff;">="false"</span><span style="color: #ff0000;"> android:color</span><span style="color: #0000ff;">="@color/testcolor2"</span> <span style="color: #0000ff;">/></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">item </span><span style="color: #ff0000;">android:state_enabled</span><span style="color: #0000ff;">="false"</span><span style="color: #ff0000;"> android:color</span><span style="color: #0000ff;">="@color/testcolor3"</span> <span style="color: #0000ff;">/></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">item </span><span style="color: #ff0000;">android:color</span><span style="color: #0000ff;">="@color/testcolor5"</span><span style="color: #ff0000;"> android:alpha</span><span style="color: #0000ff;">="0.5"</span> <span style="color: #0000ff;">/></span> <span style="color: #0000ff;"></</span><span style="color: #800000;">selector</span><span style="color: #0000ff;">></span>
123456 |
<span style="color: #0000ff;"><</span><span style="color: #800000;">selector </span><span style="color: #ff0000;">xmlns:android</span><span style="color: #0000ff;">="http://schemas.android.com/apk/res/android"</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">item </span><span style="color: #ff0000;">android:state_focused</span><span style="color: #0000ff;">="true"</span><span style="color: #ff0000;"> android:color</span><span style="color: #0000ff;">="@color/testcolor1"</span><span style="color: #0000ff;">/></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">item </span><span style="color: #ff0000;">android:state_pressed</span><span style="color: #0000ff;">="true"</span><span style="color: #ff0000;"> android:state_enabled</span><span style="color: #0000ff;">="false"</span><span style="color: #ff0000;"> android:color</span><span style="color: #0000ff;">="@color/testcolor2"</span> <span style="color: #0000ff;">/></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">item </span><span style="color: #ff0000;">android:state_enabled</span><span style="color: #0000ff;">="false"</span><span style="color: #ff0000;"> android:color</span><span style="color: #0000ff;">="@color/testcolor3"</span> <span style="color: #0000ff;">/></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">item </span><span style="color: #ff0000;">android:color</span><span style="color: #0000ff;">="@color/testcolor5"</span><span style="color: #ff0000;"> android:alpha</span><span style="color: #0000ff;">="0.5"</span> <span style="color: #0000ff;">/></span> <span style="color: #0000ff;"></</span><span style="color: #800000;">selector</span><span style="color: #0000ff;">></span> |
---|
另一种是指定drawable的状态器,XML形式如下:
<span style="color: #0000ff;"><</span><span style="color: #800000;">selector </span><span style="color: #ff0000;">xmlns:android</span><span style="color: #0000ff;">="http://schemas.android.com/apk/res/android"</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">item </span><span style="color: #ff0000;">android:state_focused</span><span style="color: #0000ff;">="true"</span><span style="color: #ff0000;"> android:drawable</span><span style="color: #0000ff;">="@drawable/testdrawable1"</span><span style="color: #0000ff;">/></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">item </span><span style="color: #ff0000;">android:state_pressed</span><span style="color: #0000ff;">="true"</span><span style="color: #ff0000;"> android:state_enabled</span><span style="color: #0000ff;">="false"</span><span style="color: #ff0000;"> android:drawable</span><span style="color: #0000ff;">="@drawable/testdrawable2"</span> <span style="color: #0000ff;">/></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">item </span><span style="color: #ff0000;">android:state_enabled</span><span style="color: #0000ff;">="false"</span><span style="color: #ff0000;"> android:drawable</span><span style="color: #0000ff;">="@drawable/testdrawable3"</span> <span style="color: #0000ff;">/></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">item </span><span style="color: #ff0000;">android:drawable</span><span style="color: #0000ff;">="@color/testdrawable5"</span><span style="color: #0000ff;">/></span> <span style="color: #0000ff;"></</span><span style="color: #800000;">selector</span><span style="color: #0000ff;">></span>
123456 |
<span style="color: #0000ff;"><</span><span style="color: #800000;">selector </span><span style="color: #ff0000;">xmlns:android</span><span style="color: #0000ff;">="http://schemas.android.com/apk/res/android"</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">item </span><span style="color: #ff0000;">android:state_focused</span><span style="color: #0000ff;">="true"</span><span style="color: #ff0000;"> android:drawable</span><span style="color: #0000ff;">="@drawable/testdrawable1"</span><span style="color: #0000ff;">/></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">item </span><span style="color: #ff0000;">android:state_pressed</span><span style="color: #0000ff;">="true"</span><span style="color: #ff0000;"> android:state_enabled</span><span style="color: #0000ff;">="false"</span><span style="color: #ff0000;"> android:drawable</span><span style="color: #0000ff;">="@drawable/testdrawable2"</span> <span style="color: #0000ff;">/></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">item </span><span style="color: #ff0000;">android:state_enabled</span><span style="color: #0000ff;">="false"</span><span style="color: #ff0000;"> android:drawable</span><span style="color: #0000ff;">="@drawable/testdrawable3"</span> <span style="color: #0000ff;">/></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">item </span><span style="color: #ff0000;">android:drawable</span><span style="color: #0000ff;">="@color/testdrawable5"</span><span style="color: #0000ff;">/></span> <span style="color: #0000ff;"></</span><span style="color: #800000;">selector</span><span style="color: #0000ff;">></span> |
---|
知道了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各参数解析的过程,赶紧看一下源代码(作为一个开源操作系统的程序猿,一定要下一份源码)。
<span style="color: #008000;">/**</span><span style="color: #008000;"> * Create a ColorStateList from an XML document, given a set of {</span><span style="color: #808080;">@link</span><span style="color: #008000;"> Resources}. </span><span style="color: #008000;">*/</span> <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span><span style="color: #000000;"> ColorStateList createFromXml(Resources r, XmlPullParser parser) </span><span style="color: #0000ff;">throws</span><span style="color: #000000;"> XmlPullParserException, IOException { </span><span style="color: #0000ff;">final</span> AttributeSet attrs =<span style="color: #000000;"> Xml.asAttributeSet(parser); </span><span style="color: #0000ff;">int</span><span style="color: #000000;"> type; </span><span style="color: #0000ff;">while</span> ((type=parser.next()) !=<span style="color: #000000;"> XmlPullParser.START_TAG </span>&& type !=<span style="color: #000000;"> XmlPullParser.END_DOCUMENT) { } </span><span style="color: #0000ff;">if</span> (type !=<span style="color: #000000;"> XmlPullParser.START_TAG) { </span><span style="color: #0000ff;">throw</span> <span style="color: #0000ff;">new</span> XmlPullParserException("No start tag found"<span style="color: #000000;">); } </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> createFromXmlInner(r, parser, attrs); }</span>
12 |
<span style="color: #008000;">/**</span><span style="color: #008000;"> * Create a ColorStateList from an XML document, given a set of {</span><span style="color: #808080;">@link</span><span style="color: #008000;"> Resources}. </span><span style="color: #008000;">*/</span> <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span><span style="color: #000000;"> ColorStateList createFromXml(Resources r, XmlPullParser parser) </span><span style="color: #0000ff;">throws</span><span style="color: #000000;"> XmlPullParserException, IOException { </span><span style="color: #0000ff;">final</span> AttributeSet attrs =<span style="color: #000000;"> Xml.asAttributeSet(parser); </span><span style="color: #0000ff;">int</span><span style="color: #000000;"> type; </span><span style="color: #0000ff;">while</span> ((type=parser.next()) !=<span style="color: #000000;"> XmlPullParser.START_TAG </span>&& type !=<span style="color: #000000;"> XmlPullParser.END_DOCUMENT) { } </span><span style="color: #0000ff;">if</span> (type !=<span style="color: #000000;"> XmlPullParser.START_TAG) { </span><span style="color: #0000ff;">throw</span> <span style="color: #0000ff;">new</span> XmlPullParserException("No start tag found"<span style="color: #000000;">); } </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> createFromXmlInner(r, parser, attrs); }</span> |
---|
我们跟着这个XmlPullparser走,因为他里面藏着XML信息。结果发现他调用了createFromXmlInner。继续跟,发现调用了colorStateList.inflate。看inflate就知道是解析函数啦!那我们来详细分析这个inflate:
(下面是我分析inflate函数时的笔记,应该算比较详细了)
<span style="color: #008000;">/**</span><span style="color: #008000;"> * Fill in this object based on the contents of an XML "selector" element. </span><span style="color: #008000;">*/</span> <span style="color: #008000;">//</span><span style="color: #008000;">解析XmlPullparser, (剧透)最终得到 mColors 和 mStateSpecs, 这两个参数用于构造ColorStateList</span> <span style="color: #0000ff;">private</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> inflate(Resources r, XmlPullParser parser, AttributeSet attrs) </span><span style="color: #0000ff;">throws</span><span style="color: #000000;"> XmlPullParserException, IOException { </span><span style="color: #0000ff;">int</span><span style="color: #000000;"> type; </span><span style="color: #0000ff;">final</span> <span style="color: #0000ff;">int</span> innerDepth = parser.getDepth()+1<span style="color: #000000;">; </span><span style="color: #0000ff;">int</span><span style="color: #000000;"> depth; </span><span style="color: #0000ff;">int</span>[][] stateSpecList = ArrayUtils.newUnpaddedArray(<span style="color: #0000ff;">int</span>[].<span style="color: #0000ff;">class</span>, 20<span style="color: #000000;">); </span><span style="color: #0000ff;">int</span>[] colorList = <span style="color: #0000ff;">new</span> <span style="color: #0000ff;">int</span><span style="color: #000000;">[stateSpecList.length]; </span><span style="color: #0000ff;">int</span> listSize = 0<span style="color: #000000;">; </span><span style="color: #0000ff;">while</span> ((type=parser.next()) !=<span style="color: #000000;"> XmlPullParser.END_DOCUMENT </span>&& ((depth=parser.getDepth()) >=<span style="color: #000000;"> innerDepth </span>|| type !=<span style="color: #000000;"> XmlPullParser.END_TAG)) { </span><span style="color: #0000ff;">if</span> (type !=<span style="color: #000000;"> XmlPullParser.START_TAG) { </span><span style="color: #0000ff;">continue</span><span style="color: #000000;">; } </span><span style="color: #008000;">//</span><span style="color: #008000;">只解析item项</span> <span style="color: #0000ff;">if</span> (depth > innerDepth || !parser.getName().equals("item"<span style="color: #000000;">)) { </span><span style="color: #0000ff;">continue</span><span style="color: #000000;">; } </span><span style="color: #0000ff;">int</span> alphaRes = 0<span style="color: #000000;">; </span><span style="color: #0000ff;">float</span> alpha = 1.0f<span style="color: #000000;">; </span><span style="color: #0000ff;">int</span> colorRes = 0<span style="color: #000000;">; </span><span style="color: #0000ff;">int</span> color = 0xffff0000<span style="color: #000000;">; </span><span style="color: #0000ff;">boolean</span> haveColor = <span style="color: #0000ff;">false</span><span style="color: #000000;">; </span><span style="color: #0000ff;">int</span><span style="color: #000000;"> i; </span><span style="color: #0000ff;">int</span> j = 0<span style="color: #000000;">; </span><span style="color: #0000ff;">final</span> <span style="color: #0000ff;">int</span> numAttrs =<span style="color: #000000;"> attrs.getAttributeCount(); </span><span style="color: #0000ff;">int</span>[] stateSpec = <span style="color: #0000ff;">new</span> <span style="color: #0000ff;">int</span><span style="color: #000000;">[numAttrs]; </span><span style="color: #008000;">//</span><span style="color: #008000;">对item项的各个属性做分析</span> <span style="color: #0000ff;">for</span> (i = 0; i < numAttrs; i++<span style="color: #000000;">) { </span><span style="color: #008000;">//</span><span style="color: #008000;">获取属性名对应的id, 如果没有则返回0, 其实就是把字符匹配的工作交出去, 提高代码灵活性</span> <span style="color: #0000ff;">final</span> <span style="color: #0000ff;">int</span> stateResId =<span style="color: #000000;"> attrs.getAttributeNameResource(i); </span><span style="color: #0000ff;">if</span> (stateResId == 0) <span style="color: #0000ff;">break</span><span style="color: #000000;">; </span><span style="color: #008000;">//</span><span style="color: #008000;">如果是alpha属性则赋值alpha. alpha是一个透明度的值</span> <span style="color: #0000ff;">if</span> (stateResId ==<span style="color: #000000;"> com.android.internal.R.attr.alpha) { </span><span style="color: #008000;">//</span><span style="color: #008000;">获取属性值对应的资源id, 就是我们熟悉的R.java里面的东西啦, 比如R.drawable.button1 = 0x7f040001这样的值</span> alphaRes = attrs.getAttributeResourceValue(i, 0<span style="color: #000000;">); </span><span style="color: #008000;">//</span><span style="color: #008000;">如果不是资源id, 是常数, 则直接翻译</span> <span style="color: #0000ff;">if</span> (alphaRes == 0<span style="color: #000000;">) { alpha </span>= attrs.getAttributeFloatValue(i, 1.0f<span style="color: #000000;">); } </span><span style="color: #008000;">//</span><span style="color: #008000;">如果是color属性, 也做类似的处理. color就是对应指定状态下的背景或颜色</span> } <span style="color: #0000ff;">else</span> <span style="color: #0000ff;">if</span> (stateResId ==<span style="color: #000000;"> com.android.internal.R.attr.color) { colorRes </span>= attrs.getAttributeResourceValue(i, 0<span style="color: #000000;">); </span><span style="color: #0000ff;">if</span> (colorRes == 0<span style="color: #000000;">) { color </span>=<span style="color: #000000;"> attrs.getAttributeIntValue(i, color); haveColor </span>= <span style="color: #0000ff;">true</span><span style="color: #000000;">; } </span><span style="color: #008000;">//</span><span style="color: #008000;">不是上面两个条件的话, 只能是设置状态的布尔值了, 如android:state_pressed="true"</span> } <span style="color: #0000ff;">else</span><span style="color: #000000;"> { </span><span style="color: #008000;">//</span><span style="color: #008000;">此时会把属性名对应的id存进数组, 并根据是否设置正负, 这样selector的各种状态条件就构建出来了 </span><span style="color: #008000;">//</span><span style="color: #008000;">具体的id值是多少, 是R.attr中的属性值吗(如state_pressed就是android.R.attr.state_pressed)?我们下面再仔细分析</span> stateSpec[j++] = attrs.getAttributeBooleanValue(i, <span style="color: #0000ff;">false</span><span style="color: #000000;">) </span>? stateResId : -<span style="color: #000000;">stateResId; } } </span><span style="color: #008000;">//</span><span style="color: #008000;">这个trimStateSet的用处是重新指定此int数组的长度, 也就是j, stateSpec初始化的时候是使用了属性数量作为长度, 在这里会被重新设置, 以节省空间. </span><span style="color: #008000;">//</span><span style="color: #008000;">因为上一条语句会做j++, 所以永远空出一个位置给下一个属性添加 </span><span style="color: #008000;">//</span><span style="color: #008000;">数组很小, 所以不会很费时间, 牺牲一点性能换取简单的写法也是可取的. 如果是我我会在循环开始的时候先找出有多少个设置状态的属性.</span> stateSpec =<span style="color: #000000;"> StateSet.trimStateSet(stateSpec, j); </span><span style="color: #008000;">//</span><span style="color: #008000;">这里对刚才获取的资源值做获取, 并做一些结构完整性检查, 如果一个item连color都没有, 那真是坑爹了</span> <span style="color: #0000ff;">if</span> (colorRes != 0<span style="color: #000000;">) { color </span>=<span style="color: #000000;"> r.getColor(colorRes); } </span><span style="color: #0000ff;">else</span> <span style="color: #0000ff;">if</span> (!<span style="color: #000000;">haveColor) { </span><span style="color: #0000ff;">throw</span> <span style="color: #0000ff;">new</span><span style="color: #000000;"> XmlPullParserException( parser.getPositionDescription() </span>+ ": <item> tag requires a 'android:color' attribute."<span style="color: #000000;">); } </span><span style="color: #008000;">//</span><span style="color: #008000;">alpha就可有可无了, 如果没有, 默认是1 (初始化的值float alpha = 1.0f;), 也就是100%不透明</span> <span style="color: #0000ff;">if</span> (alphaRes != 0<span style="color: #000000;">) { alpha </span>=<span style="color: #000000;"> r.getFloat(alphaRes); } </span><span style="color: #008000;">//</span><span style="color: #008000;">稍微转换一下, 把color算出来 </span><span style="color: #008000;">//</span><span style="color: #008000;"> Apply alpha modulation. </span><span style="color: #008000;">//</span><span style="color: #008000;">算出alpha. Color.alpha(color)看源码, 就是 color >>> 24. 这个运算是算出color的alpha值 (int color 长度为4个字节, 3个字节分别代表RGB, 还有一个最高位字节的alpha喔) </span><span style="color: #008000;">//</span><span style="color: #008000;">比如#AAFFFFFF的alpha值就是AA. 但要注意的是#FFFFFF的alpha值是FF, 因为Android的Color.parse会自动给没填写alpha的值的最高位填上FF(具体看源代码). //如果你使用int形式, 如0xaaaaaa, alpha值就是0, 这样写是会全透明的. 用int形式一定要写上alpha值, 如全黑是0xff000000. </span><span style="color: #008000;">//</span><span style="color: #008000;">所以一个item真正的alpha值是由color自带的alpha和android:alpha的值共同决定的 </span><span style="color: #008000;">//</span><span style="color: #008000;">MathUtils.constrain这个函数不用看都能知道是百分比缩放的函数, 比如0.5, 0, 255, 的出来的就是(255 - 0) * 0.5</span> <span style="color: #0000ff;">final</span> <span style="color: #0000ff;">int</span> alphaMod = MathUtils.constrain((<span style="color: #0000ff;">int</span>) (Color.alpha(color) * alpha), 0, 255<span style="color: #000000;">); </span><span style="color: #008000;">//</span><span style="color: #008000;">再把这个0~255的值塞回color的最高字节里面去 (折腾啊)</span> color = (color & 0xFFFFFF) | (alphaMod << 24<span style="color: #000000;">); </span><span style="color: #008000;">//</span><span style="color: #008000;">如果只有这个android:color这个属性, 没有表示状态的属性, 就认为他是默认色</span> <span style="color: #0000ff;">if</span> (listSize == 0 || stateSpec.length == 0<span style="color: #000000;">) { mDefaultColor </span>=<span style="color: #000000;"> color; } </span><span style="color: #008000;">//</span><span style="color: #008000;">把color 和 stateSpec塞进数组里面去. 比用ArrayList要节省点空间. 看来Android在节省内存方面上很卖力啊 </span><span style="color: #008000;">//</span><span style="color: #008000;">append能猜到又是重新定义数组长度这种事情, 不同的是会加一个数组进去</span> colorList =<span style="color: #000000;"> GrowingArrayUtils.append(colorList, listSize, color); stateSpecList </span>=<span style="color: #000000;"> GrowingArrayUtils.append(stateSpecList, listSize, stateSpec); listSize</span>++<span style="color: #000000;">; } </span><span style="color: #008000;">//</span><span style="color: #008000;">所有的item都跑完了, 可以把结果存给全局变量mColors 和 mStateSpecs了.</span> mColors = <span style="color: #0000ff;">new</span> <span style="color: #0000ff;">int</span><span style="color: #000000;">[listSize]; mStateSpecs </span>= <span style="color: #0000ff;">new</span> <span style="color: #0000ff;">int</span><span style="color: #000000;">[listSize][]; System.arraycopy(colorList, </span>0, mColors, 0<span style="color: #000000;">, listSize); System.arraycopy(stateSpecList, </span>0, mStateSpecs, 0<span style="color: #000000;">, listSize); }</span>
1234567891011121314151617181920 |
<span style="color: #008000;">/**</span><span style="color: #008000;"> * Fill in this object based on the contents of an XML "selector" element. </span><span style="color: #008000;">*/</span> <span style="color: #008000;">//</span><span style="color: #008000;">解析XmlPullparser, (剧透)最终得到 mColors 和 mStateSpecs, 这两个参数用于构造ColorStateList</span> <span style="color: #0000ff;">private</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> inflate(Resources r, XmlPullParser parser, AttributeSet attrs) </span><span style="color: #0000ff;">throws</span><span style="color: #000000;"> XmlPullParserException, IOException { </span><span style="color: #0000ff;">int</span><span style="color: #000000;"> type; </span><span style="color: #0000ff;">final</span> <span style="color: #0000ff;">int</span> innerDepth = parser.getDepth()+1<span style="color: #000000;">; </span><span style="color: #0000ff;">int</span><span style="color: #000000;"> depth; </span><span style="color: #0000ff;">int</span>[][] stateSpecList = ArrayUtils.newUnpaddedArray(<span style="color: #0000ff;">int</span>[].<span style="color: #0000ff;">class</span>, 20<span style="color: #000000;">); </span><span style="color: #0000ff;">int</span>[] colorList = <span style="color: #0000ff;">new</span> <span style="color: #0000ff;">int</span><span style="color: #000000;">[stateSpecList.length]; </span><span style="color: #0000ff;">int</span> listSize = 0<span style="color: #000000;">; </span><span style="color: #0000ff;">while</span> ((type=parser.next()) !=<span style="color: #000000;"> XmlPullParser.END_DOCUMENT </span>&& ((depth=parser.getDepth()) >=<span style="color: #000000;"> innerDepth </span>|| type !=<span style="color: #000000;"> XmlPullParser.END_TAG)) { </span><span style="color: #0000ff;">if</span> (type !=<span style="color: #000000;"> XmlPullParser.START_TAG) { </span><span style="color: #0000ff;">continue</span><span style="color: #000000;">; } </span><span style="color: #008000;">//</span><span style="color: #008000;">只解析item项</span> <span style="color: #0000ff;">if</span> (depth > innerDepth || !parser.getName().equals("item"<span style="color: #000000;">)) { </span><span style="color: #0000ff;">continue</span><span style="color: #000000;">; } </span><span style="color: #0000ff;">int</span> alphaRes = 0<span style="color: #000000;">; </span><span style="color: #0000ff;">float</span> alpha = 1.0f<span style="color: #000000;">; </span><span style="color: #0000ff;">int</span> colorRes = 0<span style="color: #000000;">; </span><span style="color: #0000ff;">int</span> color = 0xffff0000<span style="color: #000000;">; </span><span style="color: #0000ff;">boolean</span> haveColor = <span style="color: #0000ff;">false</span><span style="color: #000000;">; </span><span style="color: #0000ff;">int</span><span style="color: #000000;"> i; </span><span style="color: #0000ff;">int</span> j = 0<span style="color: #000000;">; </span><span style="color: #0000ff;">final</span> <span style="color: #0000ff;">int</span> numAttrs =<span style="color: #000000;"> attrs.getAttributeCount(); </span><span style="color: #0000ff;">int</span>[] stateSpec = <span style="color: #0000ff;">new</span> <span style="color: #0000ff;">int</span><span style="color: #000000;">[numAttrs]; </span><span style="color: #008000;">//</span><span style="color: #008000;">对item项的各个属性做分析</span> <span style="color: #0000ff;">for</span> (i = 0; i < numAttrs; i++<span style="color: #000000;">) { </span><span style="color: #008000;">//</span><span style="color: #008000;">获取属性名对应的id, 如果没有则返回0, 其实就是把字符匹配的工作交出去, 提高代码灵活性</span> <span style="color: #0000ff;">final</span> <span style="color: #0000ff;">int</span> stateResId =<span style="color: #000000;"> attrs.getAttributeNameResource(i); </span><span style="color: #0000ff;">if</span> (stateResId == 0) <span style="color: #0000ff;">break</span><span style="color: #000000;">; </span><span style="color: #008000;">//</span><span style="color: #008000;">如果是alpha属性则赋值alpha. alpha是一个透明度的值</span> <span style="color: #0000ff;">if</span> (stateResId ==<span style="color: #000000;"> com.android.internal.R.attr.alpha) { </span><span style="color: #008000;">//</span><span style="color: #008000;">获取属性值对应的资源id, 就是我们熟悉的R.java里面的东西啦, 比如R.drawable.button1 = 0x7f040001这样的值</span> alphaRes = attrs.getAttributeResourceValue(i, 0<span style="color: #000000;">); </span><span style="color: #008000;">//</span><span style="color: #008000;">如果不是资源id, 是常数, 则直接翻译</span> <span style="color: #0000ff;">if</span> (alphaRes == 0<span style="color: #000000;">) { alpha </span>= attrs.getAttributeFloatValue(i, 1.0f<span style="color: #000000;">); } </span><span style="color: #008000;">//</span><span style="color: #008000;">如果是color属性, 也做类似的处理. color就是对应指定状态下的背景或颜色</span> } <span style="color: #0000ff;">else</span> <span style="color: #0000ff;">if</span> (stateResId ==<span style="color: #000000;"> com.android.internal.R.attr.color) { colorRes </span>= attrs.getAttributeResourceValue(i, 0<span style="color: #000000;">); </span><span style="color: #0000ff;">if</span> (colorRes == 0<span style="color: #000000;">) { color </span>=<span style="color: #000000;"> attrs.getAttributeIntValue(i, color); haveColor </span>= <span style="color: #0000ff;">true</span><span style="color: #000000;">; } </span><span style="color: #008000;">//</span><span style="color: #008000;">不是上面两个条件的话, 只能是设置状态的布尔值了, 如android:state_pressed="true"</span> } <span style="color: #0000ff;">else</span><span style="color: #000000;"> { </span><span style="color: #008000;">//</span><span style="color: #008000;">此时会把属性名对应的id存进数组, 并根据是否设置正负, 这样selector的各种状态条件就构建出来了 </span><span style="color: #008000;">//</span><span style="color: #008000;">具体的id值是多少, 是R.attr中的属性值吗(如state_pressed就是android.R.attr.state_pressed)?我们下面再仔细分析</span> stateSpec[j++] = attrs.getAttributeBooleanValue(i, <span style="color: #0000ff;">false</span><span style="color: #000000;">) </span>? stateResId : -<span style="color: #000000;">stateResId; } } </span><span style="color: #008000;">//</span><span style="color: #008000;">这个trimStateSet的用处是重新指定此int数组的长度, 也就是j, stateSpec初始化的时候是使用了属性数量作为长度, 在这里会被重新设置, 以节省空间. </span><span style="color: #008000;">//</span><span style="color: #008000;">因为上一条语句会做j++, 所以永远空出一个位置给下一个属性添加 </span><span style="color: #008000;">//</span><span style="color: #008000;">数组很小, 所以不会很费时间, 牺牲一点性能换取简单的写法也是可取的. 如果是我我会在循环开始的时候先找出有多少个设置状态的属性.</span> stateSpec =<span style="color: #000000;"> StateSet.trimStateSet(stateSpec, j); </span><span style="color: #008000;">//</span><span style="color: #008000;">这里对刚才获取的资源值做获取, 并做一些结构完整性检查, 如果一个item连color都没有, 那真是坑爹了</span> <span style="color: #0000ff;">if</span> (colorRes != 0<span style="color: #000000;">) { color </span>=<span style="color: #000000;"> r.getColor(colorRes); } </span><span style="color: #0000ff;">else</span> <span style="color: #0000ff;">if</span> (!<span style="color: #000000;">haveColor) { </span><span style="color: #0000ff;">throw</span> <span style="color: #0000ff;">new</span><span style="color: #000000;"> XmlPullParserException( parser.getPositionDescription() </span>+ ": <item> tag requires a 'android:color' attribute."<span style="color: #000000;">); } </span><span style="color: #008000;">//</span><span style="color: #008000;">alpha就可有可无了, 如果没有, 默认是1 (初始化的值float alpha = 1.0f;), 也就是100%不透明</span> <span style="color: #0000ff;">if</span> (alphaRes != 0<span style="color: #000000;">) { alpha </span>=<span style="color: #000000;"> r.getFloat(alphaRes); } </span><span style="color: #008000;">//</span><span style="color: #008000;">稍微转换一下, 把color算出来 </span><span style="color: #008000;">//</span><span style="color: #008000;"> Apply alpha modulation. </span><span style="color: #008000;">//</span><span style="color: #008000;">算出alpha. Color.alpha(color)看源码, 就是 color >>> 24. 这个运算是算出color的alpha值 (int color 长度为4个字节, 3个字节分别代表RGB, 还有一个最高位字节的alpha喔) </span><span style="color: #008000;">//</span><span style="color: #008000;">比如#AAFFFFFF的alpha值就是AA. 但要注意的是#FFFFFF的alpha值是FF, 因为Android的Color.parse会自动给没填写alpha的值的最高位填上FF(具体看源代码). //如果你使用int形式, 如0xaaaaaa, alpha值就是0, 这样写是会全透明的. 用int形式一定要写上alpha值, 如全黑是0xff000000. </span><span style="color: #008000;">//</span><span style="color: #008000;">所以一个item真正的alpha值是由color自带的alpha和android:alpha的值共同决定的 </span><span style="color: #008000;">//</span><span style="color: #008000;">MathUtils.constrain这个函数不用看都能知道是百分比缩放的函数, 比如0.5, 0, 255, 的出来的就是(255 - 0) * 0.5</span> <span style="color: #0000ff;">final</span> <span style="color: #0000ff;">int</span> alphaMod = MathUtils.constrain((<span style="color: #0000ff;">int</span>) (Color.alpha(color) * alpha), 0, 255<span style="color: #000000;">); </span><span style="color: #008000;">//</span><span style="color: #008000;">再把这个0~255的值塞回color的最高字节里面去 (折腾啊)</span> color = (color & 0xFFFFFF) | (alphaMod << 24<span style="color: #000000;">); </span><span style="color: #008000;">//</span><span style="color: #008000;">如果只有这个android:color这个属性, 没有表示状态的属性, 就认为他是默认色</span> <span style="color: #0000ff;">if</span> (listSize == 0 || stateSpec.length == 0<span style="color: #000000;">) { mDefaultColor </span>=<span style="color: #000000;"> color; } </span><span style="color: #008000;">//</span><span style="color: #008000;">把color 和 stateSpec塞进数组里面去. 比用ArrayList要节省点空间. 看来Android在节省内存方面上很卖力啊 </span><span style="color: #008000;">//</span><span style="color: #008000;">append能猜到又是重新定义数组长度这种事情, 不同的是会加一个数组进去</span> colorList =<span style="color: #000000;"> GrowingArrayUtils.append(colorList, listSize, color); stateSpecList </span>=<span style="color: #000000;"> GrowingArrayUtils.append(stateSpecList, listSize, stateSpec); listSize</span>++<span style="color: #000000;">; } </span><span style="color: #008000;">//</span><span style="color: #008000;">所有的item都跑完了, 可以把结果存给全局变量mColors 和 mStateSpecs了.</span> mColors = <span style="color: #0000ff;">new</span> <span style="color: #0000ff;">int</span><span style="color: #000000;">[listSize]; mStateSpecs </span>= <span style="color: #0000ff;">new</span> <span style="color: #0000ff;">int</span><span style="color: #000000;">[listSize][]; System.arraycopy(colorList, </span>0, mColors, 0<span style="color: #000000;">, listSize); System.arraycopy(stateSpecList, </span>0, mStateSpecs, 0<span style="color: #000000;">, listSize); }</span> |
---|
我们现在来看下ColorStateList的构造函数:
<span style="color: #0000ff;">public</span> ColorStateList(<span style="color: #0000ff;">int</span>[][] states, <span style="color: #0000ff;">int</span><span style="color: #000000;">[] colors) { mStateSpecs </span>=<span style="color: #000000;"> states; mColors </span>=<span style="color: #000000;"> colors; </span><span style="color: #0000ff;">if</span> (states.length > 0<span style="color: #000000;">) { mDefaultColor </span>= colors[0<span style="color: #000000;">]; </span><span style="color: #0000ff;">for</span> (<span style="color: #0000ff;">int</span> i = 0; i < states.length; i++<span style="color: #000000;">) { </span><span style="color: #0000ff;">if</span> (states[i].length == 0<span style="color: #000000;">) { mDefaultColor </span>=<span style="color: #000000;"> colors[i]; } } } }</span>
1 |
<span style="color: #0000ff;">public</span> ColorStateList(<span style="color: #0000ff;">int</span>[][] states, <span style="color: #0000ff;">int</span><span style="color: #000000;">[] colors) { mStateSpecs </span>=<span style="color: #000000;"> states; mColors </span>=<span style="color: #000000;"> colors; </span><span style="color: #0000ff;">if</span> (states.length > 0<span style="color: #000000;">) { mDefaultColor </span>= colors[0<span style="color: #000000;">]; </span><span style="color: #0000ff;">for</span> (<span style="color: #0000ff;">int</span> i = 0; i < states.length; i++<span style="color: #000000;">) { </span><span style="color: #0000ff;">if</span> (states[i].length == 0<span style="color: #000000;">) { mDefaultColor </span>=<span style="color: #000000;"> colors[i]; } } } }</span> |
---|
inflate得到的两个最终产物 mStateSpecs 和 mColors 就是我们想要的构造函数!我们只要照葫芦画瓢,照inflate画数组,就可以把这两个数组写出来了。ColorStateList被降服了!
喔,忘了还有一个遗留问题:
穿山甲到底说了什么?
stateResId到底是什么?
再看分析:
<span style="color: #008000;">//</span><span style="color: #008000;">首先这个id是从AttributeSet中得到的,AttributeSet是一个interface,实现它的是XmlPullAttributes(为什么?因为搜遍了源码就他一个...),但可惜的是XmlPullAttributes没有实现getAttributeNameResource() </span><span style="color: #008000;">//</span><span style="color: #008000;">继续找儿子。BridgeXmlPullAttributes 继承了XmlPullAttributes,而且他实现了getAttributeNameResource()</span> BridgeXmlPullAttributes <span style="color: #0000ff;">extends</span><span style="color: #000000;"> XmlPullAttributes </span><span style="color: #008000;">//</span><span style="color: #008000;">This methods must return com.android.internal.R.attr.<name> matching the name of the attribute.</span> BridgeXmlPullAttributes.getAttributeNameResource(<span style="color: #0000ff;">int</span><span style="color: #000000;"> index) </span>-><span style="color: #000000;"> Bridge.getResourceId(ResourceType.ATTR, name); </span>->-> Integer v =<span style="color: #000000;"> Bridge.getResourceId(ResourceType.ATTR, name); </span>->->-><span style="color: #000000;"> sRevRMap.get(type); </span><span style="color: #0000ff;">if</span> (v != <span style="color: #0000ff;">null</span><span style="color: #000000;">) { </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> v.intValue(); } </span><span style="color: #008000;">//</span><span style="color: #008000;">这里写的有点乱,“->”的意思是深入一层,同样的梯度表示在同一个函数中,也就是说getAttributeNameResource调用了Bridge.getResourceId调用了sRevRMap.get,最后Bridge.getResourceId返回了sRevRMap.get的int值。 </span><span style="color: #008000;">//</span><span style="color: #008000;">这个sRevRMap是在哪初始化的呢?搜源码发现在init中()</span> <span style="color: #000000;"> init(...) { ... Class</span><?> r = com.android.internal.R.<span style="color: #0000ff;">class</span><span style="color: #000000;">; </span><span style="color: #008000;">//</span><span style="color: #008000;">这里这里!init函数把这个class里面的所有子类声明的值put进去sRevRMap了。 </span><span style="color: #008000;">//</span><span style="color: #008000;">这个com.android.internal.R.class是在Android ROM编译的时候生成的一个类,就类似我们写应用时自动生成的R.java,里面全部是声明的值。 </span><span style="color: #008000;">//</span><span style="color: #008000;">而我们getResourceId的时候传的type是ResourceType.ATTR。所以就是ATTR的id值。这个值在http:</span><span style="color: #008000;">//</span><span style="color: #008000;">developer.android.com/reference/android/R.attr.html中有详细列表。 </span><span style="color: #008000;">//</span><span style="color: #008000;">实际上我们使用只需要给对应的属性名加上前缀android.R.attr即可。如android:state_pressed对应android.R.attr.state_pressed。 </span><span style="color: #008000;">//</span><span style="color: #008000;">至于为什么初始化的时候是com.android.internal.R,使用的时候是android.R,应该是涉及到Android系统编译的东西,我没有深入了解。</span> <span style="color: #0000ff;">for</span> (Class<?><span style="color: #000000;"> inner : r.getDeclaredClasses()) { String resTypeName </span>=<span style="color: #000000;"> inner.getSimpleName(); ResourceType resType </span>=<span style="color: #000000;"> ResourceType.getEnum(resTypeName); </span><span style="color: #0000ff;">if</span> (resType != <span style="color: #0000ff;">null</span><span style="color: #000000;">) { Map</span><String, Integer> fullMap = <span style="color: #0000ff;">new</span> HashMap<String, Integer><span style="color: #000000;">(); sRevRMap.put(resType, fullMap); </span><span style="color: #0000ff;">for</span><span style="color: #000000;"> (Field f : inner.getDeclaredFields()) { </span><span style="color: #008000;">//</span><span style="color: #008000;"> only process static final fields. Since the final attribute may have </span><span style="color: #008000;">//</span><span style="color: #008000;"> been altered by layoutlib_create, we only check static</span> <span style="color: #0000ff;">int</span> modifiers =<span style="color: #000000;"> f.getModifiers(); </span><span style="color: #0000ff;">if</span><span style="color: #000000;"> (Modifier.isStatic(modifiers)) { Class</span><?> type =<span style="color: #000000;"> f.getType(); </span><span style="color: #0000ff;">if</span> (type.isArray() && type.getComponentType() == <span style="color: #0000ff;">int</span>.<span style="color: #0000ff;">class</span><span style="color: #000000;">) { </span><span style="color: #008000;">//</span><span style="color: #008000;"> if the object is an int[] we put it in sRArrayMap using an IntArray </span><span style="color: #008000;">//</span><span style="color: #008000;"> wrapper that properly implements equals and hashcode for the array </span><span style="color: #008000;">//</span><span style="color: #008000;"> objects, as required by the map contract.</span> sRArrayMap.put(<span style="color: #0000ff;">new</span> IntArray((<span style="color: #0000ff;">int</span>[]) f.get(<span style="color: #0000ff;">null</span><span style="color: #000000;">)), f.getName()); } </span><span style="color: #0000ff;">else</span> <span style="color: #0000ff;">if</span> (type == <span style="color: #0000ff;">int</span>.<span style="color: #0000ff;">class</span><span style="color: #000000;">) { Integer value </span>= (Integer) f.get(<span style="color: #0000ff;">null</span><span style="color: #000000;">); sRMap.put(value, Pair.of(resType, f.getName())); fullMap.put(f.getName(), value); } </span><span style="color: #0000ff;">else</span><span style="color: #000000;"> { </span><span style="color: #0000ff;">assert</span> <span style="color: #0000ff;">false</span><span style="color: #000000;">; } } } } } ... } </span>
12345678 |
<span style="color: #008000;">//</span><span style="color: #008000;">首先这个id是从AttributeSet中得到的,AttributeSet是一个interface,实现它的是XmlPullAttributes(为什么?因为搜遍了源码就他一个...),但可惜的是XmlPullAttributes没有实现getAttributeNameResource() </span><span style="color: #008000;">//</span><span style="color: #008000;">继续找儿子。BridgeXmlPullAttributes 继承了XmlPullAttributes,而且他实现了getAttributeNameResource()</span> BridgeXmlPullAttributes <span style="color: #0000ff;">extends</span><span style="color: #000000;"> XmlPullAttributes </span><span style="color: #008000;">//</span><span style="color: #008000;">This methods must return com.android.internal.R.attr.<name> matching the name of the attribute.</span> BridgeXmlPullAttributes.getAttributeNameResource(<span style="color: #0000ff;">int</span><span style="color: #000000;"> index) </span>-><span style="color: #000000;"> Bridge.getResourceId(ResourceType.ATTR, name); </span>->-> Integer v =<span style="color: #000000;"> Bridge.getResourceId(ResourceType.ATTR, name); </span>->->-><span style="color: #000000;"> sRevRMap.get(type); </span><span style="color: #0000ff;">if</span> (v != <span style="color: #0000ff;">null</span><span style="color: #000000;">) { </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> v.intValue(); } </span><span style="color: #008000;">//</span><span style="color: #008000;">这里写的有点乱,“->”的意思是深入一层,同样的梯度表示在同一个函数中,也就是说getAttributeNameResource调用了Bridge.getResourceId调用了sRevRMap.get,最后Bridge.getResourceId返回了sRevRMap.get的int值。 </span><span style="color: #008000;">//</span><span style="color: #008000;">这个sRevRMap是在哪初始化的呢?搜源码发现在init中()</span><span style="color: #000000;"> init(...) { ... Class</span><?> r = com.android.internal.R.<span style="color: #0000ff;">class</span><span style="color: #000000;">; </span><span style="color: #008000;">//</span><span style="color: #008000;">这里这里!init函数把这个class里面的所有子类声明的值put进去sRevRMap了。 </span><span style="color: #008000;">//</span><span style="color: #008000;">这个com.android.internal.R.class是在Android ROM编译的时候生成的一个类,就类似我们写应用时自动生成的R.java,里面全部是声明的值。 </span><span style="color: #008000;">//</span><span style="color: #008000;">而我们getResourceId的时候传的type是ResourceType.ATTR。所以就是ATTR的id值。这个值在http:</span><span style="color: #008000;">//</span><span style="color: #008000;">developer.android.com/reference/android/R.attr.html中有详细列表。 </span><span style="color: #008000;">//</span><span style="color: #008000;">实际上我们使用只需要给对应的属性名加上前缀android.R.attr即可。如android:state_pressed对应android.R.attr.state_pressed。 </span><span style="color: #008000;">//</span><span style="color: #008000;">至于为什么初始化的时候是com.android.internal.R,使用的时候是android.R,应该是涉及到Android系统编译的东西,我没有深入了解。</span> <span style="color: #0000ff;">for</span> (Class<?><span style="color: #000000;"> inner : r.getDeclaredClasses()) { String resTypeName </span>=<span style="color: #000000;"> inner.getSimpleName(); ResourceType resType </span>=<span style="color: #000000;"> ResourceType.getEnum(resTypeName); </span><span style="color: #0000ff;">if</span> (resType != <span style="color: #0000ff;">null</span><span style="color: #000000;">) { Map</span><String, Integer> fullMap = <span style="color: #0000ff;">new</span> HashMap<String, Integer><span style="color: #000000;">(); sRevRMap.put(resType, fullMap); </span><span style="color: #0000ff;">for</span><span style="color: #000000;"> (Field f : inner.getDeclaredFields()) { </span><span style="color: #008000;">//</span><span style="color: #008000;"> only process static final fields. Since the final attribute may have </span><span style="color: #008000;">//</span><span style="color: #008000;"> been altered by layoutlib_create, we only check static</span> <span style="color: #0000ff;">int</span> modifiers =<span style="color: #000000;"> f.getModifiers(); </span><span style="color: #0000ff;">if</span><span style="color: #000000;"> (Modifier.isStatic(modifiers)) { Class</span><?> type =<span style="color: #000000;"> f.getType(); </span><span style="color: #0000ff;">if</span> (type.isArray() && type.getComponentType() == <span style="color: #0000ff;">int</span>.<span style="color: #0000ff;">class</span><span style="color: #000000;">) { </span><span style="color: #008000;">//</span><span style="color: #008000;"> if the object is an int[] we put it in sRArrayMap using an IntArray </span><span style="color: #008000;">//</span><span style="color: #008000;"> wrapper that properly implements equals and hashcode for the array </span><span style="color: #008000;">//</span><span style="color: #008000;"> objects, as required by the map contract.</span> sRArrayMap.put(<span style="color: #0000ff;">new</span> IntArray((<span style="color: #0000ff;">int</span>[]) f.get(<span style="color: #0000ff;">null</span><span style="color: #000000;">)), f.getName()); } </span><span style="color: #0000ff;">else</span> <span style="color: #0000ff;">if</span> (type == <span style="color: #0000ff;">int</span>.<span style="color: #0000ff;">class</span><span style="color: #000000;">) { Integer value </span>= (Integer) f.get(<span style="color: #0000ff;">null</span><span style="color: #000000;">); sRMap.put(value, Pair.of(resType, f.getName())); fullMap.put(f.getName(), value); } </span><span style="color: #0000ff;">else</span><span style="color: #000000;"> { </span><span style="color: #0000ff;">assert</span> <span style="color: #0000ff;">false</span><span style="color: #000000;">; } } } } } ... } </span> |
---|
这里“com.android.internal.R,使用的时候是android.R”,说服力可能不足,但这个表 http://developer.android.com/reference/android/R.attr.html 应该是可靠的,Google不会做两个表出来吧。。
是com.android.internal.R,使用的时候是android.R,
1 |
是com.android.internal.R,使用的时候是android.R, |
---|
接下来还有一个