获取自定义属性值常会使用TypedValue 和 TypedArray 类
TypedValue
- applyDimension ( return float )
- complexToDimension (return float)
TypedArray
- getDimension (return float)
下面从一个小例子逐步的解释这几个方法
在自定义View构造体中获取属性经常会这样写:
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MiUiClockView);
int n = typedArray.getIndexCount();
int defaultSize = (int) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics());//默认Paint textSize
for (int i = 0; i < n; i++) {
int attr = typedArray.getIndex(i);
switch (attr) {
case R.styleable.MiUiClockView_highColor:
mHighColor = typedArray.getColor(attr, Color.WHITE);
break;
case R.styleable.MiUiClockView_textSize:
textSize = typedArray.getDimension(attr,defaultSize);
Log.e(TAG,textSize+"");
break;
}
}
typedArray.recycle();
Paint paint = new Paint();
paint.setTextSize(textSize);
xml 定义如下:
<com.example.javris.paintdemo.MiUiClockView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:highColor="#ffffff"
app:textSize="12sp"/>
attrs.xml中属性定义如下:
<declare-styleable name="MiUiClockView">
<attr name="highColor" format="color"/>
<attr name="textSize" format="dimension"/>
<attr name="intSize" format="integer"/>
</declare-styleable>
这是自定义View获取属性的老套路了。首先Paint 设置textSize的大小单位是sp,这个大家是知道的,从上面代码得知:获取paint 所需要的textSize有两种方案:
TypedArray — getDimension(int index, float defValue)
TypedValue — applyDimension(int unit, float value,DisplayMetrics metrics)
TypeValue applyDimension()
首先看一下TextView setTextSize()方法源码
public void setTextSize(float size) {
setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
}
public void setTextSize(int unit, float size) {
Context c = getContext();
Resources r;
if (c == null)
r = Resources.getSystem();
else
r = c.getResources();
setRawTextSize(TypedValue.applyDimension(
unit, size, r.getDisplayMetrics()));
}
private void setRawTextSize(float size) {
if (size != mTextPaint.getTextSize()) {
mTextPaint.setTextSize(size);
if (mLayout != null) {
nullLayouts();
requestLayout();
invalidate();
}
}
}
TypeValue类
public static float applyDimension(int unit, float value,
DisplayMetrics metrics)
{
switch (unit) {
case COMPLEX_UNIT_PX:
return value;
case COMPLEX_UNIT_DIP:
return value * metrics.density;
case COMPLEX_UNIT_SP:
return value * metrics.scaledDensity;
case COMPLEX_UNIT_PT:
return value * metrics.xdpi * (1.0f/72);
case COMPLEX_UNIT_IN:
return value * metrics.xdpi;
case COMPLEX_UNIT_MM:
return value * metrics.xdpi * (1.0f/25.4f);
}
return 0;
}
其中textView 的单位为sp所以单位参数为COMPLEX_UNIT_SP,通过调用setRawTextSize方法给Paint设置textSize,而paint所需的textSize值单位位px,正是通过TypeValue 的 applyDimension()方法进行转换的,从Switch case 语句中可以很容易看出 sp 转px dp 转 px 。
那么现在做出如下总结:
TypeValue的applyDimension方法其作用就是通过传递不同的单位和数值,最终转换成为像素值.
TypedArray getDimension()
public float getDimension(@StyleableRes int index, float defValue) {
... //省略部分代码
final int attrIndex = index;
index *= AssetManager.STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index+AssetManager.STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
return defValue;
} else if (type == TypedValue.TYPE_DIMENSION) {
return TypedValue.complexToDimension(
data[index + AssetManager.STYLE_DATA], mMetrics);
}
...
}
代码比较长省去一些,可以看到调用了TypedValue的complexToDimension(),看一下这个方法的源码
TypeValue 类
public static final int COMPLEX_UNIT_SHIFT = 0;
public static final int COMPLEX_UNIT_MASK = 0xf;
public static float complexToDimension(int data, DisplayMetrics metrics)
{
return applyDimension(
(data>>COMPLEX_UNIT_SHIFT)&COMPLEX_UNIT_MASK,
complexToFloat(data),
metrics);
}
可以看到又调用到了applyDimension()方法,第一个参数为单位, 那么(data>>COMPLEX_UNIT_SHIFT)&COMPLEX_UNIT_MASK 运算的目的就是为了让data 0-3位和0xf (二级制 1111)进行与运算,所以Data的低四位存储的是单位。
第二个参数则是通过complexToFloat()方法。
public static final int COMPLEX_MANTISSA_MASK = 0xffffff;
public static final int COMPLEX_MANTISSA_SHIFT = 8;
private static final float[] RADIX_MULTS = new float[] {
1.0f*MANTISSA_MULT, 1.0f/(1<<7)*MANTISSA_MULT,
1.0f/(1<<15)*MANTISSA_MULT, 1.0f/(1<<23)*MANTISSA_MULT
};
public static final int COMPLEX_RADIX_SHIFT = 4;
public static final int COMPLEX_RADIX_MASK = 0x3;
public static float complexToFloat(int data)
{
return (data&(TypedValue.COMPLEX_MANTISSA_MASK
<<TypedValue.COMPLEX_MANTISSA_SHIFT))
* RADIX_MULTS[(data>>TypedValue.COMPLEX_RADIX_SHIFT)
& TypedValue.COMPLEX_RADIX_MASK];
}
0xffffff 转成2进制 1111 1111 1111 1111 1111 1111 左边移动8位 则为1111 1111 1111 1111 1111 1111 0000 0000 这就是一个32位的整形,data& (…)实际上也就想得到高24位的值,在看RADIX_MULTS[….] data>> 4 向右移动4位,还记得上面complexToDimension 方法中data 低四位(0-3位)是为了求换算单位,所以data 要先右移4位然后在和0x3 (二进制 0011)作与运算,这样实际上获取data 4-7位数值,并且最大数值超不过3,这样就能在RADIX_MULTS数组中选择一个数值了,这样来看实际上data 数据的低4位存储着数值的单位而高24则存储着数值。最终通过TypeValue的applyDimension 依旧可以得到px.
那么现在做出如下总结:
TypeArray 的getDimension()方法实际上则是通过调用TypeValue的complexToDimension()方法实现的,complexToDimension中传递的data 数据 0-3存储着转换单位,4-7位为了获取一个radix ,高24位则存储着数值。
其实像这种形式的存储数据方式在Android 中并不少见,比例MeasureSpec 同样是一个int 类型,它的高2位存储的是Mode,低30位存储的是Size.目的为的就是减少内存的分配。对MeasureSpec不理解可参考博客
ok,到这TypeValue 和 TypeArray 就算讲完了,你可能会有疑问TypeValue中complexToDimen中传递的data为什么低4位就存储着单位,高24位存储着数值呢?实际上这是系统在解析自定义xml布局的时候如: app:textSize=”12sp”时就会将数值12和单位sp利用位运算存储到data中了,在这就不再看android中的源码。