Skip to content

ak-ing/SkinPlugin

Repository files navigation

Android 动态换肤框架

Android 动态换肤框架,支持动态加载插件资源,无需重启Activity,拓展能力很强



演示

Pixel 4 XL Android 13 实机测试

95b973c2ead665e65a1e17461d55e276.mp4

下载演示Demo






使用

1、实现换肤能力

将需要换肤页面的Activity继承自SkinBaseActivity

public class MainActivity extends SkinBaseActivity {
    //....
}




2、加载皮肤包

  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,默认全局开启







换肤能力

框架默认支持backgroundsrctextColortext常用资源切换,若需要增加功能


使用addSkinAttrHolder方法添加需要的属性

public <T> void addSkinAttrHolder(String attrName, ISkinMethodHolder<? extends View, ?> methodHolder)


这个方法接收两个参数

  1. 第一个参数是属性名,如:
<EditText
  android:id="@+id/editText"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:textColorHint="@color/color_blue_50" />

这里有一个EditText,我们要增加textColorHint的换肤能力,那么属性名就是textColorHint.



  1. 第二个参数是属性set方法的方法引用,如 EditText::setHintTextColor
示例
ISkinMethodHolder<EditText, Integer> setHintTextColor = EditText::setHintTextColor;
SkinManager.INSTANCE.addSkinAttrHolder("textColorHint", setHintTextColor);

到这里,我们增加一个换肤能力就实现了!!!




请注意,ISkinMethodHolder<EditText, Integer>接口中的第二个类型表示的是set方法的参数类型

必须是设置资源值的方法,不能使用设置资源Id的重载方法!!!

原因是我们即使我们传入资源包中的资源ID,set方法内部也是通过APP的Resource对象获取的





关于复杂参数的set方法处理

比如TextViewsetTextSize()方法,接受两个参数: image.png


我们还是可以通过方法引用来实现,这里需要转化一下

处理方式

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方法即可








功能拓展

MethodAcceptAndThen

使用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);



MethodAcceptProxy

使用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换肤

对于动态添加到布局中的 View(例如,在 Java 或 Kotlin 代码中创建的 View),可以使用 SkinBinder 来为其绑定换肤能力。这可以确保即使是动态创建的视图也能够响应主题变化。

使用 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 不再需要时(例如,当包含它的 ActivityFragment 被销毁时),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);
}





API参考

方法 说明
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.

About

Android 动态换肤框架,无需重启Activity,拓展能力很强

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages