Android 关于组合控件(自定义控件等)中自定义属性,通过getDimension后 设置字体大小比实际要大很多的问题分析

今天整理了一下项目中经常使用的组合控件,对属性进行了提取,扩展,尽量使这个组合控件更灵活、通用。

但过程当中,遇到一个问题:设置字体大小比预计的要大很多。

我先列出来自定义属性基本的使用步骤(很熟悉这个过程的朋友可以忽略下面这几个步骤,直接看问题分析部分)

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()三者的区别,这里,我推荐大家自己去看看源码,源码比较简单,比较容易分析出来究竟是怎么个区别,而不是直接从网上搜出来三者区别的结论来。

结语:

本文提到的问题,并不是什么大问题,贴出来,其实就是想跟大家分享一下解决一些基本问题的思路:遇到问题之后,尽量的深入源码去分析问题、提炼出来解决方法。在时间允许的情况下,养成阅读源码的好习惯,并要有足够的耐心,这会对我们提高编码能力大有益处的。

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值