Android 字体国际化适配方法以及源码解析

2023-11-17

起源

由于我们公司的app,支持多国语言,所以就导致了 同样的文案,但是长度不同,就会出现适配的问题,因为 中文 是 字表义,外文是 音表义。 今天就用8.0新特新来解决这个问题。

适配前是这样的

这里写图片描述

在固定的宽高就会出现适配的问题,在之前博客中也写过解决方案
多语言适配,让text自动改变大小
但现在谷歌已经完全解决了该问题,我们就直接用谷歌的了。

适配方法

只支持最低版本是26的方法

    android:autoSizeTextType="uniform" 设置字体类型
    android:autoSizeMinTextSize="12sp"  设置最大值
    android:autoSizeMaxTextSize="100sp"  设置最小值
    android:autoSizeStepGranularity="2sp"  设置颗粒度
    android:autoSizePresetSizes="@array/autosize_text_sizes" 预设字体的大小

但是现在基本最低版本都是14。 v4包提供了一些方法解决此问题,一共有三个方法


public static void setAutoSizeTextTypeWithDefaults(
           TextView textView, 
           int autoSizeTextType) 设置字体类型


public static void setAutoSizeTextTypeUniformWithConfiguration(
            TextView textView,
            int autoSizeMinTextSize,
            int autoSizeMaxTextSize,
            int autoSizeStepGranularity, 设置颗粒度 字体变大变小的幅度,默认是1
            int unit)

public static void setAutoSizeTextTypeUniformWithPresetSizes(
            TextView textView,
            @NonNull int[] presetSizes,  设置字体预设值,字体的变化只会在预设的几个值内变化
            int unit)

适配后

这里写图片描述

如果只想显示一行代码

这里写图片描述

代码

/**
 * Created by wanghao on 2017/11/13.
 */

public class MultiLanguageTextViewActivity extends AppCompatActivity {
    TextView tv_content_cs;
    TextView tv_content_us;
    TextView tv_content_fa;
    TextView tv_content_ru;
    LinearLayout ll_content;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.multi_language);
        tv_content_cs = findViewById(R.id.tv_content_cs);
        tv_content_us = findViewById(R.id.tv_content_us);
        tv_content_fa = findViewById(R.id.tv_content_fa);
        tv_content_ru = findViewById(R.id.tv_content_ru);
        ll_content = findViewById(R.id.ll_content);
    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu, menu);
        return true;
    }


    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        switch (id) {
            case R.id.action_none:
                AutoSizingAdapter(TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE);
                break;
            case R.id.action_uniform:
                setLine(2);
                AutoSizingAdapter(TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM);

                break;
            case R.id.action_singleLine:
                setLine(1);
                AutoSizingAdapter(TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM);

                break;
        }
        return super.onOptionsItemSelected(item);
    }

    public void setLine(int lineNum) {
        tv_content_cs.setLines(lineNum);
        tv_content_us.setLines(lineNum);
        tv_content_fa.setLines(lineNum);
        tv_content_ru.setLines(lineNum);
    }

    public void AutoSizingAdapter(int type) {
        TextViewCompat.setAutoSizeTextTypeWithDefaults(tv_content_cs, type);
        TextViewCompat.setAutoSizeTextTypeWithDefaults(tv_content_us, type);
        TextViewCompat.setAutoSizeTextTypeWithDefaults(tv_content_fa, type);
        TextViewCompat.setAutoSizeTextTypeWithDefaults(tv_content_ru, type);
    }


}


xml

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:id="@+id/ll_content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <Button
            android:id="@+id/tv_content_cn"
            android:layout_width="match_parent"
            android:layout_height="60dp"
            android:layout_toRightOf="@id/tv_country"
            android:gravity="center_vertical"
            android:text="中文:我爱北京天安门"
            android:textSize="30sp" />

        <Button
            android:id="@+id/tv_content_cs"

            android:layout_width="match_parent"
            android:layout_height="60dp"
            android:layout_marginTop="10dp"
            android:layout_toRightOf="@id/tv_country"
            android:gravity="center_vertical"
            android:text="捷克:Miluji Peking Tiananmen Square"
            android:textSize="30sp" />

        <Button
            android:id="@+id/tv_content_fa"
            android:layout_width="match_parent"
            android:layout_height="60dp"
            android:layout_marginTop="10dp"
            android:layout_toRightOf="@id/tv_country"
            android:gravity="center_vertical"
            android:text="阿拉伯语:أنا أحب ساحة تيانانمن بكين"
            android:textSize="30sp" />

        <Button
            android:id="@+id/tv_content_us"
            android:layout_width="match_parent"
            android:layout_height="60dp"
            android:layout_marginTop="10dp"
            android:layout_toRightOf="@id/tv_country"
            android:gravity="center_vertical"
            android:text="英语:I love Beijing Tiananmen Square"
            android:textSize="30sp" />

        <Button
            android:id="@+id/tv_content_ru"
            android:layout_width="match_parent"
            android:layout_height="60dp"
            android:layout_marginTop="10dp"
            android:layout_toRightOf="@id/tv_country"
            android:gravity="center_vertical"
            android:text="俄罗斯:Я люблю Пекинскую площадь Тяньаньмэнь"
            android:textSize="30sp" />

    </LinearLayout>
