Android -全自动将APP的字体替换系统包含的任意字体


最近公司设计部门提出要将应用的字体全部替换成思源黑体,当时我想到Android 5.0之后不是系统提供有思源黑体吗,这个应该非常简单实现。 但却在真正实现的时候发现了一些障碍:

  • 1:由于我们的应用已经迭代很多版本,也相对稳定了。项目中的TextView就非常多。但Android API并没有提供一个合适的API可以在全局的style或者theme一键替换成指定的字体。 目前发现只有通过TextView.setTypeface()去单个实现

  • 2:手机系统里的字体存放在哪个位置,?我怎么样才能加载到它们。(ps:百度,google一下,里面全是教你如何替换整个系统字体,可能我查找的方式不对,有好的文章可以讨论一下)。如果是将字体直接放在程序内部那将大大提升APK的大小,据我查看NotoSansHans这几个自重随便一个都有8MB左右.

  • 3: 国内手机厂家非常多,各厂家的资源库根本不统一。我们应该采用哪种方式动态全局加载统一字体呢,?

首先看到第一个问题:本来以为是配置一下什么的就可以了,但发现却是个体力活。~ 难道我要把项目里面的所有TextView,Button之类的控件全部拿出来设置一个setTypeface()吗?这个我坚决不干。当时第一个反应想到就是在view tree中能不能想点招。
如果不太懂这一块的朋友可以看下activity 中view的创建过程。
参考 Android应用程序窗口(Activity)的视图对象(View)的创建过程分析

皇天不负有心人,我在github上找到了一个老外的框架:Calligraphy.

该项目是在的核心部分就是重写的Context.LayoutInflater, 由于细节很多在这里就不重点讲解框架细节了,我会通过之后的文章在细致的分析Calligraphy的源码部分。

我们附上Calligraphy框架API核心代码:

    
    public class CalligraphyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        CalligraphyConfig.initDefault(new CalligraphyConfig.Builder()
                .setDefaultFontPath("fonts/Roboto-ThinItalic.ttf")//指定字体
                .setFontAttrId(R.attr.fontPath)
                .build()
        );
    }
}

然后在需要在Activity中或者BaseActivity中实现


    @Override
    protected void attachBaseContext(Context newBase) {
        super.attachBaseContext(CalligraphyContextWrapper.wrap(newBase));
    }


就这样可以达到全局替换,有没有很方便,很简单,?
但问题来了, 我们有没有发现在setDefaultFontPath() 里面的的路径他居然是fonts/***,?这明显不是系统路径呀? 怎么办?那解决问题最好的办法看看Calligraphy的源码了。

终于,~~ 我在Calligraphy框架中的TypefaceUtils找到关键代码:


    /**
     * A helper loading a custom font.
     *
     * @param assetManager App's asset manager.
     * @param filePath     The path of the file.
     * @return Return {@link android.graphics.Typeface} or null if the path is invalid.
     */
    public static Typeface load(final AssetManager assetManager, final String filePath) {
        synchronized (sCachedFonts) {
            try {
                if (!sCachedFonts.containsKey(filePath)) {
                    //这一行就是关键
                    final Typeface typeface = Typeface.createFromAsset(assetManager, filePath);
                    sCachedFonts.put(filePath, typeface);
                    return typeface;
                }
            } catch (Exception e) {
                Log.w("Calligraphy", "Can't create asset from " + filePath + ". Make sure you have passed in the correct path and file name.", e);
                sCachedFonts.put(filePath, null);
                return null;
            }
            return sCachedFonts.get(filePath);
        }

    }

