一. 简介
换肤功能,是很多公司项目中的重点功能,仅仅会用那是远远不够的,需要对换肤有全面整体的把握,了解底层实现原理,才能在后面的开发中举一反三,事半功倍。
1. 基本原理
对于Android项目来说,皮肤是什么,皮肤就是UI界面,换皮肤无非就是字体颜色、背景图等这些用户看得见的界面。所以换皮肤最为重要的就是换Android工程下res下面的资源文件,也即如下图所在的资源:
Google在Android10(API 29)就已经开始支持深色模式,自定义适配方案是使用资源限定符,就像横向布局适配是添加layout-land资源,高密度资源适配是添加drawable-hdpi资源,其自定义深色模式的适配方案则是在res-night下定义一套资源
在该深色模式资源文件下,所用资源命名和正常资源相同,例如相同的drawable/color/style,那么当系统切换为深色模式时,系统会自动识别并使用res-night下面的资源文件,从而切换为我们想要的深色效果。
换肤功能就类似Google的深色模式,要实现各种换肤功能我们只需要替换对应的资源文件即可,让view布局重新加载新的资源文件。
2. LayoutInflater
首先我们通过上一篇文章了解下 LayoutInflater Factory,通过关于Factory的介绍,我们得出结论:自定义Factory,然后通过setFactory方法设置给系统,那么在系统创建View时则可以进行自定义样式的干预。接下来我们来看看本文研究框架的核心实现原理。
二. 框架Android-Skin-Loader解析
框架 Android-Skin-Loader,官方的版本太旧了,经过改造适配了最新的AndroidX控件以及能正常生成皮肤包,下载地址 Android-Skin-Loader,其工程结构如图
工程中android-skin-loader-sample是一个使用例子,android-skin-loader-skin是一套皮肤包,android-skin-loader-lib为支持换肤的library,下面我们就来一一介绍了。
1. android-skin-loader-sample使用教程
1.1 xml布局配置
在需要换肤的组件上配置skin:enable=“true”
<Button
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="@drawable/news_item_selector"
android:textColor="@color/color_sel_skin_btn_text"
skin:enable="true" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/news_item_text_color_selector"
android:textSize="20sp"
skin:enable="true" />
1.2 Application初始化
public class SkinApplication extends Application {
public void onCreate() {
super.onCreate();
initSkinLoader();
}
private void initSkinLoader() {
SkinManager.getInstance().init(this);
SkinManager.getInstance().load();
}
}
1.3 Activity继承
Activity继承android-skin-loader-lib中的Base组件
public class MyActivity extends BaseActivity{
}
2. android-skin-loader-skin
此module为皮肤包组件,里面是没有任何代码的皮肤资源,用于替换主工程中的资源文件,文件目录如下:
该目录下的资源文件命名需和替换的资源名称保持一样,则才能通过相同的资源名称查到皮肤资源进行替换。
该module为application,可编译出来apk作为皮肤包,可修改后缀名为.skin文件,作为皮肤包放到主工程目录下或者进行网络下载加载。
3. android-skin-loader-lib
换肤的核心逻辑,我们分为三步走:
第一步:加载换肤包到内存中;
第二步:收集所有换肤的View;
第三步:用换肤包中的资源替换View的原有资源。
3.1 SkinManager:加载换肤包
换肤library中最为重要的类是SkinManager,是一个皮肤管理核心类,控制着换肤最为核心的逻辑。在SkinManager类中mResources为引用着换肤包资源的对象,需要换肤的时候从该资源中获取数据,那么该mResources怎么获取到的呢?
Application初始化的时候调用了SkinManager.getInstance().load(),跟进源码
/**
* Load resources from apk in asyc task
* @param skinPackagePath path of skin apk
* @param callback callback to notify user
*/
@SuppressLint("StaticFieldLeak")
public void load(String skinPackagePath, final ILoaderListener callback) {
new AsyncTask<String, Void, Resources>() {
@Override
protected Resources doInBackground(String... params) {
try {
if (params.length == 1)