动态更换Theme几种方案

动态更换theme需求多种多样,解决方案也多种多样。目前我了解的有如下三种:

  1. 固定一个或者多个主题,仅更换主题色等,可以直接通过setTheme(Style)的方式去做。这种方案实现起来较为简单,侵入性也很小。缺点是灵活性不够,而且setTheme要在onCreateView之前调用,所以需要重启Activity才能生效,比较流行的解决方案是将当前页面截图显示给用户,之后重启Activity后再切换成真正的Activity。
    因为style中只能有预设的属性,可以通过下面的方式自定义一些属性
//attrs.xml中自定义参数名

    







  1. 比上面更灵活一些,比如可以后台新配置一些简单主题或者仅更换一小部分内容,可以通过自定义控件或者json串的方式自己做一些处理,下面是我写的一个demo的解决方案。
    通过如下这种json串的方式去动态更换theme,主要思路是通过LayoutInflater.Factory2接口hook所有view的初始化,通过自定义的一个属性判断是否需要支持动态theme,之后解析json拿到最终需要的资源ID赋值即可。
{
    "textColor":{
        "colorPrimary":"colorAccent"
    },
 
    "buttonDrawable":{
        "button_background_default":"button_background_theme_1"
    },
 
    “root”:{
        "background_light":"background_dark"
    }
}
//LayoutInflater.Factory2接口
override fun onCreateView(parent: View?, name: String?, context: Context?, attrs: AttributeSet?): View? {
        // if this is NOT enable to be skined , simplly skip it
        val isSkinEnable = attrs?.getAttributeBooleanValue(SkinConfig.NAMESPACE, SkinConfig.ATTR_SKIN_ENABLE, false) ?: false
 
        if (isSkinEnable) {
            val view = createView(context, name, attrs)
 
            val attrsMap = copyAttributeSet(attrs)
            SkinManager.applyTheme(name, view, attrsMap)
            SkinManager.addDynamicView(name, view, pageName, attrsMap)
 
            return view
        }
 
        return null
    }
//通过json串拿到替换后的资源ID
private fun handlerDynamicAttr(attrsMap: HashMap, attributeName: String, jsonKey: String, resourceTypeName: String): Int {
    val originValue: String = attrsMap[attributeName] ?: ""
 
    //only handler reference value now
    if (originValue.contains("@")) {
        val originReferName = getResourceNameById(getIdByAttributeValue(originValue))
 
        //the value in dynamic json
        val dynamicReferName = try {
            mDynamicTheme?.getJSONObject(jsonKey)?.getString(originReferName)
        } catch (e: JSONException) {
            ""
        }
 
        //getId by reference name
        if (!dynamicReferName.isNullOrEmpty()) {
            return getIdByName(dynamicReferName, resourceTypeName)
        }
    }
 
    return getIdByAttributeValue(originValue)
}
  1. 类似网易云音乐等APP可以从主题商店下载主题,样式多种多样,图片背景等也需要remote加载,这种基本都是通过下载资源APK去实现的。Github有不少优秀的换肤框架可以比较方便的接入。
    基本实现原理:构建一个只包含资源的APK,需要替换的资源命名为相同的名字,之后通过反射调用AssetManager的addAssetPath构建新的assetManager,进而得到新的Resource对象。如何实时换肤以及哪些控件需要换肤,则可以参考2中的实现方式。
    这种方案灵活性很高,不需要预先知道哪些控件需要支持动态theme,但是需要反射调用系统方法,不知道兼容性上是否会有问题。
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager, skinPkgPath);

你可能感兴趣的:(动态更换Theme几种方案)