Android 切換主題 (二)

Android 切換主題 (二)

背景

我原來寫過一篇文章關於 android 切換主題的文章 -- Android 切換主題以及換膚的實現 , 裏面介紹瞭如何使用 setTheme() 來切換主題,只不過使用這個函數有個缺點那就是你必須得從新啓動當前的 Activity 才能生效!那麼問題來了,有沒有方法保證咱們既使用了 setTheme() 又能不重啓當前的 Activity 呢?我告訴這是有的,下面我就是詳細介紹這個方法 (參考 github 上的一個開源項目進行介紹!文末給出這個項目的地址)。html

===android

原理

衆所周知,setTheme() 以後須要重啓當前的 Activity 的緣由是:它要從新渲染當前的 當前的 ViewTree 。因此如今咱們的作法就是咱們來本身給他渲染不就好了!那樣的話,就不須要重啓當前的 Activity 了!下面咱們就來看代碼吧!git

===github

代碼

代碼的實現核心就是:在用戶調用 setTheme() 以後,咱們獲取當前的 Theme ,以後咱們在從中獲取到咱們用的屬性,以後在設置到對應的控件上,這樣就ok了!app

第一步

咱們先定義個接口:ide

public interface ColorUiInterface {
    public View getView();
    public void setTheme(Resources.Theme themeId);
}

這樣的話咱們就能夠重寫全部的控件,讓他繼承該接口,並實現對應的函數,那麼在 setTheme() 以後就能夠直接調用每一個控件都有的 setTheme() 方法了!函數

第二步

如今咱們就來實現一個自定義的view性能

public class ColorTextView extends TextView implements ColorUiInterface {
    private int attr_drawable = -1;
    private int attr_textAppearance = -1;
    private int attr_textColor = -1;
    public ColorTextView(Context context) {
        super(context);
    }

    public ColorTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.attr_drawable = ViewAttributeUtil.getBackgroundAttibute(attrs);
        this.attr_textColor = ViewAttributeUtil.getTextColorAttribute(attrs);
    }

    public ColorTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.attr_drawable = ViewAttributeUtil.getBackgroundAttibute(attrs);
        this.attr_textColor = ViewAttributeUtil.getTextColorAttribute(attrs);
    }

    @Override
    public View getView() {
        return this;
    }

    @Override
    public void setTheme(Resources.Theme themeId) {
        if (attr_drawable != -1) {
            ViewAttributeUtil.applyBackgroundDrawable(this, themeId, attr_drawable);
        }
        if (attr_textColor != -1) {
            ViewAttributeUtil.applyTextColor(this, themeId, attr_textColor);
        }
    }
}

從以上代碼中咱們能夠看到,我首先獲取了一下一些經常使用的須要換膚的要素,好比:背景色,字體顏色,當讓還有其餘的,這個隨用戶定製!固然讀者也能夠實現其餘的自定義 view 。字體

第三步

如今咱們來看下 ViewAttributeUtil 這個類的具體實現!this

public class ViewAttributeUtil {

  public static int getAttributeValue(AttributeSet attr, int paramInt) {
    int value = -1;
    int count = attr.getAttributeCount();
    for(int i = 0; i <count;i++) {
        if(attr.getAttributeNameResource(i) == paramInt) {
            String str = attr.getAttributeValue(i);
            if(null != str && str.startsWith("?")) {
                value = Integer.valueOf(str.substring(1,str.length())).intValue();
                return value;
            }
        }
    }
    return value;
}

public static int getBackgroundAttibute(AttributeSet attr) {
    return getAttributeValue(attr , android.R.attr.background);
}

public static int getCheckMarkAttribute(AttributeSet attr) {
    return getAttributeValue(attr, android.R.attr.checkMark);
}

public static int getSrcAttribute(AttributeSet attr) {
    return getAttributeValue(attr, android.R.attr.src);
}

public static int getTextApperanceAttribute(AttributeSet attr) {
    return getAttributeValue(attr, android.R.attr.textAppearance);
}

public static int getDividerAttribute(AttributeSet attr) {
    return getAttributeValue(attr, android.R.attr.divider);
}

public static int getTextColorAttribute(AttributeSet attr) {
    return getAttributeValue(attr, android.R.attr.textColor);
}

public static void applyBackgroundDrawable(ColorUiInterface ci, Resources.Theme theme, int paramInt) {
    TypedArray ta = theme.obtainStyledAttributes(new int[]{paramInt});
    Drawable drawable = ta.getDrawable(0);
    if(null != ci) {
        (ci.getView()).setBackgroundDrawable(drawable);
    }
    ta.recycle();
}

public static void applyImageDrawable(ColorUiInterface ci, Resources.Theme theme, int paramInt) {
    TypedArray ta = theme.obtainStyledAttributes(new int[]{paramInt});
    Drawable drawable = ta.getDrawable(0);
    if(null != ci && ci instanceof ImageView) {
        ((ImageView)ci.getView()).setImageDrawable(drawable);
    }
    ta.recycle();
}

public static void applyTextAppearance(ColorUiInterface ci, Resources.Theme theme, int paramInt) {
    TypedArray ta = theme.obtainStyledAttributes(new int[]{paramInt});
    int resourceId = ta.getResourceId(0,0);
    if(null != ci && ci instanceof TextView) {
        ((TextView)ci.getView()).setTextAppearance(ci.getView().getContext(), resourceId);
    }
    ta.recycle();
}

public static void applyTextColor(ColorUiInterface ci, Resources.Theme theme, int paramInt) {
    TypedArray ta = theme.obtainStyledAttributes(new int[]{paramInt});
    int resourceId = ta.getColor(0,0);
    if(null != ci && ci instanceof TextView) {
        ((TextView)ci.getView()).setTextColor(resourceId);
    }
    ta.recycle();
}
}

