Android 动态换肤框架,支持动态加载插件资源,无需重启Activity,拓展能力很强
Pixel 4 XL Android 13 实机测试
95b973c2ead665e65a1e17461d55e276.mp4
将需要换肤页面的Activity继承自SkinBaseActivity
public class MainActivity extends SkinBaseActivity {
//....
} SkinManager.INSTANCE.loadSkinFile(Environment.getExternalStorageDirectory() + "/skin.apk");对于Assets目录,使用
SkinManager.INSTANCE.loadSkinAssets("skin.apk");加载Assts文件,会copy一份到sdcard下,具体路径为:
/sdcard/Android/data/${packageName}/cache/skinCache/**.apk
更推荐使用SkinManager.INSTANCE.loadSkinFile
使用app:skinEnable="true or false" 来决定View是否开启换肤。
优先级大于setSkinGlobalEnable,默认全局开启
框架默认支持background、src、textColor、text常用资源切换,若需要增加功能
使用addSkinAttrHolder方法添加需要的属性
public <T> void addSkinAttrHolder(String attrName, ISkinMethodHolder<? extends View, ?> methodHolder)这个方法接收两个参数
- 第一个参数是属性名,如:
<EditText
android:id="@+id/editText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColorHint="@color/color_blue_50" />这里有一个EditText,我们要增加textColorHint的换肤能力,那么属性名就是textColorHint.
- 第二个参数是属性
set方法的方法引用,如EditText::setHintTextColor
示例
ISkinMethodHolder<EditText, Integer> setHintTextColor = EditText::setHintTextColor;
SkinManager.INSTANCE.addSkinAttrHolder("textColorHint", setHintTextColor);到这里,我们增加一个换肤能力就实现了!!!
请注意,ISkinMethodHolder<EditText, Integer>接口中的第二个类型表示的是set方法的参数类型
必须是设置资源值的方法,不能使用设置资源Id的重载方法!!!
原因是我们即使我们传入资源包中的资源ID,set方法内部也是通过APP的Resource对象获取的值
关于复杂参数的set方法处理
比如TextView的setTextSize()方法,接受两个参数:
我们还是可以通过方法引用来实现,这里需要转化一下
处理方式:
static void setTextSize(TextView view, float px) {
view.setTextSize(TypedValue.COMPLEX_UNIT_PX, px);
}
public static final ISkinMethodHolder<TextView, Float> setTextSize = SkinMethod::setTextSize;
// ......
SkinManager.INSTANCE.addSkinAttrHolder("textSize", setTextSize);关于自定义View,请提供属性的set方法即可
使用ISkinMethodHolder.andThen()来转换成MethodAcceptAndThen对象
这是对ISkinMethodHolder的功能拓展,增加了一个after操作,用于这条属性在执行完换肤之后的操作.
注意!! 只有设置了 app:skinMethodTag=""属性的View 才会执行此操作,未设置完全不会执行
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="默认主题"
app:skinMethodTag="title"
android:textColor="@color/color_blue_50" />示例
//文字颜色
ISkinMethodHolder<TextView, Integer> setTextColor = TextView::setTextColor;
MethodAcceptAndThen<TextView, Integer> setTextColorAndThen = setTextColor.andThen((textView, integer) -> {
if ("title".equals(textView.getTag(R.id.skinMethodTagID))) {
textView.setText("春节主题皮肤");
}
});
SkinManager.INSTANCE.addSkinAttrHolder("setTextColor", setTextColorAndThen);使用ISkinMethodHolder.proxy()来转换成MethodAcceptProxy对象
这是对ISkinMethodHolder的功能拓展,代理当前set方法,实现自己逻辑.
示例
ISkinMethodHolder<View, Drawable> setBackground = View::setBackground;
MethodAcceptProxy<View, Drawable> setBackgroundProxy = setBackground.proxy((view, value, methodHolder) -> {
if ("mProxyTag".equals(view.getTag(R.id.skinMethodTagID))) {
//You can do something
}
});
SkinManager.INSTANCE.addSkinAttrHolder("setBackground", setBackgroundProxy);注意!! 只有设置了app:skinMethodTag=""属性的View才会执行此操作,未设置完全不会执行
对于动态添加到布局中的 View(例如,在 Java 或 Kotlin 代码中创建的 View),可以使用 SkinBinder 来为其绑定换肤能力。这可以确保即使是动态创建的视图也能够响应主题变化。
SkinBinder 提供了一个便捷的 bind 方法,允许您为动态创建的 View 注册一个或多个换肤属性。当皮肤切换时,绑定的属性会自动更新。
// 方式1:绑定到 View 生命周期(推荐)
SkinBinder.bind(textView)
.bindColor(R.color.text_color, TextView::setTextColor)
.bindDrawable(R.drawable.bg, View::setBackground)
.bind();
// 方式2:绑定到 LifecycleOwner(Activity/Fragment)
SkinBinder.bind(textView)
.bindColor(R.color.text_color, TextView::setTextColor)
.bindTo(lifecycleOwner);当 View 不再需要时(例如,当包含它的 Activity 或 Fragment 被销毁时),SkinBinder 会自动处理解绑,无需手动操作,从而避免内存泄漏。
如果您需要在换肤发生时执行自定义逻辑(例如更新非 View 组件、刷新缓存等),可以注册换肤监听。
建议在Activity/Fragment中创建OnSkinChangeListener实例,并在生命周期方法中注册和注销,以避免内存泄漏。
// 1. 创建监听器实例
private final OnSkinChangeListener mSkinChangeListener = new OnSkinChangeListener() {
@Override
public void onSkinChanged(boolean isDefault) {
// ...
}
};
// 2. 在适当的时候注册监听 (例如 onCreate)
// 注册时会立即回调一次 onSkinChanged
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ...
SkinManager.INSTANCE.addSkinChangeListener(mSkinChangeListener);
}
// 3. 在不再需要监听时注销 (例如 onDestroy)
@Override
protected void onDestroy() {
super.onDestroy();
SkinManager.INSTANCE.removeSkinChangeListener(mSkinChangeListener);
}| 方法 | 说明 |
|---|---|
| loadDefault() | 重置换肤,切换为默认 |
| SkinBinder.bind(...) | 为动态创建的View绑定换肤属性,并自动管理生命周期。 |
| getSkinResource() | 获取资源包的资源对象,如果为空,返回APP的资源对象 |
| isSkinState() | 是否为换肤状态 |
| isSkinGlobalEnable() | 是否已启用全局换肤 |
| setSkinGlobalEnable(boolean enable) | 设置是否启用全局换肤开关,默认为true,可通过{app:skinEnable="boolean"}属性单独给View设置 |
| addSkinChangeListener(OnSkinChangeListener listener) | 注册换肤监听,注册时会立即回调一次 |
| removeSkinChangeListener(OnSkinChangeListener listener) | 取消注册换肤监听 |
| getColor() | 根据资源ID获取资源包中的Color颜色值 |
| getDrawable() | 根据资源ID获取资源包中的drawable对象 |
| getString() | 根据资源ID获取资源包中的字符 |
| getDimension() | 根据资源ID获取资源包中的Dimension资源值 |
| getSkinResId(@IdRes int resId) | 根据资源Id获取到皮肤包中的Id. |
