來自B站的開源的MagicaSakura源碼解析

簡介

MagicaSakura是Bilibili開源的一套主題切換框架,其功能是在不重啟Activity的情況下,能夠無閃屏的對程序中的控件進(jìn)行更換主題顏色.之所以能做到這一點(diǎn),是因?yàn)槠鋵?shí)現(xiàn)方式是切換主題時,設(shè)置主題顏色,通過其提供的ThemeUtils.refreshUI方法讓每個控件進(jìn)行改變顏色.

關(guān)于該框架的使用可以看原作者介紹http://www.xyczero.com/blog/article/31/

初步使用

我們需要完成 switchColor 接口.

public interface switchColor {
    @ColorInt int replaceColorById(Context context, @ColorRes int colorId);

    @ColorInt int replaceColor(Context context, @ColorInt int color);
}

在該接口里面, 有兩個方法返回的均是colorId, 我們就是在這個切換器接口里進(jìn)行根據(jù)主題變換返回不同的顏色值即可.

并將該接口設(shè)置為全局變量,因此建議在Application中實(shí)現(xiàn)該接口,并設(shè)置,設(shè)置其為全局切換器

ThemeUtils.setSwitchColor(this);

    public static switchColor mSwitchColor;
    public static void setSwitchColor(switchColor switchColor) {
        mSwitchColor = switchColor;
    }

ThemeUtils.refreshUI原理

在初始化接口后, 我們可以使用public static void refreshUI(Context context, ExtraRefreshable extraRefreshable)方法進(jìn)行主題的切換.