這個類比較簡單,就是根據對應的 themeid 獲得對應 themeid 的值!好了目前爲止,咱們還差一步就是,當咱們調用了 Activity 的 setTheme() 方法以後,從新設置到對應的view就好了!

第四步

直接上代碼

public class ColorUiUtil {
/**
 * 切換應用主題
 *
 * @param rootView
 */
public static void changeTheme(View rootView, Resources.Theme theme) {
    if (rootView instanceof ColorUiInterface) {
        ((ColorUiInterface) rootView).setTheme(theme);
        if (rootView instanceof ViewGroup) {
            int count = ((ViewGroup) rootView).getChildCount();
            for (int i = 0; i < count; i++) {
                changeTheme(((ViewGroup) rootView).getChildAt(i), theme);
            }
        }
        if (rootView instanceof AbsListView) {
            try {
                Field localField = AbsListView.class.getDeclaredField("mRecycler");
                localField.setAccessible(true);
                Method localMethod = Class.forName("android.widget.AbsListView$RecycleBin").getDeclaredMethod("clear", new Class[0]);
                localMethod.setAccessible(true);
                localMethod.invoke(localField.get(rootView), new Object[0]);
            } catch (NoSuchFieldException e1) {
                e1.printStackTrace();
            } catch (ClassNotFoundException e2) {
                e2.printStackTrace();
            } catch (NoSuchMethodException e3) {
                e3.printStackTrace();
            } catch (IllegalAccessException e4) {
                e4.printStackTrace();
            } catch (InvocationTargetException e5) {
                e5.printStackTrace();
            }
        }
    } else {
        if (rootView instanceof ViewGroup) {
            int count = ((ViewGroup) rootView).getChildCount();
            for (int i = 0; i < count; i++) {
                changeTheme(((ViewGroup) rootView).getChildAt(i), theme);
            }
        }
        if (rootView instanceof AbsListView) {
            try {
                Field localField = AbsListView.class.getDeclaredField("mRecycler");
                localField.setAccessible(true);
                Method localMethod = Class.forName("android.widget.AbsListView$RecycleBin").getDeclaredMethod("clear", new Class[0]);
                localMethod.setAccessible(true);
                localMethod.invoke(localField.get(rootView), new Object[0]);
            } catch (NoSuchFieldException e1) {
                e1.printStackTrace();
            } catch (ClassNotFoundException e2) {
                e2.printStackTrace();
            } catch (NoSuchMethodException e3) {
                e3.printStackTrace();
            } catch (IllegalAccessException e4) {
                e4.printStackTrace();
            } catch (InvocationTargetException e5) {
                e5.printStackTrace();
            }
        }
    }
}
}

代碼很簡單,就是用了遞歸,從當前的 ViewTree 中找到 繼承了咱們在第一步定義的那個接口,以後再調用其 setTheme() 方法就好了!!

最後一步

那麼咱們如今來看下如何在 Activity 中是如何調用的!

setTheme(R.style.theme_1);
   ColorUiUtil.changeTheme(rootView, getTheme());

是否是很簡單呀!

總結

這個方法我感受蠻好的,要說惟一的缺點吧!就是使用了遞歸!有點耗性能!可是這個我估計是能夠忽略不計的!!

*** 如今給出這個開源項目的地址 MultipleTheme ***

相關文章
相關標籤/搜索