Android换肤框架Android-skin-support的使用

龙旋

共 8672字,需浏览 18分钟

 ·

2021-08-03 19:14

前段时间给App接入了换肤功能,使用到了Android-skin-support这个换肤框架,所以写这篇文章记录一下。

Android-skin-support集成

  • 依旧使用v7的support,使用以下依赖

implementation 'skin.support:skin-support:3.1.4'                   // skin-support 基础控件支持implementation 'skin.support:skin-support-design:3.1.4'            // skin-support-design material design 控件支持[可选]implementation 'skin.support:skin-support-cardview:3.1.4'          // skin-support-cardview CardView 控件支持[可选]implementation 'skin.support:skin-support-constraint-layout:3.1.4' // skin-support-constraint-layout ConstraintLayout 控件支持[可选]


  • 已迁移到AndroidX,则使用以下依赖(注:和上面的support对比,就是版本号升级到了4.04,support是3.1.4喔)

implementation 'skin.support:skin-support:4.0.4'                   // skin-supportimplementation 'skin.support:skin-support-appcompat:4.0.4'         // skin-support 基础控件支持implementation 'skin.support:skin-support-design:4.0.4'            // skin-support-design material design 控件支持[可选]implementation 'skin.support:skin-support-cardview:4.0.4'          // skin-support-cardview CardView 控件支持[可选]implementation 'skin.support:skin-support-constraint-layout:4.0.4' // skin-support-constraint-layout ConstraintLayout 控件支持[可选]


  • Activity基类继承SkinCompatActivity。继承的确不太友好,只有继承了SkinCompatActivity,换肤时,才会遍历View树上的控件进行换肤。

public class BaseActivity extends SkinCompatActivity {}


Android-skin-support初始化以及简单封装使用

  • 我对换肤框架初始化和换肤方法封装了一下,提供了一个Api接口以及一个实现类

//Api接口interface SkinApi {    /**     * 初始化     */    fun init(application: Application)
/** * 从Assets中加载皮肤apk * @param targetSkinName 目标皮肤名称 * @param startBlock 开始换肤时回调 * @param successBlock 换肤成功时回调 * @param failedBlock 换肤失败时回调 */ fun loadSkinFromAssets( targetSkinName: String, startBlock: ((preSkinName: String) -> Unit)? = null, successBlock: ((preSkinName: String, applySkinName: String) -> Unit)? = null, failedBlock: ((errMsg: String) -> Unit)? = null )}
//实现类object SkinProxy : SkinApi { override fun init(application: Application) { SkinMaterialManager.init(application) //基础控件换肤初始化 SkinCompatManager.withoutActivity(application) //material design 控件换肤初始化[可选] .addInflater(SkinMaterialViewInflater()) //ConstraintLayout 控件换肤初始化[可选] .addInflater(SkinConstraintViewInflater()) //CardView v7 控件换肤初始化[可选] .addInflater(SkinCardViewInflater()) //关闭状态栏换肤,默认打开[可选] //.setSkinStatusBarColorEnable(false) //关闭windowBackground换肤,默认打开[可选] //.setSkinWindowBackgroundEnable(false) .loadSkinFromAssets( SkinStorage.getApplyAppSkinName() ) }
override fun loadSkinFromAssets( targetSkinName: String, startBlock: ((preSkinName: String) -> Unit)?, successBlock: ((preSkinName: String, applySkinName: String) -> Unit)?, failedBlock: ((errMsg: String) -> Unit)? ) { //获取应用前的皮肤名称,如果重复应用,不继续 if (SkinStorage.getApplyAppSkinName() == targetSkinName) { return } SkinCompatManager.getInstance() .loadSkinFromAssets(targetSkinName, startBlock, { preSkinName, newApplySkinName -> //保存记录到本地 SkinStorage.saveApplySkinName(newApplySkinName) successBlock?.invoke(preSkinName, newApplySkinName) }, failedBlock) }}


  • loadSkinFromAssets()方法,是从assets文件夹中加载皮肤包的方法,是我对库中SkinCompatManager添加的拓展方法,目的是添加换肤开始、成功、失败的回调。以及提供换肤前应用的皮肤名称。


/** * 插件化方式加载皮肤:从Assets文件夹中加载 * @param targetSkinName 本次要应用的皮肤 * @param startBlock 开始应用时回调 * @param successBlock 应用成功时回调 * @param failedBlock 应用失败时回调 */@JvmOverloadsfun SkinCompatManager.loadSkinFromAssets(    targetSkinName: String,    startBlock: ((preSkinName: String) -> Unit)? = null,    successBlock: ((preSkinName: String, newApplySkinName: String) -> Unit)? = null,    failedBlock: ((errMsg: String) -> Unit)? = null) {    //从sp中,获取应用前的皮肤名称    val preSkinName = SkinStorage.getApplyAppSkinName()    loadSkin(        targetSkinName,        object : SkinCompatManager.SkinLoaderListener {            override fun onStart() {                startBlock?.invoke(preSkinName)            }
override fun onSuccess() { successBlock?.invoke(preSkinName, targetSkinName) }
override fun onFailed(errMsg: String?) { failedBlock?.invoke(errMsg ?: "") } }, SkinCompatManager.SKIN_LOADER_STRATEGY_ASSETS )}


  • 在Application中调用初始化换肤框架

SkinProxy.init(it.applicationContext as Application)


  • 切换皮肤,皮肤包打包见下面的 打包皮肤包

