換膚功能接入及原理

前陣子開源出了自己的mp3剪切器項目即彪,在鴻洋大神公眾號的宣傳下引來眾人關注。本著持續(xù)維護的心供鸠,計劃中項目要實現(xiàn)一個簡單的應用內(nèi)換膚功能豫柬。

經(jīng)過調研發(fā)現(xiàn)一款很用心的開源的換膚框架Android-skin-support。它支持應用內(nèi)換膚桥温、插件式換膚和自定義加載的方式引矩。在這里只介紹一下應用內(nèi)換膚方式,希望能夠拋磚引玉侵浸,詳細的介紹可以參考它的github旺韭。先來看看效果Gif圖。

效果圖

[圖片上傳失敗...(image-e2132e-1516800042573)]

使用方法

  1. 先來添加jcenter依賴庫,
    compile 'skin.support:skin-support:2.2.3'                   
    compile 'skin.support:skin-support-design:2.2.3'  
  1. Application類中增加初始化sdk代碼
SkinCompatManager.withoutActivity(this)                         // 基礎控件換膚初始化
                .addInflater(new SkinMaterialViewInflater())            // material design 控件換膚初始化[可選]
                .setSkinStatusBarColorEnable(false)                     // 關閉狀態(tài)欄換膚掏觉,默認打開[可選]
                .setSkinWindowBackgroundEnable(false)                   // 關閉windowBackground換膚区端,默認打開[可選]
                .loadSkin();
  1. 設置需要換膚的控件,我這里主要為toolbar換膚澳腹。
<skin.support.widget.SkinCompatToolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:fitsSystemWindows="true"
                android:minHeight="?attr/actionBarSize"
                android:popupTheme="@style/ThemeOverlay.AppCompat.Light"
                app:theme="@style/ThemeOverlay.AppCompat.ActionBar"
  1. 準備換膚的資源织盼,我這里用的主題顏色以及顏色的名稱,放入arrays.xml中。
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- 默認主題顏色 -->
    <color name="theme_color">#000000</color>

    <!-- theme array -->
    <color name="theme_color_redbase">#FB5B81</color>
    <color name="theme_color_blue">#0077D9</color>
    <color name="theme_color_bluelight">#03A9F4</color>
    <color name="theme_color_balck">#000000</color>
    <color name="theme_color_teal">#009688</color>
    <color name="theme_color_brown">#795548</color>
    <color name="theme_color_green">#4CAF50</color>
    <color name="theme_color_red">#FF5252</color>

    <!-- 主題顏色酱塔, 用于換膚沥邻, 例如:@color/xx_redbase -->
    <string-array name="theme_colors">
        <item>@color/theme_color_redbase</item>
        <item>@color/theme_color_blue</item>
        <item>@color/theme_color_bluelight</item>
        <item>@color/theme_color_balck</item>
        <item>@color/theme_color_teal</item>
        <item>@color/theme_color_brown</item>
        <item>@color/theme_color_green</item>
        <item>@color/theme_color_red</item>
    </string-array>

    <!-- 主題名稱, 用于換膚延旧, 例如:@color/xx_redbase -->
    <string-array name="theme_names">
        <item>redbase</item>
        <item>blue</item>
        <item>bluelight</item>
        <item>balck</item>
        <item>teal</item>
        <item>brown</item>
        <item>green</item>
        <item>red</item>
    </string-array>
</resources>

  1. 換膚api調用, 這里傳入了選中的主題顏色名稱谋国,以及換膚的加載方式(應用內(nèi)換膚)
SkinCompatManager.getInstance().loadSkin(
 mThemeColorList.get(mThemeColorAdapter.getPosition()).getName(),
 null,SkinCompatManager.SKIN_LOADER_STRATEGY_BUILD_IN);

問題

  • 如何定位需要換膚的view,如何進行換膚操作迁沫?
  • 如何遍歷每個activity中的需要換膚的view?

淺析

  1. 定位需要被換膚view的思路 主要通過LayoutInflaterFactory獲取到當前activity中所有的組件芦瘾,通過過濾定位到需要換膚的view〖可以參考鴻洋的這篇文章 Android 探究 LayoutInflater setFactory近弟。
    SkinCompatDelegate類實現(xiàn)了LayoutInflaterFactory接口,并實現(xiàn)了它的createView()方法, 這里看SkinCompatViewInflater類的createView()代碼如下:
            case "View":
                view = new SkinCompatView(context, attrs);
                break;
            case "LinearLayout":
                view = new SkinCompatLinearLayout(context, attrs);
                break;
            case "RelativeLayout":
                view = new SkinCompatRelativeLayout(context, attrs);
                break;
            case "FrameLayout":
                view = new SkinCompatFrameLayout(context, attrs);
                break;
            case "TextView":
                view = new SkinCompatTextView(context, attrs);
                break;
            case "ImageView":
                view = new SkinCompatImageView(context, attrs);
                break;

可以看到在createView遍歷后將其替換了對應的Skin開頭的對應的類挺智。而Skin開頭的類是什么呢祷愉, 來看SkinCompatView類。

