最近没什么难点技术好写,有难点的技术没研究透也不敢写,我估摸着太久不更新也不太好,得显得我还存在着对技术的热爱。
这不正好上头派了个换肤的功能,那换肤不外乎是那些思路,用框架也行,自己写也不难,主要不想用别人写的,不然出BUG就不好整了。
1. 最基本的思路
那要换肤,不外乎就是if-else,判断某个参数,根据该参数来if-else,但这样写太Low了嘛,每次要加需要换肤的地方,都这样写,那就很难看。
我们可以封装一个类,需要换肤的时候传入原ResId,返回特定皮肤的ResId。
我的思路就是做一个规范,相应渠道的皮肤图片的名称就是原图名_渠道名,这样以后扩展不同的渠道只需要导入相应的资源就行,不用改代码。
比如说原图的名是 home_top_logo.png , 那渠道1的图片名就要用 home_top_logo_channelone.png, 那渠道2的图片名就要用 home_top_logo_channeltwo.png,大概是这么一个意思。
String channelName = 渠道名
public int getChannelRid(Context context, int res){
String resName = context.getResources().getResourceEntryName(res);
String suffix = "";
if (channelName != null){
suffix = "_"+ channelName.toLowerCase();
}else { return res; }
Resources resources = context.getResources();
return resources.getIdentifier(resName+suffix, "drawable", context.getPackageName());
}
这样就能传入ResId,返回对应渠道的ResId
2. 代码要优美
在这个思路的基础上,如果我们做离线换肤,那就需要提前把渠道图片放到包中。如果渠道多的话,就会导致你的drawable文件夹里:
home_top_logo.png
home_top_logo_channelone.png
home_top_logo_channeltwo.png
......
home_top_logo_channelsix.png
当资源多的情况下,我们就会看得很累,这时候我们正常的思路是什么,没错,就是分文件夹
但是res资源里面怎么分文件夹呢,这就需要用到Gradle来实现,如果你还不熟悉Gradle,那一定要去学。我们的思路就是开发时这些资源分文件夹,这样就很方便增删改查,打包时再一起打到包里就行。
这时候就要用到sourceSets了,简单的写
sourceSets{
main{
res.srcDirs = ['src/main/res', 'src/main/reschone', 'src/main/reschtwo']
}
}
3. 插件包实现
按照之前的做法也只能实现离线换肤,如果要实现在线,还得需要用到插件化。插件化是什么,之前也写过简单的文章介绍。(写文章的好处就是忘记了能马上回忆起来)
https://www.jianshu.com/p/d813e3df1cb9
如果我们的这个插件包只是用来放资源,那就挺简单,无需去考虑平常插件化场景中的生命周期这类问题,只需要防止资源冲突就行。
我们把资源放到另外一个项目中,然后打成apk文件放到设备里,再在当前应用去引用apk的资源,这个就是主要的原理。
String resName = context.getResources().getResourceEntryName(res);
String suffix = "";
if (channelName != null){
suffix = "_"+ channelName.toLowerCase();
}
int rid = -1;
try {
if (pluginResources == null) {
AssetManager assetManager = AssetManager.class.newInstance();
AssetManager.class.getDeclaredMethod("addAssetPath", String.class)
.invoke(assetManager, path + suffix + ".apk");
pluginResources = new Resources(assetManager, context.getResources().getDisplayMetrics(),
context.getResources().getConfiguration());
}
rid = pluginResources.getIdentifier(resName+suffix,
"drawable", 插件包名);
}catch (Exception e){
e.printStackTrace();
}
正常我们这样就能获得到插件中资源的ResId了。but我们要获取资源必须要用这个pluginResources,不能用宿主context的Resources,因为不是同个表中的,会找不到。
最后说说解决资源冲突的方法。(这里不扩展去讲,因为这种技巧懂的都懂,不懂的我就说个思路,要自己去操作才能学会)
(1)可以去网上抄代码改AAPT (小心有些人的代码是不生效的)
(2)可以反编译去改再回编译,反正是你自己的包。
但是一般不会冲突
4. 前人栽树后人撞树
那么问题就来了,我这代码是接以前的人写的,他那封装好那个方法就是返回ResId,然后在几层之后的最外层再调用setXXXResource()
如果我们直接返回插件的ResId肯定是莫得了,找不到文件滴。这时候我们就需要一个标识来判断这个ID是宿主的ID还是插件的ID。
正常我们资源文件的ID格式都是固定的 PPTTNNNN格式,但是前面两个一般我们不改都是7f开头。
如果我们做了防冲突的操作,一般会改PP,这样就能根据这两个十六进制位来区分是哪个包里的资源。