//目标皮肤包名称val targetSkin = "purple.skin";//开始切换皮肤SkinProxy.loadSkinFromAssets(    targetSkin, { preSkinName ->        logd("开始换肤,当前应用的皮肤为: $preSkinName")    }, { preSkinName, newApplySkinName ->        logd("换肤成功,旧皮肤为: ${preSkinName},新皮肤为: $newApplySkinName")    }, { errMsg ->        logd("换肤失败,errMsg: $errMsg")    })


打包皮肤包

打包皮肤包,库文档并没有细说,但其实很简单,新建一个Application的Module模块(可运行模块),在res资源目录下放置同名资源,打包为apk包,放到宿主的assets文件夹下的skins文件夹下。

主题可以有很多,我们不必每个皮肤包都新建一个Module,我们只需要使用Gradle做多渠道打包即可。

注意,皮肤包的包名不能和宿主包名一致,所以使用applicationIdSuffix,给每个皮肤包都加一个后缀即可。

  • build.gradle文件配置多渠道打包

flavorDimensions "default"//多种皮肤包,多渠道打包配置productFlavors {    //原始颜色    "default" {        applicationIdSuffix ".default"    }    //紫色    purple {        applicationIdSuffix ".purple"    }    //蓝色    blue {        applicationIdSuffix ".blue"    }}


  • 例如:我的需求只有将主题色替换掉即可,所以在不同的多渠道文件夹下,放置不同主题的colors.xml文件

<?xml version="1.0" encoding="utf-8"?><resources>    <color name="base_color_primary">#673AB7</color>    <color name="base_color_primary2">#7C4DFF</color>    <color name="base_color_primary_dark">#512DA8</color>    <color name="base_color_accent">#7C4DFF</color></resources>



  • 打包皮肤包apk,将apk重命名,注意将apk后缀改为skin后缀,然后将皮肤包放置到宿主的assets文件夹下的skins文件夹下。


自定义控件支持换肤

换肤框架,提供了v7、design库的控件的换肤版本,但是我们自己的自定义控件则自己去适配了,下面我给出自定义顶部栏、TabLayout的换肤适配。(还有其他适配方案,可以看官方Github文档),基本步骤如下:

  1. 继承需要换肤的控件,实现SkinCompatSupportable接口


  2. 在控件的构造方法中,获取需要换肤的自定义属性,获取当前应用的皮肤资源,进行换肤(回显之前的换肤设置)


  3. 在SkinCompatSupportable接口的applySkin回调中,再处理应用运行中换肤的处理(启动时不会回调,手动切换皮肤时回调)。


  • 自定义顶部栏,其实使用的是QMUI的TopBar,有兴趣的小伙伴可以去看下。


  • 自定义属性

<!--************ TopBar自定义属性 ***********--><declare-styleable name="TopBar">    <!-- 省略其他自定义属性... -->
<attr name="topbar_bg_color" format="color"/>
<!-- 省略其他自定义属性... --></declare-styleable>


  • 换肤兼容

//定义支持换肤控件的TopBarpublic class SkinCompatTopBar extends TopBar implements SkinCompatSupportable {    private int mTopBarBgColorResId;
public SkinCompatTopBar(Context context, AttributeSet attrs) { super(context, attrs); //步骤一:获取需要支持换肤的自定义属性,例如这里顶部栏的背景颜色 TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.TopBar, 0, 0); mTopBarBgColorResId = array.getResourceId(R.styleable.TopBar_topbar_bg_color, SkinCompatHelper.INVALID_ID); array.recycle(); //2。应用之前使用的换肤(回显) applyTopBarBackgroundColor(); }
//换肤处理 private void applyTopBarBackgroundColor() { mTopBarBgColorResId = SkinCompatHelper.checkResourceId(mTopBarBgColorResId); if (mTopBarBgColorResId != SkinCompatHelper.INVALID_ID) { int color = SkinCompatResources.getColor(getContext(), mTopBarBgColorResId); setTopBarBackgroundColor(color); } }
@Override public void applySkin() { //3、应用运行间,手动切换换肤回调,再次进行换肤操作 applyTopBarBackgroundColor(); }}


  • TabLayout,自定义SkinTabLayout继承于SkinMaterialTabLayout,而SkinMaterialTabLayout继承design包的TabLayout,但是换肤库中的SkinMaterialTabLayout并没有处理TabLayout的背景换肤处理,不太明白为什么其他属性都处理了,这个那么常用的属性不处理。


public class SkinTabLayout extends SkinMaterialTabLayout {    private SkinCompatBackgroundHelper mSkinCompatBackgroundHelper;
public SkinTabLayout(Context context) { this(context, null); }
public SkinTabLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); }
public SkinTabLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); //步骤一:库里其实提供了android:backgroundColor背景颜色属性处理SkinCompatBackgroundHelper类,我们让它去对TabLayout进行背景颜色换肤即可,不需要自己写 mSkinCompatBackgroundHelper = new SkinCompatBackgroundHelper(this); mSkinCompatBackgroundHelper.loadFromAttributes(attrs, defStyleAttr); //步骤二:马上处理换肤 mSkinCompatBackgroundHelper.applySkin(); }
@Override public void applySkin() { super.applySkin(); //步骤三:应用运行间,手动切换换肤回调,再次进行换肤操作 if (mSkinCompatBackgroundHelper != null) { mSkinCompatBackgroundHelper.applySkin(); } }}
到这里就结束啦。
浏览 104
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报