Android -全自動將APP的字體替換系統(tǒng)包含的任意字體


最近公司設計部門提出要將應用的字體全部替換成思源黑體,當時我想到Android 5.0之后不是系統(tǒng)提供有思源黑體嗎,這個應該非常簡單實現(xiàn)。 但卻在真正實現(xiàn)的時候發(fā)現(xiàn)了一些障礙:

  • 1:由于我們的應用已經(jīng)迭代很多版本,也相對穩(wěn)定了。項目中的TextView就非常多。但Android API并沒有提供一個合適的API可以在全局的style或者theme一鍵替換成指定的字體。 目前發(fā)現(xiàn)只有通過TextView.setTypeface()去單個實現(xiàn)

  • 2:手機系統(tǒng)里的字體存放在哪個位置,?我怎么樣才能加載到它們。(ps:百度,google一下,里面全是教你如何替換整個系統(tǒng)字體,可能我查找的方式不對,有好的文章可以討論一下)。如果是將字體直接放在程序內(nèi)部那將大大提升APK的大小,據(jù)我查看NotoSansHans這幾個自重隨便一個都有8MB左右.

  • 3: 國內(nèi)手機廠家非常多,各廠家的資源庫根本不統(tǒng)一。我們應該采用哪種方式動態(tài)全局加載統(tǒng)一字體呢,?

首先看到第一個問題:本來以為是配置一下什么的就可以了,但發(fā)現(xiàn)卻是個體力活。~ 難道我要把項目里面的所有TextView,Button之類的控件全部拿出來設置一個setTypeface()嗎?這個我堅決不干。當時第一個反應想到就是在view tree中能不能想點招。
如果不太懂這一塊的朋友可以看下activity 中view的創(chuàng)建過程。
參考 Android應用程序窗口(Activity)的視圖對象(View)的創(chuàng)建過程分析

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

該項目是在的核心部分就是重寫的Context.LayoutInflater, 由于細節(jié)很多在這里就不重點講解框架細節(jié)了,我會通過之后的文章在細致的分析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中實現(xiàn)


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


就這樣可以達到全局替換,有沒有很方便,很簡單,?
但問題來了, 我們有沒有發(fā)現(xiàn)在setDefaultFontPath() 里面的的路徑他居然是fonts/***,?這明顯不是系統(tǒng)路徑呀? 怎么辦?那解決問題最好的辦法看看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);
        }

    }

我們發(fā)現(xiàn),原來fonts/**只是一個相對路徑,根目錄而是我們項目中的assets目錄。如果是這樣,我們必須將第三方字體移植到項目中來,那這樣就遇到了我們第二個問題。系統(tǒng)字體在什么位置,?字體文件這么大。我如果加載到項目的assets目錄中來,apk肯定大到不行。就是我愿意,相信領導應該也不愿意吧!

系統(tǒng)字體的位置很好找到,就在/system/fonts/下,但字體這么大,我一定需要把他copy到項目中來嗎,?
下面附圖:

font icon
font icon

可以看到,NotoSansHans(思源黑體,漢文)之類的字體真的這么大。。怎么破?隨便一個都8MB左右,這一套子重copy進來那得40多MB。
明顯,如果為了個字體就讓APK文件莫名多出幾十MB,這不現(xiàn)實。

這個時候唯一的辦法就是修改框架。怎么修改呢,?

剛才我們看到如果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); 來創(chuàng)建Typeface, 難道就不能使用別的API來創(chuàng)建Typeface了嗎,?


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

哎喲,還不錯哦。~ 居然它有這么多種方式來創(chuàng)建Typeface,不明白為什么作者為啥不在這個地方做個兼容呢?讓開發(fā)者有更隨信的設置路徑呢,? 不吐槽了,~咱們試著改造改造。

在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版權(quán)問題,我也不方便將我完全修改的jar包附上。 因為我是在他原基礎上修改框架了。我會在分析源碼的文章中講解如何做好兼容的方式。

就這樣。我們可以任意的加載任意可以訪問到的目錄字體了。有木有很簡單!

到最后一個問題,由于國內(nèi)手機廠商內(nèi)部資源沒有一個統(tǒng)一標準甚至在有些機型在5.0之后并沒有NotoSansHans的字體,既然我們已經(jīng)實現(xiàn)了可以加載任意目錄下字體,這個時候我想到的是利用服務器下發(fā)一套字體文件供我們使用。目前缺點是,在第一次加載程序的時候不會生成自定義字體。只有在第二次加載時才能Load到自定義字體。 這里希望大家給予一些不同的意見,希望一同分享。

一起看下運行效果:


icon
icon
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內(nèi)容