</ScrollView>

Button上的样式 一样通用

这里写图片描述

美滋滋。

到这里只用到了一个方法就是

public static void setAutoSizeTextTypeWithDefaults(
           TextView textView, 
           int autoSizeTextType) 设置字体类型

什么是颗粒度 预设值呢?

颗粒度Granularity

就是 当你的字体不断变大,默认是 不断让字体增加1,如果你的颗粒度设置为10,那么每次 字体变大就增加10,
比如 0,10,20,30 这样子

反过来
就是 你字体内容不断变多,显示不开的时候,没自动让字体变小,默认是不断让字体减1,如果你的颗粒度是10,那么每次字体减少10.
比如 30,20,10,0 这样子


public static void setAutoSizeTextTypeUniformWithConfiguration(
            TextView textView,
            int autoSizeMinTextSize,
            int autoSizeMaxTextSize,
            int autoSizeStepGranularity, 设置颗粒度 字体变大变小的幅度,默认是1
            int unit)

如下动图

这里我们设置
最大字体:400
最小字体:1
颗粒度:50
单位:px
xml字体设置:300px
这里写图片描述

我们发现。输入一个字符,字体大小是251,然后不断地减少50.
那么字体为什么是251呢?

一会儿一起看源码。

预设值 PresetSizes

预设值是一个数组,字体变大变小只会在这个数组的值中进行切换,选择一个最合适的

使用方法方法

TextViewCompat.setAutoSizeTextTypeUniformWithPresetSize

现在设置数组为
{50, 105,200,100, 219, 230, 251} 随机,也没有规律
然后不断的增加输入的内容
如下效果:
这里写图片描述

可以发现。字体变化顺序是
251,230,200,105
为什么没有显示219呢。是因为219显示不开。就跳过去了。

看源码,如何实现

这里我们通过分析

TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration

方法就能了解它的原理。

这是V4包中我们调用的方法

 public static void setAutoSizeTextTypeUniformWithConfiguration(
            TextView textView,
            int autoSizeMinTextSize,
            int autoSizeMaxTextSize,
            int autoSizeStepGranularity,
            int unit) throws IllegalArgumentException {
        IMPL.setAutoSizeTextTypeUniformWithConfiguration(textView, autoSizeMinTextSize,
                autoSizeMaxTextSize, autoSizeStepGranularity, unit);
    }
这是V4包中我们调用的方法

在查看IMPL 方法


    static final TextViewCompatBaseImpl IMPL;

    static {
        if (Build.VERSION.SDK_INT >= 26) {
            IMPL = new TextViewCompatApi26Impl();
        } else if (Build.VERSION.SDK_INT >= 23) {
            IMPL = new TextViewCompatApi23Impl();
        } else if (Build.VERSION.SDK_INT >= 18) {
            IMPL = new TextViewCompatApi18Impl();
        } else if (Build.VERSION.SDK_INT >= 17) {
            IMPL = new TextViewCompatApi17Impl();
        } else if (Build.VERSION.SDK_INT >= 16) {
            IMPL = new TextViewCompatApi16Impl();
        } else {
            IMPL = new TextViewCompatBaseImpl();
        }
    }

往下看TextViewCompatApi26Impl 方法中


    @RequiresApi(26)
    static class TextViewCompatApi26Impl extends TextViewCompatApi23Impl {

    ...省略代码

        @Override
        public void setAutoSizeTextTypeUniformWithConfiguration(
                TextView textView,
                int autoSizeMinTextSize,
                int autoSizeMaxTextSize,
                int autoSizeStepGranularity,
                int unit) throws IllegalArgumentException {
            textView.setAutoSizeTextTypeUniformWithConfiguration(
                    autoSizeMinTextSize, autoSizeMaxTextSize, autoSizeStepGranularity, unit);
        }

        ...省略代码
}

进到setAutoSizeTextTypeUniformWithConfiguration 里面看看

