美文网首页
动态更换Theme几种方案

动态更换Theme几种方案

作者: 愤怒的国足 | 来源:发表于2019-04-22 19:00 被阅读0次

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

    1. 固定一个或者多个主题,仅更换主题色等,可以直接通过setTheme(Style)的方式去做。这种方案实现起来较为简单,侵入性也很小。缺点是灵活性不够,而且setTheme要在onCreateView之前调用,所以需要重启Activity才能生效,比较流行的解决方案是将当前页面截图显示给用户,之后重启Activity后再切换成真正的Activity。
      因为style中只能有预设的属性,可以通过下面的方式自定义一些属性
    //attrs.xml中自定义参数名
    <resources>
        <attr name="app_background" format="reference"/>
    </resources>
    
    <!-- app theme中赋值 -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
       ....
       <item name="app_background">@drawable/ic_launcher_background</item>
    </style>
    
    <!--布局文件中引用-->
    <TextView
       ....
       android:background="?attr/app_background"/>
    
    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<String, String>, 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几种方案

          本文链接:https://www.haomeiwen.com/subject/kkdugqtx.html