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到項目中來嗎,?
下面附圖:

font icon
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到自定義字體。 這里希望大家給予一些不同的意見,希望一同分享。

一起看下運行效果:


icon
icon
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,622評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,716評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,746評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,991評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,706評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,036評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,029評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,203評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,725評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,451評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,677評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,161評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,857評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,266評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,606評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,407評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,643評論 2 380

推薦閱讀更多精彩內容