public void setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize,
            int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit) {
        //判断是否支持字体缩放
        if (supportsAutoSizeText()) {
            final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
            //获取单位是px的最小值
            final float autoSizeMinTextSizeInPx = TypedValue.applyDimension(
                    unit, autoSizeMinTextSize, displayMetrics);
            //获取单位是px的最大值
            final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension(
                    unit, autoSizeMaxTextSize, displayMetrics);
            //获取单位是px的颗粒度
            final float autoSizeStepGranularityInPx = TypedValue.applyDimension(
                    unit, autoSizeStepGranularity, displayMetrics);

            //把这三个值 赋值给 textview中相应的变量
validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx,
                    autoSizeMaxTextSizeInPx,
                    autoSizeStepGranularityInPx);
//重点来了,现在获取到了 最大值,最小值,颗粒度三个值。下面进行一些逻辑判断
            if (setupAutoSizeText()) {//进一步判断是否能字体缩放
                autoSizeText();// 得到一个最合适的字体
                invalidate();//刷新
            }
        }
    }


先看看setupAutoSizeText 方法

    private boolean setupAutoSizeText() {
        //能否进行缩放操作
        if (supportsAutoSizeText() && mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) {
            // Calculate the sizes set based on minimum size, maximum size and step size if we do
            // not have a predefined set of sizes or if the current sizes array is empty.
            //这段英文意思是
            //如果我们没有定义一些数值,可以通过最大值,最小值,颗粒度,三个值来获取一些 sizes 
            //这个一些数值 就是上面说的第三个方法setAutoSizeTextTypeUniformWithPresetSize,设定的 预设值 。
            //但是这个方法没有,只能计算一下
            if (!mHasPresetAutoSizeValues || mAutoSizeTextSizesInPx.length == 0) {
                //size的数量
                int autoSizeValuesLength = 1;
                //获取不带小数的最小值
                float currentSize = Math.round(mAutoSizeMinTextSizeInPx);
                while (Math.round(currentSize + mAutoSizeStepGranularityInPx)
                        <= Math.round(mAutoSizeMaxTextSizeInPx)) {
                        //获取这个sizes 有多少个数
                    autoSizeValuesLength++;
                    currentSize += mAutoSizeStepGranularityInPx;
                }
                //创先size数组
                int[] autoSizeTextSizesInPx = new int[autoSizeValuesLength];
                //先获取最小值
                float sizeToAdd = mAutoSizeMinTextSizeInPx;
                for (int i = 0; i < autoSizeValuesLength; i++) {
                    autoSizeTextSizesInPx[i] = Math.round(sizeToAdd);
                    //获取数组里面的一些列值
                    sizeToAdd += mAutoSizeStepGranularityInPx;
                }
                //把获取的数组 ,赋值给全局变量
                //加入最小值是1,最大值是100,颗粒度是30 
                //现在获取到的数组是 {1,31,61,91} 四个值
                mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(autoSizeTextSizesInPx);
            }

            mNeedsAutoSizeText = true;
        } else {
            mNeedsAutoSizeText = false;
        }

        return mNeedsAutoSizeText;
    }

下面在看autoSizeText() 方法


    /**
     * Automatically computes and sets the text size.
     * 自动计算并设置文本大小。
     */
    private void autoSizeText() {
        if (!isAutoSizeEnabled()) {
            return;
        }

        if (mNeedsAutoSizeText) {
            if (getMeasuredWidth() <= 0 || getMeasuredHeight() <= 0) {
                return;
            }
            //获取界面字体可显示的宽度
            final int availableWidth = mHorizontallyScrolling
                    ? VERY_WIDE
                    : getMeasuredWidth() - getTotalPaddingLeft() - getTotalPaddingRight();
            //获取界面字体可显示的高度
            final int availableHeight = getMeasuredHeight() - getExtendedPaddingBottom()
                    - getExtendedPaddingTop();

            if (availableWidth <= 0 || availableHeight <= 0) {
                return;
            }

            synchronized (TEMP_RECTF) {
                TEMP_RECTF.setEmpty();
                //把宽高赋值给rectf
                TEMP_RECTF.right = availableWidth;
                TEMP_RECTF.bottom = availableHeight;
                // 获取rectf 范围内 ,可以显示字体的最大值
                final float optimalTextSize = findLargestTextSizeWhichFits(TEMP_RECTF);

                if (optimalTextSize != getTextSize()) {
                //给textview 赋值
                    setTextSizeInternal(TypedValue.COMPLEX_UNIT_PX, optimalTextSize,
                            false /* shouldRequestLayout */);
//
                    makeNewLayout(availableWidth, 0 /* hintWidth */, UNKNOWN_BORING, UNKNOWN_BORING,
                            mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
                            false /* bringIntoView */);
                }
            }
        }
        // Always try to auto-size if enabled. Functions that do not want to trigger auto-sizing
        // after the next layout pass should set this to false.
        mNeedsAutoSizeText = true;
    }

