今天整理了一下项目中经常使用的组合控件,对属性进行了提取,扩展,尽量使这个组合控件更灵活、通用。
但过程当中,遇到一个问题:设置字体大小比预计的要大很多。
我先列出来自定义属性基本的使用步骤(很熟悉这个过程的朋友可以忽略下面这几个步骤,直接看问题分析部分)
1、抽取自定义属性tsc_TextSize到attr.xml中
<declare-styleable name="MTopSelectorControl">
<attr name="tsc_TextSize" format="dimension" />
//省略其它的属性...仅以tsc_TextSize一个作为示例
</declare-styleable>
2、在布局文件中设置自定义属性tsc_TextSize的值
<com.xxx.view.MTopSelectorControl
android:id="@+id/layout_topSelector"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
app:tsc_TextSize="@dimen/sp_14" />
3、在组合控件编码过程中,取出设定的tsc_TextSize的值(app:tsc_TextSize="@dimen/sp_14"),并给具体的控件设置字体大小
private float tsc_TextSize;//文本字体大小
/**
* 取出定义好的属性值
* @param attrs
*/
private void initTypedArray(AttributeSet attrs) {
//加载自定义的属性
tsc_TextSize = a.getDimension(R.styleable.MTopSelectorControl_tsc_TextSize, 12);
//回收资源,这一句必须调用
a.recycle();
}
/**
* 初始化顶部选择的控件
*/
private void initTextView(String strName) {
TextView textView = percentRelativeLayout.findViewById(R.id.tv_typename);
textView.setText(strName);
textView.setTextSize(tsc_TextSize);
}
通过上面这些操作步骤,基本上就能达到自定义属性的定义、赋值和使用了。但是这样一来,会发现字体大小比预计的要大很多。
下面进行问题分析:
1、首先看获取自定义属性值的代码
tsc_TextSize = a.getDimension(R.styleable.MTopSelectorControl_tsc_TextSize, 12); 点击去看getDimension源码:
public float getDimension(@StyleableRes int index, float defValue) {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
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);
} else if (type == TypedValue.TYPE_ATTRIBUTE) {
final TypedValue value = mValue;
getValueAt(index, value);
throw new UnsupportedOperationException(
"Failed to resolve attribute at index " + attrIndex + ": " + value);
}
throw new UnsupportedOperationException("Can't convert value at index " + attrIndex
+ " to dimension: type=0x" + Integer.toHexString(type));
}
再看 return TypedValue.complexToDimension(data[index + AssetManager.STYLE_DATA], mMetrics);
complexToDimension源码
public static float complexToDimension(int data, DisplayMetrics metrics)
{
//再接着点进去applyDimension 看源码
return applyDimension(
(data>>COMPLEX_UNIT_SHIFT)&COMPLEX_UNIT_MASK,
complexToFloat(data),
metrics);
}
再点进去看applyDimension 源码:
//这里是applyDimension源码
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;
//添加一个注释:我们使用的sp作为的单位,应该走下面这个case
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;
}
通过上面源码,不难发现一个关键现象:即将取到的自定义属性的值,在不同类型下,通过乘以既定的系数,进行PX转换,
那么最终得到的值,肯定是变化了的。具体到我们的例子,设定的是14sp,那么获取到的实际的数值是
return value * metrics.scaledDensity; 即,14*3(当前测试机的值)=42(px).
好,现在知道了 tsc_TextSize = a.getDimension(R.styleable.MTopSelectorControl_tsc_TextSize, 12);获取到的实际值是42(px)了
然后再看赋值过程: textView.setTextSize(tsc_TextSize); 正如本文上面提到的,看到的实际效果是文本大了很多。为什么呢?
那就让我们继续看看textView.setTextSize(tsc_TextSize);这个setTextSize方法的源码吧
@android.view.RemotableViewMethod
public void setTextSize(float size) {
//这里添加个注释:问题就出在这里,下面这个方法,直接给了一个默认值TypedValue.COMPLEX_UNIT_SP
setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
}
问题就出现在上面这个方法里了,给了默认值TypedValue.COMPLEX_UNIT_SP,而上面我们获取到的自定义属性的值,已经是给转换成px的值了,这里再按照sp为单位进行setTextSize ,肯定是要变大了。
再继续看 setTextSize(TypedValue.COMPLEX_UNIT_SP, size);的源码
public void setTextSize(int unit, float size) {
if (!isAutoSizeEnabled()) {
setTextSizeInternal(unit, size, true /* shouldRequestLayout */);
}
}
//这里添加个注释:setTextSizeInternal方法的源码就不贴出来了,感兴趣的朋友可以继续点进去查看。
发现这个方法,其实是public的,我们也是可以调用的.那么,结合我们的实际场景,我们应该采用TypedValue.COMPLEX_UNIT_PX,也就是要用px作为单位来进行setTextSize ,即:textView.setTextSize(TypedValue.COMPLEX_UNIT_PX,tsc_TextSize);
改变了之后,运行会发现,是理想效果了。
另外啊,a.getDimension()\a.getDimensionPixelOffset()\a.getDimensionPixelSize()三者的区别,这里,我推荐大家自己去看看源码,源码比较简单,比较容易分析出来究竟是怎么个区别,而不是直接从网上搜出来三者区别的结论来。
结语:
本文提到的问题,并不是什么大问题,贴出来,其实就是想跟大家分享一下解决一些基本问题的思路:遇到问题之后,尽量的深入源码去分析问题、提炼出来解决方法。在时间允许的情况下,养成阅读源码的好习惯,并要有足够的耐心,这会对我们提高编码能力大有益处的。