簡介
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)
-
MagicaSakura
多主題框架是針對的換色而言,其設(shè)計(jì)就是為換色而生,而對于其他的明星皮膚等換膚需求,則做不了該需求 - 使用該框架,我們的xml文件需要大改,很多需要改色的控件都需要使用其提供的
Tint
工具包的類替換原來的控件,有寫Tint包里面沒有類比如Toolbar
則需要自己處理.
本文作者:Anderson/Jerey_Jobs
博客地址 : http://jerey.cn/
簡書地址 : Anderson大碼渣
github地址 : https://github.com/Jerey-Jobs