全局字体设置 || 老年模式
通过设置字号,同步改变全局的字体。
长文干货,建议点赞收藏。
实现方式有多种:
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中绑定设置的方法。
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="登录/注册"
tools:textSize="@dimen/sp_18"
app:bindFontType="@{@string/font_translation_title}" />
当布局创建时,会自动执行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属性就行。
在需求跟扩展以及刷新效果来说,这个方案是很不错的。