Android-skin-support 換膚框架使用

前段時(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文檔)宠纯,基本步驟如下:

  1. 繼承需要換膚的控件,實(shí)現(xiàn)SkinCompatSupportable接口
  2. 在控件的構(gòu)造方法中层释,獲取需要換膚的自定義屬性婆瓜,獲取當(dāng)前應(yīng)用的皮膚資源,進(jìn)行換膚(回顯之前的換膚設(shè)置)
  3. 在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();
        }
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末器净,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子当凡,更是在濱河造成了極大的恐慌山害,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,386評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件沿量,死亡現(xiàn)場離奇詭異浪慌,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)朴则,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門权纤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事汹想⊥獾耍” “怎么了?”我有些...
    開封第一講書人閱讀 164,704評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵古掏,是天一觀的道長坐榆。 經(jīng)常有香客問我,道長冗茸,這世上最難降的妖魔是什么席镀? 我笑而不...
    開封第一講書人閱讀 58,702評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮夏漱,結(jié)果婚禮上豪诲,老公的妹妹穿的比我還像新娘。我一直安慰自己挂绰,他們只是感情好屎篱,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著葵蒂,像睡著了一般交播。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上践付,一...
    開封第一講書人閱讀 51,573評(píng)論 1 305
  • 那天秦士,我揣著相機(jī)與錄音,去河邊找鬼永高。 笑死隧土,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的命爬。 我是一名探鬼主播曹傀,決...
    沈念sama閱讀 40,314評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼饲宛!你這毒婦竟也來了皆愉?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,230評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤艇抠,失蹤者是張志新(化名)和其女友劉穎幕庐,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體练链,經(jīng)...
    沈念sama閱讀 45,680評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡翔脱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了媒鼓。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片届吁。...
    茶點(diǎn)故事閱讀 39,991評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡错妖,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出疚沐,到底是詐尸還是另有隱情暂氯,我是刑警寧澤,帶...
    沈念sama閱讀 35,706評(píng)論 5 346
  • 正文 年R本政府宣布亮蛔,位于F島的核電站痴施,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏究流。R本人自食惡果不足惜辣吃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望芬探。 院中可真熱鬧神得,春花似錦、人聲如沸偷仿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽酝静。三九已至节榜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間别智,已是汗流浹背宗苍。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評(píng)論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留亿遂,地道東北人浓若。 一個(gè)月前我還...
    沈念sama閱讀 48,158評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像蛇数,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子是越,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評(píng)論 2 355