前段時(shí)間給App接入了換膚功能,使用到了Android-skin-support這個(gè)換膚框架罩缴,所以寫這篇文章記錄一下咪橙。
換膚效果.png
Android-skin-support集成
- 依舊使用v7的support,使用以下依賴
implementation 'skin.support:skin-support:3.1.4' // skin-support 基礎(chǔ)控件支持
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對(duì)比诊笤,就是版本號(hào)升級(jí)到了4.04,support是3.1.4喔)
implementation 'skin.support:skin-support:4.0.4' // skin-support
implementation 'skin.support:skin-support-appcompat:4.0.4' // skin-support 基礎(chǔ)控件支持
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,換膚時(shí)鄙煤,才會(huì)遍歷View樹上的控件進(jìn)行換膚晾匠。
public class BaseActivity extends SkinCompatActivity {
}
Android-skin-support初始化以及簡單封裝使用
- 我對(duì)換膚框架初始化和換膚方法封裝了一下,提供了一個(gè)Api接口以及一個(gè)實(shí)現(xiàn)類
//Api接口
interface SkinApi {
/**
* 初始化
*/
fun init(application: Application)
/**
* 從Assets中加載皮膚apk
* @param targetSkinName 目標(biāo)皮膚名稱
* @param startBlock 開始換膚時(shí)回調(diào)
* @param successBlock 換膚成功時(shí)回調(diào)
* @param failedBlock 換膚失敗時(shí)回調(diào)
*/
fun loadSkinFromAssets(
targetSkinName: String,
startBlock: ((preSkinName: String) -> Unit)? = null,
successBlock: ((preSkinName: String, applySkinName: String) -> Unit)? = null,
failedBlock: ((errMsg: String) -> Unit)? = null
)
}
//實(shí)現(xiàn)類
object SkinProxy : SkinApi {
override fun init(application: Application) {
SkinMaterialManager.init(application)
//基礎(chǔ)控件換膚初始化
SkinCompatManager.withoutActivity(application)
//material design 控件換膚初始化[可選]
.addInflater(SkinMaterialViewInflater())
//ConstraintLayout 控件換膚初始化[可選]
.addInflater(SkinConstraintViewInflater())
//CardView v7 控件換膚初始化[可選]
.addInflater(SkinCardViewInflater())
//關(guān)閉狀態(tài)欄換膚梯刚,默認(rèn)打開[可選]
//.setSkinStatusBarColorEnable(false)
//關(guān)閉windowBackground換膚凉馆,默認(rèn)打開[可選]
//.setSkinWindowBackgroundEnable(false)
.loadSkinFromAssets(
SkinStorage.getApplyAppSkinName()
)
}
override fun loadSkinFromAssets(
targetSkinName: String,
startBlock: ((preSkinName: String) -> Unit)?,
successBlock: ((preSkinName: String, applySkinName: String) -> Unit)?,
failedBlock: ((errMsg: String) -> Unit)?
) {
//獲取應(yīng)用前的皮膚名稱,如果重復(fù)應(yīng)用,不繼續(xù)
if (SkinStorage.getApplyAppSkinName() == targetSkinName) {
return
}
SkinCompatManager.getInstance()
.loadSkinFromAssets(targetSkinName, startBlock, { preSkinName, newApplySkinName ->
//保存記錄到本地
SkinStorage.saveApplySkinName(newApplySkinName)
successBlock?.invoke(preSkinName, newApplySkinName)
}, failedBlock)
}
}
- loadSkinFromAssets()方法句喜,是從assets文件夾中加載皮膚包的方法预愤,是我對(duì)庫中SkinCompatManager添加的拓展方法,目的是添加換膚開始咳胃、成功植康、失敗的回調(diào)。以及提供換膚前應(yīng)用的皮膚名稱展懈。
/**
* 插件化方式加載皮膚:從Assets文件夾中加載
* @param targetSkinName 本次要應(yīng)用的皮膚
* @param startBlock 開始應(yīng)用時(shí)回調(diào)
* @param successBlock 應(yīng)用成功時(shí)回調(diào)
* @param failedBlock 應(yīng)用失敗時(shí)回調(diào)
*/
@JvmOverloads
fun SkinCompatManager.loadSkinFromAssets(
targetSkinName: String,
startBlock: ((preSkinName: String) -> Unit)? = null,
successBlock: ((preSkinName: String, newApplySkinName: String) -> Unit)? = null,
failedBlock: ((errMsg: String) -> Unit)? = null
) {
//從sp中销睁,獲取應(yīng)用前的皮膚名稱
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中調(diào)用初始化換膚框架
SkinProxy.init(it.applicationContext as Application)
- 切換皮膚,皮膚包打包見下面的 打包皮膚包
//目標(biāo)皮膚包名稱
val targetSkin = "purple.skin";
//開始切換皮膚
SkinProxy.loadSkinFromAssets(
targetSkin, { preSkinName ->
logd("開始換膚存崖,當(dāng)前應(yīng)用的皮膚為: $preSkinName")
}, { preSkinName, newApplySkinName ->
logd("換膚成功冻记,舊皮膚為: ${preSkinName},新皮膚為: $newApplySkinName")
}, { errMsg ->
logd("換膚失敗来惧,errMsg: $errMsg")
}
)
打包皮膚包
打包皮膚包冗栗,庫文檔并沒有細(xì)說,但其實(shí)很簡單供搀,新建一個(gè)Application的Module模塊(可運(yùn)行模塊)隅居,在res資源目錄下放置同名資源,打包為apk包葛虐,放到宿主的assets文件夾下的skins文件夾下胎源。
主題可以有很多,我們不必每個(gè)皮膚包都新建一個(gè)Module屿脐,我們只需要使用Gradle做多渠道打包即可涕蚤。
注意,皮膚包的包名不能和宿主包名一致的诵,所以使用applicationIdSuffix万栅,給每個(gè)皮膚包都加一個(gè)后綴即可。
- build.gradle文件配置多渠道打包
flavorDimensions "default"
//多種皮膚包奢驯,多渠道打包配置
productFlavors {
//原始顏色
"default" {
applicationIdSuffix ".default"
}
//紫色
purple {
applicationIdSuffix ".purple"
}
//藍(lán)色
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>
多渠道打包皮膚包.png
- 打包皮膚包apk瘪阁,將apk重命名撒遣,注意將apk后綴改為skin后綴,然后將皮膚包放置到宿主的assets文件夾下的skins文件夾下管跺。
放置皮膚包到宿主assets文件夾下.png
自定義控件支持換膚
換膚框架义黎,提供了v7、design庫的控件的換膚版本豁跑,但是我們自己的自定義控件則自己去適配了廉涕,下面我給出自定義頂部欄、TabLayout的換膚適配。(還有其他適配方案狐蜕,可以看官方Github文檔)宠纯,基本步驟如下:
- 繼承需要換膚的控件,實(shí)現(xiàn)SkinCompatSupportable接口
- 在控件的構(gòu)造方法中层释,獲取需要換膚的自定義屬性婆瓜,獲取當(dāng)前應(yīng)用的皮膚資源,進(jìn)行換膚(回顯之前的換膚設(shè)置)
- 在SkinCompatSupportable接口的applySkin回調(diào)中贡羔,再處理應(yīng)用運(yùn)行中換膚的處理(啟動(dòng)時(shí)不會(huì)回調(diào)廉白,手動(dòng)切換皮膚時(shí)回調(diào))。
自定義頂部欄乖寒,其實(shí)使用的是QMUI的TopBar猴蹂,有興趣的小伙伴可以去看下。
自定義屬性
<!--************ TopBar自定義屬性 ***********-->
<declare-styleable name="TopBar">
<!-- 省略其他自定義屬性... -->
<attr name="topbar_bg_color" format="color"/>
<!-- 省略其他自定義屬性... -->
</declare-styleable>
- 換膚兼容
//定義支持換膚控件的TopBar
public 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磅轻。應(yīng)用之前使用的換膚(回顯)
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、應(yīng)用運(yùn)行間马澈,手動(dòng)切換換膚回調(diào)瓢省,再次進(jìn)行換膚操作
applyTopBarBackgroundColor();
}
}
- TabLayout,自定義SkinTabLayout繼承于SkinMaterialTabLayout痊班,而SkinMaterialTabLayout繼承design包的TabLayout,但是換膚庫中的SkinMaterialTabLayout并沒有處理TabLayout的背景換膚處理摹量,不太明白為什么其他屬性都處理了涤伐,這個(gè)那么常用的屬性不處理。
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);
//步驟一:庫里其實(shí)提供了android:backgroundColor背景顏色屬性處理SkinCompatBackgroundHelper類缨称,我們讓它去對(duì)TabLayout進(jìn)行背景顏色換膚即可凝果,不需要自己寫
mSkinCompatBackgroundHelper = new SkinCompatBackgroundHelper(this);
mSkinCompatBackgroundHelper.loadFromAttributes(attrs, defStyleAttr);
//步驟二:馬上處理換膚
mSkinCompatBackgroundHelper.applySkin();
}
@Override
public void applySkin() {
super.applySkin();
//步驟三:應(yīng)用運(yùn)行間,手動(dòng)切換換膚回調(diào)睦尽,再次進(jìn)行換膚操作
if (mSkinCompatBackgroundHelper != null) {
mSkinCompatBackgroundHelper.applySkin();
}
}
}