由于網(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
對象。
三唱星、自定義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)