public class SkinCompatView extends View implements SkinCompatSupportable {
    private SkinCompatBackgroundHelper mBackgroundTintHelper;
    ...
    @Override
    public void setBackgroundResource(int resId) {
        super.setBackgroundResource(resId);
        if (mBackgroundTintHelper != null) {
            mBackgroundTintHelper.onSetBackgroundResource(resId);
        }
    }
    @Override
    public void applySkin() {
        if (mBackgroundTintHelper != null) {
            mBackgroundTintHelper.applySkin();
        }
    }

}

可以看到它實現(xiàn)了SkinCompatSupportable接口以及其applySkin方法,該方法就是換膚的實現(xiàn)方法了二鳄。
接著來看SkinCompatDelegate.onCreateView()

@Override
    public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
        View view = createView(parent, name, context, attrs);
        if (view == null) {
            return null;
        }
        if (view instanceof SkinCompatSupportable) { 
            mSkinHelpers.add(new WeakReference<>((SkinCompatSupportable) view));
        }
        return view;
    }

上文已經(jīng)將view轉換為Skin開頭的View(實現(xiàn)了SkinCompatSupportable的View)可以看到將其緩存到了mSkinHelpers數(shù)組中赴涵。再看SkinCompatDelegate.applySkin()

    public void applySkin() {
        if (mSkinHelpers != null && !mSkinHelpers.isEmpty()) {
            for (WeakReference ref : mSkinHelpers) {
                if (ref != null && ref.get() != null) {
                    ((SkinCompatSupportable) ref.get()).applySkin();
                }
            }
        }
    }

該方法就是換膚的操作了,遍歷緩存中所有的view订讼,執(zhí)行其換膚方法applySkin髓窜。這里我們再看一下applySkin里面的實現(xiàn),來看SkinBuildInLoader.getTargetResourceEntryName()欺殿。

    @Override
    public String getTargetResourceEntryName(Context context, String skinName, int resId) {
        return context.getResources().getResourceEntryName(resId) + "_" + skinName;
    }
  1. 第二個問題寄纵,可以通過BaseActivity的方式來達到目的,但是有更好的方式脖苏。來看SkinActivityLifecycle的構造方法application.registerActivityLifecycleCallbacks(this);程拭。注冊之后沒個activity的聲明周期回調都會在該類中回調到, 如onActivityCreated()
@Override
    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
        if (isContextSkinEnable(activity)) {
            installLayoutFactory(activity);
            updateStatusBarColor(activity);
            updateWindowBackground(activity);
            if (activity instanceof SkinCompatSupportable) {
                ((SkinCompatSupportable) activity).applySkin();
            }
        }
    }

在每個activity創(chuàng)建后都會對應執(zhí)行到installLayoutFactory(activity)方法棍潘,從而達到對每個activity 換膚view做收集換膚的目的恃鞋。
到此,基本原理亦歉,都闡述完了山宾,具體不懂的更詳細的還要結合源碼來看,如下:

Github

mp3剪切器項目

Android-skin-support

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鳍徽,一起剝皮案震驚了整個濱河市资锰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌阶祭,老刑警劉巖绷杜,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異濒募,居然都是意外死亡鞭盟,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門瑰剃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來齿诉,“玉大人,你說我怎么就攤上這事晌姚≡辆纾” “怎么了?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵挥唠,是天一觀的道長抵恋。 經(jīng)常有香客問我,道長宝磨,這世上最難降的妖魔是什么弧关? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任盅安,我火速辦了婚禮,結果婚禮上世囊,老公的妹妹穿的比我還像新娘别瞭。我一直安慰自己,他們只是感情好株憾,可當我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布畜隶。 她就那樣靜靜地躺著,像睡著了一般号胚。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上浸遗,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天猫胁,我揣著相機與錄音,去河邊找鬼跛锌。 笑死弃秆,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的髓帽。 我是一名探鬼主播菠赚,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼郑藏!你這毒婦竟也來了衡查?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤必盖,失蹤者是張志新(化名)和其女友劉穎拌牲,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體歌粥,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡塌忽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了失驶。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片土居。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖嬉探,靈堂內(nèi)的尸體忽然破棺而出擦耀,到底是詐尸還是另有隱情,我是刑警寧澤涩堤,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布埂奈,位于F島的核電站,受9級特大地震影響定躏,放射性物質發(fā)生泄漏账磺。R本人自食惡果不足惜芹敌,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望垮抗。 院中可真熱鬧氏捞,春花似錦、人聲如沸冒版。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽辞嗡。三九已至捆等,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間续室,已是汗流浹背栋烤。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留挺狰,地道東北人明郭。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像丰泊,于是被迫代替她去往敵國和親薯定。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,163評論 25 707
  • afinalAfinal是一個android的ioc瞳购,orm框架 https://github.com/yangf...
    passiontim閱讀 15,434評論 2 45
  • 原文鏈接:https://github.com/opendigg/awesome-github-android-u...
    IM魂影閱讀 32,940評論 6 472
  • 喜歡多肉植物的花友們话侄,大叔每天都會精選一款多肉植物,讓大家了解学赛,這樣就會不斷學習到多肉的知識满葛,每天認識一款,一年也...
    多肉花客閱讀 813評論 0 2
  • 我是一只無家可歸的鳥罢屈, 昨天夜里嘀韧,蹲在樹上過夜, 剛一閉眼缠捌, 就遇到場暴雨锄贷, 淋成落湯鳥, 我又冷又饑曼月, 我使勁地...
    旖旎i閱讀 546評論 13 11