浅扒Android动态设置字体大小

简介: 说点废话Android开发中,TextView类的控件应该说是很常用了。一般来说我们是通过android:textSize="20sp"来设置字体大小,但是很多时候也需要动态设置字体大小,调用也很简单:textView.

说点废话

Android开发中,TextView类的控件应该说是很常用了。一般来说我们是通过android:textSize="20sp"
来设置字体大小,但是很多时候也需要动态设置字体大小,调用也很简单:

textView.setTextSize(textSize);

为了适配各种各样的型号,我们一般会将字体大小定义到dimens.xml之中:

<dimen name="text_size">16sp</dimen>

然后在java代码中设置定义好的字体大小:

float dimen = getResources().getDimension(R.dimen.text_size);
textView.setTextSize(dimen);

满心欢喜的运行一下,看一效果,结果发现字体奇大无比!!!远非16sp!!!难道不应该通过getDimension()取值吗?通过logcat我发现,在Nexus 6p并且<dimen name="text_size">16sp</dimen>下,在通过getDimension(R.dimen.text_size)得到返回值是56.0!
实际上,在java代码中取在dimens.xml中定义的值一共有三种:

  • getDimension()
  • getDimensionPixelOffset()
  • getDimensionPixelSize()

看到这三个函数的名称时,还是会有点不知所云。本着“不求甚解,遍历式开发”的原则,我把这三种方式都试了一遍,结果发现字体大小没一个是对的,这就诡异了。难道这里有平行宇宙?至此,我只能翻出我的英汉大词典,让我们去探寻一下docs吧。

getDimension()

    /**
     * Retrieve a dimensional for a particular resource ID.  Unit 
     * conversions are based on the current {@link DisplayMetrics} associated
     * with the resources.
     * 
     * @param id The desired resource identifier, as generated by the aapt
     *           tool. This integer encodes the package, type, and resource
     *           entry. The value 0 is an invalid identifier.
     * 
     * @return Resource dimension value multiplied by the appropriate 
     * metric.
     */
    public float getDimension(@DimenRes int id) throws NotFoundException {
      
    }

通过注释我们不难发现,getDimension()是根据指定id获取一个基于当前DisplayMetrics的值。这个值究竟是什么也没有说,只知道是float,并且单位转换是基于当前资源的,但肯定不是像素,如果是像素应该是int。

getDimensionPixelSize

   /**
     * Retrieve a dimensional for a particular resource ID for use
     * as a size in raw pixels.  This is the same as
     * {@link #getDimension}, except the returned value is converted to
     * integer pixels for use as a size.  A size conversion involves
     * rounding the base value, and ensuring that a non-zero base value
     * is at least one pixel in size.
     * 
     * @param id The desired resource identifier, as generated by the aapt
     *           tool. This integer encodes the package, type, and resource
     *           entry. The value 0 is an invalid identifier.
     * 
     * @return Resource dimension value multiplied by the appropriate 
     * metric and truncated to integer pixels.
     */
    public int getDimensionPixelSize(@DimenRes int id) throws NotFoundException {
 
    }

getDimensionPixelSize()的功能与getDimension()类似,不同的是将结果转换为int,并且小数部分四舍五入,这个结果将作为尺寸。getDimensionPixelSize()进行了尺寸转换,这个转换实际是上四舍五入的结果,并且保证返回值是一个至少是1像素的非零数值。

getDimensionPixelOffset()

    /**
     * Retrieve a dimensional for a particular resource ID for use
     * as an offset in raw pixels.  This is the same as
     * {@link #getDimension}, except the returned value is converted to
     * integer pixels for you.  An offset conversion involves simply
     * truncating the base value to an integer.
     * 
     * @param id The desired resource identifier, as generated by the aapt
     *           tool. This integer encodes the package, type, and resource
     *           entry. The value 0 is an invalid identifier.
     * 
     * @return Resource dimension value multiplied by the appropriate 
     * metric and truncated to integer pixels.
     */
    public int getDimensionPixelOffset(@DimenRes int id) throws NotFoundException {
    }

getDimensionPixelOffset()getDimension()功能类似,不同的是将结果转换为int,这个结果将用作原始像素的偏移量。偏移转换(offset conversion,函数命名中的offset是这个意思)的作用之一是将基础值简单地截短为整数,注意直接截断小数位,即取整(其实就是把float强制转化为int,注意不是四舍五入)。

阶段性总结

