Android 替换字体的方式以及同时替换多种字体

替换字体的方式

1.Android原生的适配方案:按照区域(values-ko values-it-rIT values-zh-rCN) 按照机器的Android版本 按照设备分辨率来加载不同的font

2.利用反射替换安卓原生字体

例子

    <style name="fontMedium">
        <item name="android:fontFamily">monospace</item>
        <item name="android:textStyle">normal</item>
    </style>
    <TextView
        style="@style/fontMedium"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="30sp"
        android:text="ABCDEFGHIGKLIMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" />
    /**
     * 替换字体,其本质是将系统底层的字体变量进行替换自己的字体引用 ,
     * 只会替换控件中没有自定义字体的控件,已自定义的就是使用的是自定义的字体
     *
     * @param context
     * @param oldFontName            支持的名称有 MONOSPACE、SERIF,NORMAL(程序无法运行)、SANS与DEFAULT和DEFAULT_BOLD与SANS_SERIF(可以运行但是显示字体没有修改成功)
     *                              而且需要与 需要与AndroidManifest文件application节点的android:theme引用的styles文件中
     *                              <item name="android:typeface">monospace</item> 的值对应
     * @param newFontNameFromAssets 新的字体路径,必须要放在assets文件夹下,如:fonts/Nsimsun.ttf
     */
    public static void replaceFont(Context context, String oldFontName, String newFontNameFromAssets) {
        Typeface newTypeface = Typeface.createFromAsset(context.getAssets(), newFontNameFromAssets);
        try {
            //android 5.0及以上我们反射修改Typeface.sSystemFontMap变量
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                Map<String, Typeface> newMap = new HashMap<>();
                newMap.put(oldFontName, newTypeface);
                final Field staticField = Typeface.class.getDeclaredField("sSystemFontMap");
                staticField.setAccessible(true);
                staticField.set(null, newMap);
            } else {
                final Field staticField = Typeface.class.getDeclaredField(oldFontName);
                staticField.setAccessible(true);
                staticField.set(null, newTypeface);
            }
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

在activity setContentView之前调用替换字体方法
replaceFont(this,“notosansdisplay_medium”, “fonts/xxx.otf”);//font存放在asset/fonts下,不是res/font下
参考资料:https://www.jianshu.com/p/282716d73c6a
这种方式起作用 但仅限于使用的字体是安卓自带的字体 如果不是安卓自带的字体 则无法替换 例如:

<style name="fontMedium">
    <item name="android:fontFamily">@font/notosansdisplay_medium</item>
    <item name="android:textStyle">normal</item>
</style>

则无法找到自定义font notosansdisplay_medium

3.递归替换 (性能较差 没有研究)

如果有多种字体 需要 多次遍历

Typeface typeface = getResources().getFont(R.font.myfont);
textView.setTypeface(typeface);

public static void applyFont(final Context context, final View root, final String fontName) {
    try {
        if (root instanceof ViewGroup) {
            ViewGroup viewGroup = (ViewGroup) root;
            for (int i = 0; i < viewGroup.getChildCount(); i++)
                applyFont(context, viewGroup.getChildAt(i), fontName);
        } else if (root instanceof TextView)
            ((TextView) root).setTypeface(Typeface.createFromAsset(context.getAssets(), fontName));
    } catch (Exception e) {
        Log.e(TAG, String.format("Error occured when trying to apply %s font for %s view", fontName, root));
        e.printStackTrace();
    }
}

4.使用第三方库替换字体

三方库:
https://github.com/chrisjenx/Calligraphy
如何使用及原理
https://segmentfault.com/a/1190000011604008

一个APP内使用多种字体时替换多种字体

基本原理 使用反射 即上面的方法二

背景:

    <style name="fontLight">
        <item name="android:fontFamily">@font/font_light</item>
        <item name="android:textStyle">normal</item>
    </style>
    <style name="fontMedium">
        <item name="android:fontFamily">@font/font_medium</item>
        <item name="android:textStyle">normal</item>
    </style>
<font-family xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <font
        android:font="@font/notosansdisplay_light"
        android:fontStyle="normal"
        android:fontWeight="200"
        app:font="@font/notosansdisplay_light"
        app:fontStyle="normal"
        app:fontWeight="200" />
</font-family>

<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <font
        android:font="@font/notosansdisplay_medium"
        android:fontStyle="normal"
        android:fontWeight="500"
        app:font="@font/notosansdisplay_medium"
        app:fontStyle="normal"
        app:fontWeight="500" />
</font-family>

需求:

在机型A上运行时使用自定义字体A1 A2
在机型B上运行时使用自定义字体B1 B2

思路:使用上面的第二种反射机制替换字体

遇到两个问题
1.该方法只能替换Android原生的字体
2.如何替换多种字体
解决问题1:
既然只能替换Android原生的字体 那么我们在静态xml中就先使用Android字体,然后利用这些字体与A1 A2 B1 B2形成映射的map 在代码里面动态设置为自定义字体
例如:
在A机型
Android字体font1 对应自定义字体A1
Android字体font2 对应自定义字体A2
在B机型
Android字体font1 对应自定义字体B1
Android字体font2 对应自定义字体B2
解决问题2:
对原先的方法的进行改进
final Field staticField = Typeface.class.getDeclaredField(“sSystemFontMap”);
staticField.set(null, newMap);
接受的实际是个map 之前的方法是一个只有一个元素的map 只要对原先的方法进行改进 传入映射的map即可

最终方案及步骤:

1.添加字体A1 A2 B1 B2 放到assert/fonts下
2.修改style的定义
原先定义:

	<style name="fontLight">
        <item name="android:fontFamily">@font/font_light</item>
        <item name="android:textStyle">normal</item>
    </style>
    <style name="fontMedium">
        <item name="android:fontFamily">@font/font_medium</item>
        <item name="android:textStyle">normal</item>
    </style>

变更为:

    <style name="fontLight">
        <item name="android:fontFamily">sans-serif-light</item>
        <item name="android:textStyle">normal</item>
        <item name="android:fontWeight" tools:targetApi="o">200</item>
    </style>

    <style name="fontMedium">
        <item name="android:fontFamily">sans-serif-medium</item>
        <item name="android:textStyle">normal</item>
        <item name="android:fontWeight" tools:targetApi="o">500</item>
    </style>

这里sans-serif-light sans-serif-medium即对应Android原生的font A和B
3.根据机型使用字体(使用时机在Application的onCreate或者Activit的setContentView之前)

                switch (model) {
                    case A1:
                        //caution:
                        // 1.the key value in map(like sans-serif,sans-serif-medium) should be android font, custom font can't be found by system
                        // 2.if move the font file or change name, this font also need change, or will cause file not found exception
                        oldNewFontMap.put("sans-serif-medium", "fonts/A1.OTF");
                        oldNewFontMap.put("sans-serif-light", "fonts/A2.OTF");
                        break;
                    case B1:
                        oldNewFontMap.put("sans-serif-medium", "fonts/B1.ttf");
                        oldNewFontMap.put("sans-serif-light", "fonts/B2.ttf");
                        break;
                }
                replaceFont(this, oldNewFontMap);

4.对replaceFont进行改进 让其可以同时更改多种字体

    public static void replaceFont(Context context, HashMap<String, String> oldNewFontMapping) {
        try {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                Map<String, Typeface> newMap = new HashMap<>();
                for (Map.Entry<String, String> entry : oldNewFontMapping.entrySet()) {
                    newMap.put(entry.getKey(), Typeface.createFromAsset(context.getAssets(), entry.getValue()));
                }
                final Field staticField = Typeface.class.getDeclaredField("sSystemFontMap");
                staticField.setAccessible(true);
                staticField.set(null, newMap);
            } else {
                for (Map.Entry<String, String> entry : oldNewFontMapping.entrySet()) {
                    final Field staticField = Typeface.class.getDeclaredField(entry.getKey());
                    staticField.setAccessible(true);
                    staticField.set(null, Typeface.createFromAsset(context.getAssets(), entry.getValue()));
                }
            }
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值