【Android】全局自定義字體的實現(xiàn)

由于網(wǎng)上大部分教程新版本已經(jīng)失效媳维,特此記錄济锄。

一绒净、修改TextView字體

假設(shè)現(xiàn)在有一個字體文件msyh.ttf;對于某個TextView來說递览,如果想修改它的字體叼屠,可以簡單的使用如下代碼:

val tv = findView()
val tf = Typeface.createFromAsset(assets, "msyh.ttf")
tv.typeface = tf 

這樣就可以將單個TextView設(shè)置為對應(yīng)字體。如果想要實現(xiàn)全局修改字體绞铃,則需要通過修改Factory2的方式來實現(xiàn)镜雨。

二、Factory2

Factory2用于根據(jù)xml標(biāo)簽創(chuàng)建實例對象的過程。

眾所周知荚坞,在初始化布局的時候挑宠,會調(diào)用LayoutInflater.inflate方法,而其會嘗試使用LayoutInflater.tryCreateView方法來創(chuàng)建View對象颓影。該方法如下:

    public final View tryCreateView(@Nullable View parent, @NonNull String name,
        @NonNull Context context, @NonNull AttributeSet attrs) {
        // ……
        View view;
        if (mFactory2 != null) {
            view = mFactory2.onCreateView(parent, name, context, attrs);
        } else if (mFactory != null) {
            view = mFactory.onCreateView(name, context, attrs);
        } else {
            view = null;
        }
        // ……
        return view;
    }

可以看到各淀,其會優(yōu)先調(diào)用mFactory2.onCreateView來創(chuàng)建對象。所以诡挂,如果可以替換LayoutInflater.mFactory2這個對象碎浇,就可以操縱布局的創(chuàng)建過程。

那么璃俗,mFactory2對象又是從何而來的呢奴璃?通過打斷點Debug,可以追蹤到城豁,系統(tǒng)默認(rèn)的Factory2是在onCreate方法中設(shè)置的苟穆,而其也就是AppCompatActivity.mDelegate對象。

默認(rèn)Factory2

三唱星、自定義Factory2

由于已經(jīng)知道系統(tǒng)默認(rèn)的Factory2就是AppCompatActivity.mDelegate雳旅,則可自定義Factory2如下:

    val myFactory2 = object : LayoutInflater.Factory2 {
        override fun onCreateView(parent: View?, name: String, context: Context, attrs: AttributeSet): View? {
            val delegate = activity.delegate
            val view = delegate.createView(parent, name, context, attrs)
            if (view is TextView) {
                view.typeface = typeface
            }
            return view
        }
    
        override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? {
            return onCreateView(null, name, context, attrs)
        }
    }

然后通過反射將LayoutInflater.mFactory2替換即可。

    val inflater = LayoutInflater.from(activity)
    try {
        val clazz = LayoutInflater::class.java
        val factoryField = clazz.getDeclaredField("mFactory")
        val factory2Field = clazz.getDeclaredField("mFactory2")
        factoryField.isAccessible = true
        factory2Field.isAccessible = true
        factoryField.set(inflater, myFactory2)
        factory2Field.set(inflater, myFactory2)
    } catch (e: Exception) {
        e.printStackTrace()
    }

需要特別注意的是间聊,以上的代碼需要在Activity.setContentView方法之前調(diào)用攒盈。因為控件從xml解析為對應(yīng)類型的對象是在setContentView中完成的,要在此之前將LayoutInflater.mFactory2替換哎榴。

四沦童、完整代碼

    /**
     * 通過反射修改[LayoutInflater.mFactory]和[LayoutInflater.mFactory2]字段設(shè)置全局字體。
     *
     * 這個函數(shù)需要在[AppCompatActivity.setContentView]之前調(diào)用叹话,否則無效。
     */
    fun setGlobalTypefaceInner(activity: AppCompatActivity, typeface: Typeface) {
        val myFactory2 = object : LayoutInflater.Factory2 {
            override fun onCreateView(parent: View?, name: String, context: Context, attrs: AttributeSet): View? {
                val delegate = activity.delegate
                val view = delegate.createView(parent, name, context, attrs)
                if (view is TextView) {
                    view.typeface = typeface
                }
                return view
            }

            override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? {
                return onCreateView(null, name, context, attrs)
            }
        }
        val inflater = LayoutInflater.from(activity)
        try {
            val clazz = LayoutInflater::class.java
            val factoryField = clazz.getDeclaredField("mFactory")
            val factory2Field = clazz.getDeclaredField("mFactory2")
            factoryField.isAccessible = true
            factory2Field.isAccessible = true
            factoryField.set(inflater, myFactory2)
            factory2Field.set(inflater, myFactory2)
        } catch (e: Exception) {
            e.printStackTrace()
        }

    }

