通過設置字號,同步改變全局的字體。
長文干貨,建議點贊收藏。
實現方式有多種:
1.通過AppTheme主題設置
通過配置不同的字體主題,然后設置更換主題來改變全局的字體樣式,主題中可配置自定義字體大小等;xml布局中也需要添加style主題,設置主題后需要recreate ui,體驗不好。
2.修改系統fontScale,縮放字體大小
百度字體設置等,很多方案都是這種,主要重寫Application的onConfigurationChanged監聽系統字體大小變化,然后重啟app或者activity才會刷新同步,而且對數值不可控,還會影響一些默認配置以及存在適配問題,不作深入研究,直接拋棄。
3.自定義view
每個view中會有一個監聽,修改字體后觸發監聽更改字體;每次創建讀取本地緩存,設置字體;需要在顯示的地方更換為自定義的view。
看着略顯麻煩,但是不得不說,很靈活,並且方便擴展需求,但是麻煩,除了自定義view還得處理監聽。
4.自定義binding屬性「最終選擇」
類似於方案3,在binding方法中去初始化跟設置字體,擴展性好,並且不需要替換textview,流程就跟方案1類似,需要在xml中配置類型屬性,然后binding會自動根據配置的屬性去設置字體。
全局字體設置之自定義binding
先看效果圖,在設置頁面,通過設置字體的類型,然后保存下來,同時刷新binding監聽,更改所有顯示的textview字體大小。
並且可以方便擴展,比如tablayout中選中放大等效果。