在看是如何获取范围内的最大值的呢


    /**
     * Performs a binary search to find the largest text size that will still fit within the size
     * available to this view.
     * 执行一个二进制搜索,以找到最大的文本大小,它仍然符合这个视图的大小。
     */
    private int findLargestTextSizeWhichFits(RectF availableSpace) {
        final int sizesCount = mAutoSizeTextSizesInPx.length;
        if (sizesCount == 0) {
            throw new IllegalStateException("No available text sizes to choose from.");
        }
        //下面是一个二分法的算法。
        int bestSizeIndex = 0;
        int lowIndex = bestSizeIndex + 1;//头
        int highIndex = sizesCount - 1;//尾
        int sizeToTryIndex;
        while (lowIndex <= highIndex) {
            sizeToTryIndex = (lowIndex + highIndex) / 2;//去中间值
            //还记得上面的我们定义的一个数组么{1,31,61,91}
            // 可以走一下下面的方法,感受一下二分法的魅力
            if (suggestedSizeFitsInSpace(mAutoSizeTextSizesInPx[sizeToTryIndex], availableSpace)) {
                bestSizeIndex = lowIndex;
                lowIndex = sizeToTryIndex + 1;
            } else {
                highIndex = sizeToTryIndex - 1;
                bestSizeIndex = highIndex;
            }
        }
        //这里返回一个最终值
        return mAutoSizeTextSizesInPx[bestSizeIndex];
    }

在来看看这个方法suggestedSizeFitsInSpace 是如何用 rectf 来判断字体是否合适的
    private boolean suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace) {

       省略代码...

        mTempTextPaint.set(getPaint());//获取画笔
        mTempTextPaint.setTextSize(suggestedSizeInPx);

//获取一个StaticLayout 。来让一行显示不开的字体 换行 
        final StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain(
                text, 0, text.length(),  mTempTextPaint, Math.round(availableSpace.right));

        layoutBuilder.setAlignment(getLayoutAlignment())
                .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier())
                .setIncludePad(getIncludeFontPadding())
                .setBreakStrategy(getBreakStrategy())
                .setHyphenationFrequency(getHyphenationFrequency())
                .setJustificationMode(getJustificationMode())
                .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
                .setTextDirection(getTextDirectionHeuristic());

        final StaticLayout layout = layoutBuilder.build();

        //换行后判断是否大于设定的maxLines
        // Lines overflow.
        if (maxLines != -1 && layout.getLineCount() > maxLines) {
            return false;
        }

        //换行后判断高度是否高于rectf的高度
        // Height overflow.
        if (layout.getHeight() > availableSpace.bottom) {
            return false;
        }

        return true;
    }


结束

到这里就结束了。希望对你们有帮助。这里卖个关子。我上面的代码完全是TextView里面的。只适合在sdk是26的时候使用。下一篇介绍一下。V4包是如何 做到兼容低版本的
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android 字体国际化适配方法以及源码解析 的相关文章

