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

由于網(wǎng)上大部分教程在新版本系統(tǒng)中已經(jīng)失效,特此記錄。

一、修改TextView字體

假設(shè)現(xiàn)在有一個(gè)字體文件msyh.ttf;對(duì)于某個(gè)TextView來(lái)說(shuō),如果想修改它的字體,可以簡(jiǎn)單的使用如下代碼:

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

這樣就可以將單個(gè)TextView設(shè)置為對(duì)應(yīng)字體。如果想要實(shí)現(xiàn)全局修改字體,則需要通過(guò)修改Factory2的方式來(lái)實(shí)現(xiàn)。

二、Factory2

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

眾所周知,在初始化布局的時(shí)候,會(huì)調(diào)用LayoutInflater.inflate方法,而其會(huì)嘗試使用LayoutInflater.tryCreateView方法來(lái)創(chuàng)建View對(duì)象。該方法如下:

    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;
    }

可以看到,其會(huì)優(yōu)先調(diào)用mFactory2.onCreateView來(lái)創(chuàng)建對(duì)象。所以,如果可以替換LayoutInflater.mFactory2這個(gè)對(duì)象,就可以操縱布局的創(chuàng)建過(guò)程。

那么,mFactory2對(duì)象又是從何而來(lái)的呢?通過(guò)打斷點(diǎn)Debug,可以追蹤到,系統(tǒng)默認(rèn)的Factory2是在onCreate方法中設(shè)置的,而其也就是AppCompatActivity.mDelegate對(duì)象。

三、自定義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)
        }
    }

然后通過(guò)反射將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)用。因?yàn)榭丶膞ml解析為對(duì)應(yīng)類型的對(duì)象是在setContentView中完成的,要在此之前將LayoutInflater.mFactory2替換。

四、補(bǔ)充

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

1. Actionbar title

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

查找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)題沒(méi)有被修改。解決方案如下:

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

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

// 定義擴(kuò)展函數(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)

3. 代碼

修改全局字體函數(shù)的完整代碼:

    /**
     * 通過(guò)反射修改[LayoutInflater.mFactory]和[LayoutInflater.mFactory2]字段設(shè)置全局字體。
     *
     * 這個(gè)函數(shù)需要在[AppCompatActivity.setContentView]之前調(diào)用,否則無(wú)效。
     */
    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)用這個(gè)方法,則可以修改當(dāng)前Activity中所有TextView的字體。

最后

您的點(diǎn)贊收藏就是對(duì)我最大的鼓勵(lì)! 歡迎關(guān)注我,分享Android干貨,交流Android技術(shù)。 對(duì)文章有何見(jiàn)解,或者有何技術(shù)問(wèn)題,歡迎在評(píng)論區(qū)一起留言討論!最后給大家分享一些Android相關(guān)的視頻教程,感興趣的朋友可以去看看。

本文轉(zhuǎn)自 https://juejin.cn/post/7043383979470225415,如有侵權(quán),請(qǐng)聯(lián)系刪除。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容