首先新建字體設置工具類。
用於存儲字體類型,獲取字體樣式。
class FontUtils { companion object { private const val TAG = "FontUtils" private const val KEY_APP_FONT = "key_app_font" //標准字體 const val NORMAL_FONT_STYLE = "normal_text_size" //大號字體 const val BIG_FONT_STYLE = "big_text_size" //特大字體 const val LARGE_FONT_STYLE = "large_text_size" private val instance by lazy { FontUtils() } fun getFontUtils(): FontUtils = instance } /** 字體樣式類型 */ fun getAppFontStyle(): String { return SPUtils.getInstance().getString(KEY_APP_FONT, NORMAL_FONT_STYLE) } fun saveAppFontStyle(appFontStyle: String?) { SPUtils.getInstance().put(KEY_APP_FONT, appFontStyle) } /** 獲取模型 */ fun getFontVo(fontType: String?): FontBean? { val fontStyle: String = getAppFontStyle() val fontVoList: List<FontBean>? = getRawFileList(fontStyle) return if (CollectionUtils.isNotEmpty(fontVoList)) { // LogUtils.i(TAG, "getFontVo-- fontVo:" + GsonUtils.toJson(fontVo)); getFontByType(fontVoList, fontType) } else null } /** * 解析模型 * @param fontType 具體字號類型 * */ private fun getFontByType(fontVoList: List<FontBean>?, fontType: String?): FontBean? { return CollectionUtils.find( fontVoList ) { it != null && StringUtils.equals(it.fontType, fontType) } } /** 字體模型 */ private fun getRawFileList(fontStyle: String?): List<FontBean>? { return when { StringUtils.equals(NORMAL_FONT_STYLE, fontStyle) -> { getFontListByRaw(R.raw.font_normal) } StringUtils.equals(BIG_FONT_STYLE, fontStyle) -> { getFontListByRaw(R.raw.font_big) } StringUtils.equals(LARGE_FONT_STYLE, fontStyle) -> { getFontListByRaw(R.raw.font_large) } else -> getFontListByRaw(R.raw.font_normal) } } /** 讀取本地模型 路徑 /res/raw/resId */ private fun getFontListByRaw(@RawRes resId: Int): List<FontBean>? { return GsonUtils.fromJson<List<FontBean>>( ResourceUtils.readRaw2String(resId), object : TypeToken<List<FontBean>>() {}.type ) } }
下面是字體模型截圖,類似方案1中的字體主題,分別對應設置頁面的標准字體,大號字體,特大字體,可隨意擴展。
xml中設置的字體類型就來自模型中讀取的數值

接下來就是自定義binding屬性,具體設置方法。
@BindingAdapter(value = ["bindFontType"], requireAll = false) fun TextView.setBindingFontStyle(fontType: String?) { if (TextUtils.isEmpty(fontType)) { LogUtils.i("setBindingFontStyle", "IS NULL") return } //去除字體內邊距 includeFontPadding = false val fontVo: FontBean? = FontUtils.getFontUtils().getFontVo(fontType) if (fontVo != null) { textSize = fontVo.fontSize } val activity = ActivityUtils.getActivityByContext(context) as AppCompatActivity? if (activity != null) { LiveEventBus .get(LiveEventBusKey.FONT_STYLE, Int::class.java) .observe(activity, { val fontBean: FontBean? = FontUtils.getFontUtils().getFontVo(fontType) if (fontBean != null) { textSize = fontBean.fontSize } }) } }
自定義binding方法中通過livedata注冊了一個監聽,所以跟方案3類似,實則是每一個textview都存在一個監聽,而livedata可以綁定生命周期,自動創建跟銷毀監聽,避免內存泄漏。
在xml中綁定設置的方法。

當布局創建時,會自動執行binding方法。
binding方法中會根據xml里的字體大小類型執行工具類中的getFontVo方法。
getFontVo方法回去讀取緩存在本地的字體類型,等於主題類型,從而讀取到具體的模型數據,拿到數據設置更新。
而binding方法中的監聽,綁定了當前的生命周期,所以當頁面銷毀或回收時會自動解除監聽。
只要xml中設置了自定義的binding屬性,就能同步修改更新,不影響原本的設置,如絲滑般柔順。
當然,因為是基於binding,所以項目得基於databinding才行。
因為我后面接觸過的項目都是databinding,並且也是主流。
如果不是就推薦方案3了,通過自定義view實現,大致流程也差不多。
碼字不易,喜歡就賞個贊吧。
自定義擴展
因為是binding,所以有時候在無法滿足需求的情況下可以額外擴展方法。
比如tablayout,實現一個選中字體放大的效果。
@BindingAdapter(value = ["bindFontType", "isCheck", "checkBuffSize"], requireAll = false) fun TextView.setBindingFontStyle(fontType: String?, isCheck: Boolean, checkBuffSize: Int) { if (TextUtils.isEmpty(fontType)) { LogUtils.i("setBindingFontStyle", "IS NULL") return } //去除字體內邊距 includeFontPadding = false //選中字體 val fontVo: FontBean? = FontUtils.getFontUtils().getFontVo(fontType) if (fontVo != null) { textSize = if (isCheck) { fontVo.fontSize + checkBuffSize } else { fontVo.fontSize } } val activity = ActivityUtils.getActivityByContext(context) as AppCompatActivity? if (activity != null) { LiveEventBus .get(LiveEventBusKey.FONT_STYLE, Int::class.java) .observe(activity, { val fontBean: FontBean? = FontUtils.getFontUtils().getFontVo(fontType) if (fontBean != null) { textSize = fontBean.fontSize } }) } }
很簡單,擴展了兩個屬性,一個是否選中,一個是增量。
只需要在xml中動態配置一下,然后通過邏輯控制就能同步設置。

這里有時候會碰到一些問題,比如tablayout例子。
如果是自定義tablayout,customview並沒有參與綁定,無法實現監聽的情況。
這里可以手動綁定一下解決,參數可以統一設置在binding方法中,也可以額外設置,不過最好統一管理,便於擴展跟維護。
/** * 配置選中/未選中狀態 */ private fun setTabLayoutSelected(tab: TabLayout.Tab, isCheck: Boolean) { val topicBinding: TabviewTopicBinding? = DataBindingUtil.getBinding(tab.customView!!) if (topicBinding != null) { topicBinding.setIsCheck(isCheck)//通過binding統一控制 if (isCheck) {//也可以隨意 topicBinding.tvTitle.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD)) } else { topicBinding.tvTitle.setTypeface(Typeface.defaultFromStyle(Typeface.NORMAL)) } } }
只要把工具類封裝好了,后續只需要設置binding屬性就行。
在需求跟擴展以及刷新效果來說,這個方案是很不錯的。