由此可见,这三个函数返回的都是绝对尺寸,而不是相对尺寸(dp\sp等)。如果getDimension()返回结果是30.5f,那么getDimensionPixelSize()返回结果就是31,getDimensionPixelOffset()返回结果就是30。
至此,应该说getDimensionPixelSize() getDimension() getDimensionPixelOffset()我们已经大致有所了解了,但是如果想更深入了解一下,就需要深入源码以验证上述解释。

扒源码

深入源码,我们可以发现其实这三个函数的实现大同小异,以getDimension()为例:

  public float getDimension(@DimenRes int id) throws NotFoundException {
        final TypedValue value = obtainTempTypedValue();
        try {
            final ResourcesImpl impl = mResourcesImpl;
            impl.getValue(id, value, true);
            if (value.type == TypedValue.TYPE_DIMENSION) {
                return TypedValue.complexToDimension(value.data, impl.getDisplayMetrics());
            }
            throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
                    + " type #0x" + Integer.toHexString(value.type) + " is not valid");
        } finally {
            releaseTempTypedValue(value);
        }
    }

TypedValue是动态类型数据的容器,其主要用于盛放Resources的值。上述代码第7行就是根据id获取TypedValue的值,getDimension()getDimensionPixelOffset()getDimensionPixelSize()函数体唯一的不同就是第7行:

  • getDimension()调用的是TypedValuecomplexToDimension()方法
  • getDimensionPixelSize调用的是TypedValuecomplexToDimensionPixelSize()方法
  • getDimensionPixelOffset调用的是TypedValuecomplexToDimensionPixelOffset()方法

顺藤摸瓜,我们继续深入ypedValue,查看complexToDimension()complexToDimensionPixelSize()complexToDimensionPixelOffset()函数的区别,会发现这三个函数体内容依旧大同小异,以complexToDimension()为例:

    public static float complexToDimension(int data, DisplayMetrics metrics) {
        return applyDimension(
            (data>>COMPLEX_UNIT_SHIFT)&COMPLEX_UNIT_MASK,
            complexToFloat(data),
            metrics);
    }

complexToDimensionPixelOffset()complexToDimension()不同的是将结果进行了强转,实际上相当直接截断小数部分;
complexToDimensionPixelSize()是将结果进行四舍五入,并取整。这里的四舍五入实际上就是把结果加上0.5f然后进行强转(有兴趣了解原理的可以留言)。

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;
        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;
    }

在上述代码中,我们发现在applyDimension()中根据单位的不同,将float乘上不同的系数。如dip/dp需乘上屏幕系数,sp则需乘上字号的缩放系数,pt、in、mm等也是根据相应的算法进行换算(从COMPLEX_UNIT_PX直接返回float可以看出,该方法是将数值转成像素数)。

再次总结

通过上述探索,我们不难发现,在Adroid并没有在java代码中直接获取dimens.xml中定义的dp(dip)/sp的值的API,只有getDimension()getDimensionPixelOffset()getDimensionPixelSize()这个三个方法来获取绝对尺寸。但有时候我们确实需要动态获取dimen.xml中的值,并为TextView设置字体大小。而这种方法直接应用在textView.setTextSize(dimen);都是有问题的。那我们将从TextView入手,寻找一个正确的姿势来设置字体大小。

setTextSize()

首先把代码端上来:

  public void setTextSize(float size) {
        setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
    }

原来setTextSize(float)调用了他的重载方法setTextSize(int,float),并且第一个参数传的默认值是TypedValue.COMPLEX_UNIT_SP,眼熟吗,没错就是之前提到的。那么,我们继续看看一下setTextSize(int,float)做了什么:

   public void setTextSize(int unit, float size) {
        if (!isAutoSizeEnabled()) {
            setTextSizeInternal(unit, size, true /* shouldRequestLayout */);
        }
    }

很显然是调用了setTextSizeInternal(unit, size, true /* shouldRequestLayout */);。看到这累不,不过看都看了就再看看呗,说不定比苍老师好看:

    private void setTextSizeInternal(int unit, float size, boolean shouldRequestLayout) {
        Context c = getContext();
        Resources r;

        if (c == null) {
            r = Resources.getSystem();
        } else {
            r = c.getResources();
        }

        setRawTextSize(TypedValue.applyDimension(unit, size, r.getDisplayMetrics()),shouldRequestLayout);
    }

高能!!!TypedValue.applyDimension(unit, size, r.getDisplayMetrics())是不是很眼熟???还记得applyDimension()是怎么处理数据的吗?

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