随机推荐

  • 重启kibana的脚本

    2019独角兽企业重金招聘Python工程师标准 gt gt gt bin bash echo e 033 31m stop kibana by 5601 Now 033 0m ps ef netstat anp grep 5601 awk
  • 【Java笔记】Java8中数组(引用类型)、String、List、Set之间的相互转换问题

    数组 String List Set之间的相互转换问题 如果觉得对你有帮助 能否点个赞或关个注 以示鼓励笔者呢 博客目录 先点这里 本博主要是相理清楚一些转换的逻辑 顺便总结一下贴出来 这里是把一些可以JDK自带的使用方法写出了 不代表没有
  • 解析js面试题(上)

    注 本篇博客的题目出自于博主 小平果118博客 题目地址 http blog csdn net i10630226 article details 49765737 由于该博客并没有抛出来来答案和解析 我在这里以自己的理解来做一下这些题目
  • MicroBlaze系列教程(3):AXI_TIMER的使用

    文章目录 toc AXI TIMER简介 常用函数 使用示例 参考资料 工程下载 本文是Xilinx MicroBlaze系列教程的第3篇文章 AXI TIMER简介 AXI TIMER支持两路可编程32位计数器 可以配置为中断 捕获 PW
  • LoadRunner下载和安装

    文章目录 一 下载地址 二 安装步骤 三 安装过程中遇到的问题 一 下载地址 官方下载地址 https my microfocus com myproducts 网盘下载地址 https pan baidu com s 1hiGC9FjfK
  • cudnn下载与安装

    cudnn的安装 cudnn的安装非常简单 1 下载安装文件 按需求下载cudnn的安装文件 https developer nvidia com rdp cudnn archive 需要注册账号 2 安装cudnn 解压下载的文件 可以看
  • 搭建完整的arm-linux-gcc等交叉编译环境

    搭建完整的arm linux gcc等交叉编译环境 链接 https www cnblogs com uestc mm p 6656325 html 安装环境 Linux版本 Ubuntu 12 04 内核版本 Linux 3 5 0 交叉
  • Android AutoLayout集成使用

    转载自楊帥简书 https www jianshu com p da5d5d4d0901 由于Android屏幕尺寸众多的情况 Android适配一直是个问题 谷歌推出了百分比布局 本人有使用 但是百分比布局在部分时候适配并不是那么完美 偶
  • docker(六)数据挂载之数据卷

    docker容器运行时 是有一个可写入层的 如果我们把容器运行时的数据写在可写入层 会有以下问题 当容器停止运行时 写入的数据会丢失 你也很难将这些数据从容器中取出来给另外的应用程序使用 容器的可写入层与宿主机是紧密耦合的 这些写入的数据在
  • 指针的进阶【下篇】

    文章目录 8 指向函数指针数组的指针 9 回调函数 8 指向函数指针数组的指针 请看代码与注释 int Add int x int y return x y int Sub int x int y return x y int main i
  • Ubuntu22.04.1 Server版本 配置C++开发环境----笔记

    远程ssh 没记错在安装 server版的时候是可以选的 如果没有就执行安装命令 sudo install openssh server y 下载完成后配置端口22 vim etc ssh ssh config 重启ssh sudo ser
  • 利用回调获取自拟定Dialog的返回值

    这里要讲的是如何利用回调获取自拟定DiaLog的返回值 首先看下问题的背景 我们经常会用到Dialog 系统提供了一些常用的Dialog 可以参考这篇博客 点击打开链接 那其中一个常用Dialog做讲解 span style font si
  • java实现进程管理,Java调用批处理或可执行文件和Runtime、Process类实现Java版进程管理器...

    Java调用批处理或可执行文件 用Java编写应用时 有时需要在程序中调用另一个现成的可执行程序或系统命令 这时可以通过组合使用Java提供的Runtime类和Process类的方法实现 下面是一种比较典型的程序模式 Process pro
  • 大数据时代如何保护数据安全?

    当前 大数据正在成为信息时代的核心战略资源 对国家治理能力 经济运行机制 社会生活方式产生了深刻影响 与此同时 各项应用技术背后的数据安全风险也日益凸显 近年来 有关数据泄露 数据窃听 数据滥用等安全事件屡见不鲜 保护数据资产已引起各国高度
  • JS/CSS 实现鼠标移动更改列表奇偶行背景色

    需求 鼠标移动到一个列表时 奇数行显示一种背景色 偶数行显示另一种背景色 鼠标移出时 背景色复原 实现效果 代码实现 CSS JS
  • matlab正激变换器,12个步骤设计恒流正激式开关电源

    原标题 12个步骤设计恒流正激式开关电源 摘要 1 电源技术要求 2 设计步骤 3 变压器设计 4 输出滤波器设计 5 复位电路计算 6 功率开关管选择 7 输出二极管选择 8 恒流输出电路设计 9 缓冲吸收电路设计 10 控制电路设计 1
  • 华为ensp安装包及安装教程(详细版)

    今天给小伙伴们详细介绍一下ensp的安装教程 附有安装包有 可自行下载 目录 一 安装包链接 二 安装详细步骤 1 安装Oracle VM VirtualBox 2 安装Wireshark 3 安装ensp 4 安装完成 三 总结 一 安装
  • Linux中动态内存的分配与回收(heap, buddy system, stab)

    1 malloc 和free的原理 http m blog csdn net article details id 39496057 从操作系统角度来看 进程分配内存有两种方式 分别由两个系统调用完成 brk和mmap 不考虑共享内存 1
  • JS实现制作弹出小窗口并获取参数值3(父窗口获取子窗口值 JS)

  • Android 字体国际化适配方法以及源码解析

    起源 由于我们公司的app 支持多国语言 所以就导致了 同样的文案 但是长度不同 就会出现适配的问题 因为 中文 是 字表义 外文是 音表义 今天就用8 0新特新来解决这个问题 适配前是这样的 在固定的宽高就会出现适配的问题 在之前博客中也