我们发现,原来fonts/**只是一个相对路径,根目录而是我们项目中的assets目录。如果是这样,我们必须将第三方字体移植到项目中来,那这样就遇到了我们第二个问题。系统字体在什么位置,?字体文件这么大。我如果加载到项目的assets目录中来,apk肯定大到不行。就是我愿意,相信领导应该也不愿意吧!

系统字体的位置很好找到,就在/system/fonts/下,但字体这么大,我一定需要把他copy到项目中来吗,?
下面附图:

Android -全自动将APP的字体替换系统包含的任意字体_第1张图片
font icon

可以看到,NotoSansHans(思源黑体,汉文)之类的字体真的这么大。。怎么破?随便一个都8MB左右,这一套子重copy进来那得40多MB。
明显,如果为了个字体就让APK文件莫名多出几十MB,这不现实。

这个时候唯一的办法就是修改框架。怎么修改呢,?

刚才我们看到如果TypefaceUtils的load方法,其中关键的一句:


    public static Typeface load(final AssetManager assetManager, final String filePath) {
        synchronized (sCachedFonts) {
            try {
                if (!sCachedFonts.containsKey(filePath)) {
                    //这一行就是关键
                    final Typeface typeface = Typeface.createFromAsset(assetManager, filePath);
                    sCachedFonts.put(filePath, typeface);
                    return typeface;
                }
            } catch (Exception e) {
                Log.w("Calligraphy", "Can't create asset from " + filePath + ". Make sure you have passed in the correct path and file name.", e);
                sCachedFonts.put(filePath, null);
                return null;
            }
            return sCachedFonts.get(filePath);
        }

    }

我们看到Typeface.createFromAsset(assetManager, filePath); 来创建Typeface, 难道就不能使用别的API来创建Typeface了吗,?


    Typeface : 
        createFromAsset(AssetManager mgr, String path)
        createFromFile(File path)
        createFromFile(String path) 
        createFromFamilies(FontFamily[] families)
        createFromFamiliesWithDefault(FontFamily[] families)
        

哎哟,还不错哦。~ 居然它有这么多种方式来创建Typeface,不明白为什么作者为啥不在这个地方做个兼容呢?让开发者有更随信的设置路径呢,? 不吐槽了,~咱们试着改造改造。

在demo版本中,我们直接将 :


public static Typeface load(final AssetManager assetManager, final String filePath) {
        synchronized (sCachedFonts) {
            try {
                if (!sCachedFonts.containsKey(filePath)) {
                    //这一行就是关键
                    final Typeface typeface = Typeface.createFromAsset(assetManager, filePath);
                    sCachedFonts.put(filePath, typeface);
                    return typeface;
                }
            } catch (Exception e) {
                Log.w("Calligraphy", "Can't create asset from " + filePath + ". Make sure you have passed in the correct path and file name.", e);
                sCachedFonts.put(filePath, null);
                return null;
            }
            return sCachedFonts.get(filePath);
        }

    }

修改成:


public static Typeface load(final AssetManager assetManager, final String filePath) {
        synchronized (sCachedFonts) {
            try {
                if (!sCachedFonts.containsKey(filePath)) {
                    //修改完成以后
                    final Typeface typeface = Typeface.createFromFile(filePath);
                    sCachedFonts.put(filePath, typeface);
                    return typeface;
                }
            } catch (Exception e) {
                Log.w("Calligraphy", "Can't create asset from " + filePath + ". Make sure you have passed in the correct path and file name.", e);
                sCachedFonts.put(filePath, null);
                return null;
            }
            return sCachedFonts.get(filePath);
        }

    }

当然,这样是简单暴力的,本篇文章只提供一个一个解决思路。由于Calligraphy版权问题,我也不方便将我完全修改的jar包附上。 因为我是在他原基础上修改框架了。我会在分析源码的文章中讲解如何做好兼容的方式。

就这样。我们可以任意的加载任意可以访问到的目录字体了。有木有很简单!

到最后一个问题,由于国内手机厂商内部资源没有一个统一标准甚至在有些机型在5.0之后并没有NotoSansHans的字体,既然我们已经实现了可以加载任意目录下字体,这个时候我想到的是利用服务器下发一套字体文件供我们使用。目前缺点是,在第一次加载程序的时候不会生成自定义字体。只有在第二次加载时才能Load到自定义字体。 这里希望大家给予一些不同的意见,希望一同分享。

一起看下运行效果:


Android -全自动将APP的字体替换系统包含的任意字体_第2张图片
icon

你可能感兴趣的:(Android -全自动将APP的字体替换系统包含的任意字体)