综上,setTextSize(float)给传的值的单位其实是SP,但通过getDimension()取的值却不是这样的。为了证实默认单位是SP,各位看官可以直接传个16,看看和16sp是不是一样的。所以问题是不得到了解决?

结论

Android中并不提供直接从dimens.xml获取dp/sp数值的方法,通过getDimensionPixelSize() getDimension() getDimensionPixelOffset()获取的值是经过处理的。所以正确地动态设置TextView字体大小的姿势应该是:

int dimen = getResources().getDimensionPixelSize(R.dimen.text_size);
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX,dimen);
目录
相关文章
|
4月前
|
XML API Android开发
码农之重学安卓:利用androidx.preference 快速创建一、二级设置菜单(demo)
本文介绍了如何使用androidx.preference库快速创建具有一级和二级菜单的Android设置界面的步骤和示例代码。
140 1
码农之重学安卓:利用androidx.preference 快速创建一、二级设置菜单(demo)
|
3月前
|
Android开发
Android经典实战之Textview文字设置不同颜色、下划线、加粗、超链接等效果
本文介绍了 `SpannableString` 在 Android 开发中的强大功能,包括如何在单个字符串中应用多种样式,如颜色、字体大小、风格等,并提供了详细代码示例,展示如何设置文本颜色、添加点击事件等,助你实现丰富文本效果。
308 3
|
4月前
|
Java 网络安全 开发工具
UNITY与安卓⭐一、Android Studio初始设置
UNITY与安卓⭐一、Android Studio初始设置
|
5月前
|
XML Android开发 数据格式
Android 中如何设置activity的启动动画,让它像dialog一样从底部往上出来
在 Android 中实现 Activity 的对话框式过渡动画:从底部滑入与从顶部滑出。需定义两个 XML 动画文件 `activity_slide_in.xml` 和 `activity_slide_out.xml`,分别控制 Activity 的进入与退出动画。使用 `overridePendingTransition` 方法在启动 (`startActivity`) 或结束 (`finish`) Activity 时应用这些动画。为了使前 Activity 保持静止,可定义 `no_animation.xml` 并在启动新 Activity 时仅设置新 Activity 的进入动画。
148 12
|
3月前
|
图形学 iOS开发 Android开发
从Unity开发到移动平台制胜攻略:全面解析iOS与Android应用发布流程,助你轻松掌握跨平台发布技巧,打造爆款手游不是梦——性能优化、广告集成与内购设置全包含
【8月更文挑战第31天】本书详细介绍了如何在Unity中设置项目以适应移动设备,涵盖性能优化、集成广告及内购功能等关键步骤。通过具体示例和代码片段,指导读者完成iOS和Android应用的打包与发布,确保应用顺利上线并获得成功。无论是性能调整还是平台特定的操作,本书均提供了全面的解决方案。
160 0
|
4月前
|
开发工具 Android开发
Android项目架构设计问题之外部客户方便地设置回调如何解决
Android项目架构设计问题之外部客户方便地设置回调如何解决
31 0
|
4月前
|
数据可视化 Java 数据挖掘
Android项目架构设计问题之设置RecyclerView的LayoutManager如何解决
Android项目架构设计问题之设置RecyclerView的LayoutManager如何解决
39 0
|
5月前
|
Java Android开发
android 设置系统时间的流程
android 设置系统时间的方法
425 2
|
5月前
|
Android开发
Android面试题经典之如何全局替换App的字体
在Android应用中替换字体有全局和局部方法。全局替换涉及在`Application`的`onCreate`中设置自定义字体,并创建新主题。局部替换则可在布局中通过`ResourcesCompat.getFont()`加载字体文件并应用于`TextView`。
89 2
|
6月前
|
Android开发 开发者
Android UI设计中,Theme定义了Activity的视觉风格,包括颜色、字体、窗口样式等,定义在`styles.xml`。
【6月更文挑战第26天】Android UI设计中,Theme定义了Activity的视觉风格,包括颜色、字体、窗口样式等,定义在`styles.xml`。要更改主题,首先在该文件中创建新主题,如`MyAppTheme`,覆盖所需属性。然后,在`AndroidManifest.xml`中应用主题至应用或特定Activity。运行时切换主题可通过重新设置并重启Activity实现,或使用`setTheme`和`recreate()`方法。这允许开发者定制界面并与品牌指南匹配,或提供多主题选项。
95 6