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

簡介

MagicaSakura是Bilibili開源的一套主題切換框架,其功能是在不重啓Activity的狀況下,可以無閃屏的對程序中的控件進行更換主題顏色.之因此能作到這一點,是由於其實現方式是切換主題時,設置主題顏色,經過其提供的ThemeUtils.refreshUI方法讓每一個控件進行改變顏色.javascript

關於該框架的使用能夠看原做者介紹www.xyczero.com/blog/articl…java

初步使用

咱們須要完成 switchColor 接口.android

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

    @ColorInt int replaceColor(Context context, @ColorInt int color);
}複製代碼

在該接口裏面, 有兩個方法返回的均是colorId, 咱們就是在這個切換器接口裏進行根據主題變換返回不一樣的顏色值便可.git

並將該接口設置爲全局變量,所以建議在Application中實現該接口,並設置,設置其爲全局切換器github

ThemeUtils.setSwitchColor(this);app

public static switchColor mSwitchColor;
    public static void setSwitchColor(switchColor switchColor) {
        mSwitchColor = switchColor;
    }複製代碼

ThemeUtils.refreshUI原理

在初始化接口後, 咱們可使用public static void refreshUI(Context context, ExtraRefreshable extraRefreshable)方法進行主題的切換.框架

咱們看一看該方法的源碼.其先拿到界面的rootview,再調用了`refreshView方法進行刷新.ide

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接口, 讓其執行((Tintable) view).tint()方法, 如果viewGroup, 則不斷遞歸進行該操做. 如果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()方法源碼。發現其是經過三個helper的tint來作的。其抽象出三個Helper,分別控制的是文本顏色變換,背景顏色變換以及複合繪圖變換。工具

@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源碼來看。

先看其構造函數,直接調用幾個Helper的void loadFromAttribute(AttributeSet attrs, int defStyleAttr)方法,也就是說在這些View的加載時,便去從配置的屬性中進行加載顏色,這解決了在刷新UI時,那些未出現的控件顏色沒法更改的問題。

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();
}複製代碼

其實裏面就是獲取顏色,設置顏色這些事情。

爲何須要複寫那些控件?

MagicaSakura的原理咱們知道是遍歷Tintable類View, 其會自動根據主題顏色換色,可是對於還未出現的那些View, 以後再出現,如果原生的,其不會更新本身的主題色的.我本想避免使用複寫控件的方式經過其餘屬性進行主題變換的,發現根本無法解決未出現的控件的主題問題。

缺點

  1. MagicaSakura多主題框架是針對的換色而言,其設計就是爲換色而生,而對於其餘的明星皮膚等換膚需求,則作不了該需求
  2. 使用該框架,咱們的xml文件須要大改,不少須要改色的控件都須要使用其提供的Tint工具包的類替換原來的控件,有寫Tint包裏面沒有類好比Toolbar則須要本身處理.

本文做者:Anderson/Jerey_Jobs

博客地址 : jerey.cn/
簡書地址 : Anderson大碼渣
github地址 : github.com/Jerey-Jobs

相關文章
相關標籤/搜索