由于網(wǎ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
替換。
四、完整代碼
/**
* 通過(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的字體。
五、補(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)