我們看一看該方法的源碼.其先拿到界面的rootview,再調(diào)用了`refreshView方法進(jìn)行刷新.

public static void refreshUI(Context context, ExtraRefreshable extraRefreshable) {
    TintManager.clearTintCache();
    Activity activity = getWrapperActivity(context);
    if (activity != null) {
        if (extraRefreshable != null) {
            extraRefreshable.refreshGlobal(activity);
        }
        //拿到界面的根目錄.
        View rootView = activity.getWindow().getDecorView().findViewById(android.R.id.content);
        refreshView(rootView, extraRefreshable);
    }
}

再來看refreshView方法, 可以看到,如果該view 完成了Tintable接口, 讓其執(zhí)行((Tintable) view).tint()方法, 若是viewGroup, 則不斷遞歸進(jìn)行該操作. 若是ListView(GridView)或者RecylerView就notify一下.若是RecyclerView垮斯,也是刷新一下克懊。

private static void refreshView(View view, ExtraRefreshable extraRefreshable) {
    if (view == null) return;

    view.destroyDrawingCache();
    if (view instanceof Tintable) {
        ((Tintable) view).tint();
        if (view instanceof ViewGroup) {
            for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
                refreshView(((ViewGroup) view).getChildAt(i), extraRefreshable);
            }
        }
    } else {
        if (extraRefreshable != null) {
            extraRefreshable.refreshSpecificView(view);
        }
        if (view instanceof AbsListView) {
            try {
                if (sRecyclerBin == null) {
                    sRecyclerBin = AbsListView.class.getDeclaredField("mRecycler");
                    sRecyclerBin.setAccessible(true);
                }
                if (sListViewClearMethod == null) {
                    sListViewClearMethod = Class.forName("android.widget.AbsListView$RecycleBin")
                            .getDeclaredMethod("clear");
                    sListViewClearMethod.setAccessible(true);
                }
                sListViewClearMethod.invoke(sRecyclerBin.get(view));
            }
            ...
            ListAdapter adapter = ((AbsListView) view).getAdapter();
            while (adapter instanceof WrapperListAdapter) {
                adapter = ((WrapperListAdapter) adapter).getWrappedAdapter();
            }
            if (adapter instanceof BaseAdapter) {
                ((BaseAdapter) adapter).notifyDataSetChanged();
            }
        }
        if (view instanceof RecyclerView) {
            try {
                if (sRecycler == null) {
                    sRecycler = RecyclerView.class.getDeclaredField("mRecycler");
                    sRecycler.setAccessible(true);
                }
                if (sRecycleViewClearMethod == null) {
                    sRecycleViewClearMethod = Class.forName("android.support.v7.widget.RecyclerView$Recycler")
                            .getDeclaredMethod("clear");
                    sRecycleViewClearMethod.setAccessible(true);
                }
                sRecycleViewClearMethod.invoke(sRecycler.get(view));
            }
            ...
            ((RecyclerView) view).getRecycledViewPool().clear();
            ((RecyclerView) view).invalidateItemDecorations();
        }
        if (view instanceof ViewGroup) {
            for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
                refreshView(((ViewGroup) view).getChildAt(i), extraRefreshable);
            }
        }
    }
}

view.tint()是怎么做的?

我們來看tint()方法源碼踪少。發(fā)現(xiàn)其是通過三個helper的tint來做的。其抽象出三個Helper,分別控制的是文本顏色變換贱鄙,背景顏色變換以及復(fù)合繪圖變換。

@Override
private AppCompatBackgroundHelper mBackgroundHelper;
private AppCompatCompoundDrawableHelper mCompoundDrawableHelper;
private AppCompatTextHelper mTextHelper;

public void tint() {
    if (mTextHelper != null) {
        mTextHelper.tint();
    }
    if (mBackgroundHelper != null) {
        mBackgroundHelper.tint();
    }
    if (mCompoundDrawableHelper != null) {
        mCompoundDrawableHelper.tint();
    }
}

我們從TintTextView源碼來看短曾。

先看其構(gòu)造函數(shù),直接調(diào)用幾個Helper的void loadFromAttribute(AttributeSet attrs, int defStyleAttr)方法,也就是說在這些View的加載時赐劣,便去從配置的屬性中進(jìn)行加載顏色嫉拐,這解決了在刷新UI時,那些未出現(xiàn)的控件顏色無法更改的問題。

public TintTextView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    if (isInEditMode()) {
        return;
    }
    TintManager tintManager = TintManager.get(getContext());

    mTextHelper = new AppCompatTextHelper(this, tintManager);
    mTextHelper.loadFromAttribute(attrs, defStyleAttr);

    mBackgroundHelper = new AppCompatBackgroundHelper(this, tintManager);
    mBackgroundHelper.loadFromAttribute(attrs, defStyleAttr);

    mCompoundDrawableHelper = new AppCompatCompoundDrawableHelper(this, tintManager);
    mCompoundDrawableHelper.loadFromAttribute(attrs, defStyleAttr);
}

來看一個Helper的load方法

void loadFromAttribute(AttributeSet attrs, int defStyleAttr) {
    TypedArray array = mView.getContext().obtainStyledAttributes(attrs, ATTRS, defStyleAttr, 0);

    int textColorId = array.getResourceId(0, 0);
    if (textColorId == 0) {
        setTextAppearanceForTextColor(array.getResourceId(2, 0), false);
    } else {
        setTextColor(textColorId);
    }

    if (array.hasValue(1)) {
        setLinkTextColor(array.getResourceId(1, 0));
    }
    array.recycle();
}

其實(shí)里面就是獲取顏色魁兼,設(shè)置顏色這些事情婉徘。

為什么需要復(fù)寫那些控件?

MagicaSakura的原理我們知道是遍歷Tintable類View, 其會自動根據(jù)主題顏色換色,但是對于還未出現(xiàn)的那些View, 之后再出現(xiàn),若是原生的,其不會更新自己的主題色的.我本想避免使用復(fù)寫控件的方式通過其他屬性進(jìn)行主題變換的,發(fā)現(xiàn)根本沒法解決未出現(xiàn)的控件的主題問題咐汞。

缺點(diǎn)

  1. MagicaSakura多主題框架是針對的換色而言,其設(shè)計(jì)就是為換色而生,而對于其他的明星皮膚等換膚需求,則做不了該需求
  2. 使用該框架,我們的xml文件需要大改,很多需要改色的控件都需要使用其提供的Tint工具包的類替換原來的控件,有寫Tint包里面沒有類比如Toolbar則需要自己處理.

本文作者:Anderson/Jerey_Jobs

博客地址 : http://jerey.cn/

簡書地址 : Anderson大碼渣

github地址 : https://github.com/Jerey-Jobs

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末盖呼,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子化撕,更是在濱河造成了極大的恐慌几晤,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件植阴,死亡現(xiàn)場離奇詭異蟹瘾,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)掠手,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進(jìn)店門憾朴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人喷鸽,你說我怎么就攤上這事伊脓。” “怎么了魁衙?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵报腔,是天一觀的道長。 經(jīng)常有香客問我剖淀,道長纯蛾,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任纵隔,我火速辦了婚禮翻诉,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘捌刮。我一直安慰自己碰煌,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布绅作。 她就那樣靜靜地躺著芦圾,像睡著了一般。 火紅的嫁衣襯著肌膚如雪俄认。 梳的紋絲不亂的頭發(fā)上个少,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天洪乍,我揣著相機(jī)與錄音,去河邊找鬼夜焦。 笑死壳澳,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的茫经。 我是一名探鬼主播巷波,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼卸伞!你這毒婦竟也來了抹镊?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤瞪慧,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后部念,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體弃酌,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年儡炼,在試婚紗的時候發(fā)現(xiàn)自己被綠了妓湘。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡乌询,死狀恐怖榜贴,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情妹田,我是刑警寧澤唬党,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站鬼佣,受9級特大地震影響驶拱,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜晶衷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一蓝纲、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧晌纫,春花似錦税迷、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至哥牍,卻和暖如春露懒,著一層夾襖步出監(jiān)牢的瞬間闯冷,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工懈词, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蛇耀,地道東北人。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓坎弯,卻偏偏與公主長得像纺涤,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子抠忘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評論 2 348

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

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,756評論 25 707
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫撩炊、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,059評論 4 62
  • 有人說崎脉,大學(xué)拧咳,是象牙塔。進(jìn)入其中的莘莘學(xué)子囚灼,終將帶著滿腹經(jīng)綸離開骆膝。然而,也不盡然灶体。大學(xué)阅签,對某些人來說,更像是一座屠...
    鷓鴣三啼閱讀 748評論 9 7
  • 交作業(yè):十年后的自己 看到這題目時確實(shí)讓我嚇了一跳蝎抽,因?yàn)楦緵]有去考慮十年后要干嘛政钟?然而如今卻渾渾噩噩工作了整整二...
    老浪閱讀 235評論 0 0
  • 好疼
    子清閱讀 108評論 0 1