Android

全局字体设置 || 老年模式

2021-08-25  本文已影响0人  猿规

通过设置字号,同步改变全局的字体。
长文干货,建议点赞收藏。

实现方式有多种:

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属性就行。
在需求跟扩展以及刷新效果来说,这个方案是很不错的。

上一篇 下一篇

猜你喜欢

热点阅读