在Activity的onCreate中墩瞳,setContentView之前調(diào)用這個方法驼壶,則可以修改當(dāng)前Activity的字體。

五喉酌、補充

通過以上的操作热凹,已經(jīng)完成了大多數(shù)TextView字體的修改。還有一些需要手動修改的補充如下泪电。

1. Actionbar title

Actionbar的標(biāo)題無法通過上述方式修改字體般妙,解決方案如下:

查找id為R.id.action_bar的控件,該控件為當(dāng)前Actionbar相速。再遍歷其子控件碟渺,修改其中類型為TextView的字體。

    // 在Activity中調(diào)用
    private fun setTitleTypeface() {
        val actionBarId = R.id.action_bar
        val actionbar = findViewById<ViewGroup>(actionBarId)
        for (view in actionbar.children) {
            if (view is TextView) {
                // titleView
                view.typeface = Typefaces.getCurrent(this)
            }
        }
    }

2. Alertdialog的標(biāo)題

Alertdialog的內(nèi)容修改字體正常突诬,但是標(biāo)題沒有被修改苫拍。解決方案如下:

查找Alertdialog中id為R.id.alertTitle的子控件芜繁,這個控件就是標(biāo)題TextView;然后對其設(shè)置字體绒极。

可以增加擴展函數(shù)fun AlertDialog.Builder.show(typeface: Typeface)骏令,使用該函數(shù)顯示Dialog。

// 定義擴展函數(shù)
fun AlertDialog.Builder.show(typeface: Typeface): AlertDialog {
    val dlg = show()
    val title = dlg.findViewById<TextView>(R.id.alertTitle)
    title?.typeface = typeface
    return dlg
}

使用方式:

    val myTypeface = getTypeface()
    AlertDialog.Builder(this)
        .setTitle("標(biāo)題")
        .setMessage("內(nèi)容")
        .setPositiveButton("確定") { _, _ ->
            // do sth
        }
        .setNegativeButton("取消", null)
        .show(myTypeface)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末垄提,一起剝皮案震驚了整個濱河市榔袋,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌铡俐,老刑警劉巖凰兑,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異高蜂,居然都是意外死亡聪黎,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進(jìn)店門备恤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來稿饰,“玉大人,你說我怎么就攤上這事露泊『砹” “怎么了?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵惭笑,是天一觀的道長侣姆。 經(jīng)常有香客問我,道長沉噩,這世上最難降的妖魔是什么捺宗? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮川蒙,結(jié)果婚禮上蚜厉,老公的妹妹穿的比我還像新娘。我一直安慰自己畜眨,他們只是感情好昼牛,可當(dāng)我...
    茶點故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著康聂,像睡著了一般贰健。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上恬汁,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天伶椿,我揣著相機與錄音,去河邊找鬼。 笑死悬垃,一個胖子當(dāng)著我的面吹牛游昼,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播尝蠕,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼烘豌,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了看彼?” 一聲冷哼從身側(cè)響起廊佩,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎靖榕,沒想到半個月后标锄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡茁计,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年料皇,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片星压。...
    茶點故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡践剂,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出娜膘,到底是詐尸還是另有隱情逊脯,我是刑警寧澤,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布竣贪,位于F島的核電站军洼,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏演怎。R本人自食惡果不足惜匕争,卻給世界環(huán)境...
    茶點故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望爷耀。 院中可真熱鬧汗捡,春花似錦、人聲如沸畏纲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽盗胀。三九已至,卻和暖如春锄贼,著一層夾襖步出監(jiān)牢的瞬間票灰,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留屑迂,地道東北人浸策。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像惹盼,于是被迫代替她去往敵國和親庸汗。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,446評論 2 348

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