概述
Android 换肤方案详解(一)主要围绕Android-Skin-Loader讲解了换肤的原理。接下来这篇文章主要分享换肤过程中遇到的问题和心得。
可能会遇到的问题及解决方案
实践中发现该方案仍存在一些局限
- 只支持xml静态注册view
- 每增加一个换肤属性需要实现一个类
- 动态添加view?
因为以上方案是在onCreate()创建view时自定义了LayoutInflater.Factory,所以我们可以获取onCreate()中创建的view和attr集,但代码中通过addView()方式添加的view没办法保存进去
设计思路:重写viewGroup的addView()方法,在addView()中将childView注册到换肤集中。为了方便使用流程,又封装了view.setSkinTag(属性1,属性2,…)方法,该方法内部根据view和attr构建换肤实体、调用childview.setTag(tagId, 换肤实体),重写的addView()只需根据childview.getTag()便能将换肤实体注册到换肤集
封装过程:略
使用流程:addView()前调用setSkinTag()- 注册背景、文本、文字颜色、图片资源的换肤
// 注册背景 view.setSkinTag(bgResId = R.drawable.shape_tab_launcher_g_focused_bg)
- 注册自定义属性的换肤
// 注册自定义属性 view.setSkinTag(TabViewProcess.textColor, R.color.selector_tab_text_color_g, SkinAttr.RES_TYPE_NAME_COLOR)
object TabViewProcess: CustomAttrAction<TabView> { val textColor = CustomAttrEntity(TabView::class.java.name, attrName = "textColor", this) override fun apply(t: TabView, attrName: String, resId: Int) { if (attrName == "textColor" && resId != 0) { t.initColorStateList(resId) SkinUtil.getColorStateList(resId)?.let { t.setTextColor(it) } } } }
- 代码中有修改属性值,例如view获取焦点时改变背景?
设计思路:封装一个方法转换皮肤资源,在修改属性值时调用该方法
封装过程:略
使用流程:view.setImageDrawable(SkinUtil.getDrawable(resID))、… - 代码中修改过属性值,一键换肤时会将注册时的属性值(xml中的属性值或代码动态添加view的属性值)设置给view?
设计思路:SkinUtil.getDrawable(resID)只是动态修改属性,并未修改换肤集该view的值,故可以封装一行代码修改换肤集该view的属性值 - 自定义view中自定义的attr怎么换肤?
每增加一个属性需要实现一个类,对于常规控件还好,但自定义view属性值一多,除了修改lib代码外(耦合),还需实现很多类(繁杂),不便管理
设计思路:通过注册方式自行管理view下所有属性的换肤功能,且不影响view的代码(解耦)
封装过程:略
使用流程:重写registerCustomAttr()// 对于自定义view---CircleTextImage,换肤需要改变三个属性,只需三行代码注册 override fun registerCustomAttr() { add(CircleTextImageProcess.textColor) add(CircleTextImageProcess.circleColor) add(CircleTextImageProcess.strokeColor) }
// 对于自定义view---CircleTextImage,换肤所有事项自行处理 object CircleTextImageProcess: CustomAttrAction<CircleTextImage> { val textColor = CustomAttrEntity(CircleTextImage::class.java.name, attrName = "textColor", this) val circleColor = CustomAttrEntity(CircleTextImage::class.java.name, attrName = "circleColor", this) val strokeColor = CustomAttrEntity(CircleTextImage::class.java.name, attrName = "strokeColor", this) override fun apply(t: CircleTextImage, attrName: String, resId: Int) { if (attrName == "textColor" && resId != 0) { t.setTextColor(SkinUtil.getColor(resId)) } else if (attrName == "circleColor" && resId != 0) { t.setCircleColor(SkinUtil.getColor(resId)) } else if (attrName == "strokeColor" && resId != 0) { t.setStrokeColor(SkinUtil.getColor(resId)) } } }
优化项
资源包瘦身
因为资源包实际上就是一个应用,所以可以参考应用瘦身方案,此外可根据实际继续瘦身
资源包只需要拥有res下换肤R文件即可,不一定需要跑起来,所以应删尽删
参考我之前的文章-Android应用瘦身
实践:前15M+,后207K
- 开启混淆、资源压缩(普通应用瘦身手段)
不开混淆可能会导入android自带的一些库,例如下图classes.dex、classes2.dex、classes3.dex、classes4.dex
- AndroidManifest.xml尽量简洁(皮肤包瘦身手段)
去除icon、banner、lable、theme这些信息,同时清除icon、banner、lable、theme引用的res文件。尤其是theme,占用空间挺大的,只保留以下几行即可<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.xxx.xxx"> <application android:allowBackup="true" android:supportsRtl="true" /> </manifest>
- 将dependencies节点下所有引用删除(皮肤包瘦身手段)
// 下面代码都可以删除 dependencies { implementation 'androidx.core:core-ktx:1.7.0' implementation 'androidx.appcompat:appcompat:1.4.1' implementation 'com.google.android.material:material:1.5.0' testImplementation 'junit:junit:4.+' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' }
资源规范
- 整理资源时,某view引用R.drawable.selector_xxx
- R.drawable.selector_xxx中引用了R.drawable.xxx,需修改R.drawable.xxx
- R.drawable.selector_xxx中引用了R.color.xxx,需修改R. color.xxx
总之修改最后一层引用的资源文件即可
- 多个view引用同一个R文件(例如R.color.xxx),需要实现Aview换肤,Bview无变化,这时需要增加R文件以示区分(例如R.color.xxx_skin)
- 删除无关换肤的R文件