`
haliluya4
  • 浏览: 122281 次
  • 性别: Icon_minigender_1
  • 来自: 福州
社区版块
存档分类
最新评论

Android Dimension转换算法原理分析

阅读更多

最近在做深度主题,要实现类似小米那种在主题包中设置dimension值,然后在系统中替换原值的功能。

特地研究了一下Android系统中dimension类型的值的存储方式以及相关的转换算法。

 

Android中,我们可以在values文件夹中定义各种资源,其中有一种就是dimension

dimension是一个包含单位(dpdipspptpxmmin)的尺寸,可以用于定义视图的宽度、字号等。如下图所示。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <dimen name="textview_height">25dp</dimen>
    <dimen name="textview_width">150dp</dimen>
    <dimen name="ball_radius">30dp</dimen>
    <dimen name="font_size">16sp</dimen>
</resources>
<TextView
    android:layout_height="@dimen/textview_height"
    android:layout_width="@dimen/textview_width"
    android:textSize="@dimen/font_size"/>

而在代码中,我们可以通过getDimension方法获取到资源文件中定义的dimension值。

Resources res = getResources();
float fontSize = res.getDimension(R.dimen.font_size);

从上图可以发现,不论之前在资源文件中定义的dimension是什么单位,在代码中均转换成了float类型的数值。

但两个dimension值,如果数值部分相同,但单位不同,显然转换后的float值应该不同。

为了证明以上结论,我做了一个实验。

首先,定义一组dimension,数值部分相同,单位不同。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <dimen name="tdp">10dp</dimen>
    <dimen name="tdip">10dip</dimen>
    <dimen name="tsp">10sp</dimen>
    <dimen name="tpt">10pt</dimen>
    <dimen name="tpx">10px</dimen>
    <dimen name="tmm">10mm</dimen>
    <dimen name="tin">10in</dimen>
</resources>

然后,在代码中分别使用getDimensiongetValue方法获取这些dimension的值。

Resources r = getResources();
TypedValue tv = new TypedValue();
r.getValue(R.dimen.tdp, tv, true);
System.out.println("10dp="+r.getDimension(R.dimen.tdp)+" data="+Integer.toBinaryString(tv.data));
r.getValue(R.dimen.tdip, tv, true);
System.out.println("10dip="+r.getDimension(R.dimen.tdip)+" data="+Integer.toBinaryString(tv.data));
r.getValue(R.dimen.tsp, tv, true);
System.out.println("10sp="+r.getDimension(R.dimen.tsp)+" data="+Integer.toBinaryString(tv.data));
r.getValue(R.dimen.tpt, tv, true);
System.out.println("10pt="+r.getDimension(R.dimen.tpt)+" data="+Integer.toBinaryString(tv.data));
r.getValue(R.dimen.tpx, tv, true);
System.out.println("10px="+r.getDimension(R.dimen.tpx)+" data="+Integer.toBinaryString(tv.data));
r.getValue(R.dimen.tmm, tv, true);
System.out.println("10mm="+r.getDimension(R.dimen.tmm)+" data="+Integer.toBinaryString(tv.data));
r.getValue(R.dimen.tin, tv, true);
System.out.println("10in="+r.getDimension(R.dimen.tin)+" data="+Integer.toBinaryString(tv.data));

最后,可以从下图看到输出的结果。

从上图可以看出,不同单位情况下,即使数值相同,转换成的float值也是千差万别。

但从后面的data值中,我们却发现,对于不同的单位,其最高位大部分是相同的,仅仅是后面几位有所区别。我们可以大胆地猜想:“dimension在系统中是以数值+单位的形式存储的。”

分析了AndroidResoureces类的getDimension方法的源码后我们发现,对于不同的dimension,在使用getValue获取到对应的int值之后,会通过TypedValuecomplexToDimension方法将其转换为float
complexToDimension方法主要是调用applyDimension方法,将getValue获取到的int值,拆成单位和数值两部分,然后根据单位的不同,对数值进行处理。
其中, (data>>COMPLEX_UNIT_SHIFT)&COMPLEX_UNIT_MASK的作用是将该int值与上0xf,以获取其最低4位,4位是单位。而complexToFloat则是使用该int值的最高24位当作数值,4-7位作为radix,进行计算,转成float

最后,在applyDimension中根据单位的不同,将float乘上不同的系数。如dip/dp需乘上屏幕系数,sp需乘上字号的缩放系数,ptinmm等也是根据相应的算法进行换算。(从COMPLEX_UNIT_PX直接返回float可以看出,该方法是将数值转成像素数

至此,我们之前的猜想大致上是正确的,只是需要加上一个radix部分。即“dimension在系统中是以数值+4radix+4位单位的形式存储的”。

因此,为了实现将形如“10dip”的dimension转成getValue返回的int值,我们需要进行以下处理(参考com.androi.layoutlib.bridge.impl.ResourceHelperparseFloatAttribute方法)。

首先,将dimension字符串拆成数值与单位两部分,并将数值转成浮点数。

从上文可以知道,dimension的数值部分在实际存储时,系统只提供了24位存储空间。再考虑到一个dimension是有符号的,可正可负。故最高位表示正负。因此,真正的数值只有23位进行存储。

所以,接下来,对于负数要先转成对应的正数,并乘上2230.5(四舍五入)后转成long。(即仅保留整数和小数部分各23位)

接着,对转换后的long值进行判断。

若最低23位为0,即小数部分为0,标记radixTypedValue.COMPLEX_RADIX_23p0shift23,即只保留整数部分。

若最高41位为0,即整数部分为0javalong8个字节存储),标记radixTypedValue.COMPLEX_RADIX_0p23shift0,即只保留小数部分。

若最高330,即整数部分最多只有8位有效,标记radixTypedValue.COMPLEX_RADIX_8p15shift8,即只保留整数部分8位,小数部分15位。

若最高25位为0,即整数部分最多只有16位有效,标记radixTypedValue.COMPLEX_RADIX_16p7shift16,即只保留整数部分16位,小数部分7位。

若以上都不符合,说明小数部分不为0,整数有效部分超过16位,则标记radixTypedValue.COMPLEX_RADIX_23p0shift23,即只保留整数部分。

然后,根据shift将转换后的long值进行右移,取最低24位,转为int。若原值为负数,将右移的数转成负数后再取最低24位。

最后,将最终数值左移8位,最低4-7位或上radix0-3位或上单位。


上图给出了123113.51213dp的处理流程,按照算法,该dimension值会被转换成31516929存储在系统中。为了验证算法的正确性,笔者做了以下实验。

首先,在资源文件中添加一个值为123113.51213dpdimension

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <dimen name="tdp">123113.51213dp</dimen>
</resources>

 然后,在代码中获取该值。

Resources r = getResources();
TypedValue tv = new TypedValue();
r.getValue(R.dimen.tdp, tv, true);
System.out.println("123113.51213dp="+r.getDimension(R.dimen.tdp)+" data="+(tv.data));

 最后,查看输出结果,完全符合!

至此,dimension的处理算法分析完毕。

  • 大小: 2.9 KB
  • 大小: 9 KB
  • 大小: 4.4 KB
  • 大小: 5.5 KB
  • 大小: 10.5 KB
  • 大小: 29.7 KB
  • 大小: 1.3 KB
分享到:
评论
4 楼 hety163 2014-12-20  
niubility~
3 楼 Tyler3419 2014-11-26  
你好,大神,能不能封装出一个方法,输入一个string的dimen字符,得到data呢?
最近也在做资源重定向,修改系统层东西,图片和颜色都弄好了,发现dimen文件最麻烦了。
2 楼 snowdream 2014-11-03  
分析很透彻,谢谢分享。
1 楼 xiangxm 2013-12-22  
牛人啊    

相关推荐

Global site tag (gtag.js) - Google Analytics