天天看點

android 換膚(1)——插件式無縫換膚(解析鴻洋大神的換膚流程)

對于app換膚,這是一個常見而又常用的功能。雖然我做的項目中還沒涉及到換膚,但是還是想研究下。

于是,下載下傳了鴻洋大神的換膚demo來研究。

先看效果圖:(尊重鴻洋大神的代碼,效果圖上原創)

android 換膚(1)——插件式無縫換膚(解析鴻洋大神的換膚流程)

鴻洋大神的換膚有兩種:

1,ChangeSkin;侵入式換膚

2,AndroidChangeSkin;tag換膚

這兩種我都分析過。各有各的好處。不過我還是推薦使用第一種。

博文後面我會比較這兩種換膚的差異,以及為什麼推薦使用第一種tag換膚的原因。

這兩種換膚的核心方法其實是一樣的,隻不過怎麼擷取需要換膚的資源的方法不同而已。

先來分析關鍵代碼:

先看項目目錄:

android 換膚(1)——插件式無縫換膚(解析鴻洋大神的換膚流程)
android 換膚(1)——插件式無縫換膚(解析鴻洋大神的換膚流程)
android 換膚(1)——插件式無縫換膚(解析鴻洋大神的換膚流程)

我們可以從資源的命名看出來,資源的差異在于字尾名的不同,這是app内部換膚的關鍵,根據字尾名不同來區分。

而插件式換膚,也就是獲得sd卡裡的apk檔案,然後獲得其中的資源檔案進行換膚的資源命名也是遵循這個規則。

1,接口

ISkinCallback

/**
 * 設定皮膚狀态回調
 */
public interface ISkinCallback {
    void onStart();//開始

    void onError(Exception e);//錯誤

    void onComplete();//完成
}
           

ISkinListener

/**
 * 通知皮膚設定的接口
 */
public interface ISkinListener {
    //這個接口用于通知應該改變皮膚資源了
    void onSkinChanged();
}
           

執行換膚的時候其實就是調用的ISkinListener接口而已。所有繼承它的view都能得到換膚的通知。

2,儲存使用者的皮膚喜好設定

SkinSharedPreferencesUtils 類

/**
 * 儲存使用者對該app的皮膚設定
 */
public class SkinSharedPreferencesUtils {
    private SharedPreferences sharedPreferences;//鍵值對執行個體

    public SkinSharedPreferencesUtils(Context context) {
        sharedPreferences = context.getSharedPreferences(SkinContacts.PREF_NAME, Context.MODE_PRIVATE);
    }

    //獲得插件路徑
    public String getPluginPath() {
        if (sharedPreferences == null) return "";
        return sharedPreferences.getString(SkinContacts.KEY_PLUGIN_PATH, "");
    }

    //添加插件路徑
    public void setPluginPath(String pluginPath) {
        if (sharedPreferences == null) return;
        sharedPreferences.edit().putString(SkinContacts.KEY_PLUGIN_PATH, pluginPath).apply();
    }

    //獲得資源字尾
    public String getAttrSuffix() {
        if (sharedPreferences == null) return "";
        return sharedPreferences.getString(SkinContacts.KEY_PLUGIN_SUFFIX, "");
    }

    //添加資源字尾
    public void setAttrSuffix(String attrSuffix) {
        if (sharedPreferences == null) return;
        sharedPreferences.edit().putString(SkinContacts.KEY_PLUGIN_SUFFIX, attrSuffix).apply();
    }

    //獲得插件的包名
    public String getPluginPackage() {
        if (sharedPreferences == null) return "";
        return sharedPreferences.getString(SkinContacts.KEY_PLUGIN_PACKAGE, "");
    }

    //添加插件的包名
    public void setPluginPackage(String pluginPackage) {
        if (sharedPreferences == null) return;
        sharedPreferences.edit().putString(SkinContacts.KEY_PLUGIN_PACKAGE, pluginPackage).apply();

    }

    //清理目前的sharedPreferences
    public boolean clear() {
        if (sharedPreferences == null) return false;
        return sharedPreferences.edit().clear().commit();
    }
}
           

這沒啥好說的,鍵值對儲存在app中。

3,定義換膚常量

SkinContacts類

/**
 * 皮膚常量
 */
public class SkinContacts {

    public static final String PREF_NAME = "skin_plugin_name";//插件工廠名
    public static final String KEY_PLUGIN_PATH = "key_plugin_path";//插件路徑
    public static final String KEY_PLUGIN_PACKAGE = "key_plugin_package";//插件包名
    public static final String KEY_PLUGIN_SUFFIX = "key_plugin_suffix";//插件字尾
    public static final String ATTR_PREFIX = "skin:";//資源驗證的字首,隻有帶有skin的字首才是要被修改的字首
    public static final int SKIN_TAG = ;//表填驗證,獲得目前view的所有名字
}
           

4,重點方法1:皮膚資料總管

ResourceManager類

/**
 * 皮膚資料總管
 */
public class ResourceManager {
    private Resources mResources;//資源對象
    private String mPluginPackageName;//插件包名
    private String mSuffix;//皮膚差別的字尾名

    //預設圖檔和顔色類型
    private static final String DEFTYPE_DRAWABLE = "drawable";
    private static final String DEFTYPE_COLOR = "color";

    public ResourceManager(Resources mResources, String mPluginPackageName, String mSuffix) {
        this.mResources = mResources;
        this.mPluginPackageName = mPluginPackageName;
        this.mSuffix = TextUtils.isEmpty(mSuffix) ? "" : mSuffix;
    }

    //将資源名添加字尾,用于app内部的皮膚修改
    private String appendSuffix(String name) {
        //如果設定了皮膚的字尾名,則在資源名稱的後面添加字尾名
        //例如:預設皮膚的資源名是:skin_index_drawable
        //如果添加了字尾名,則說明使用了另外一套皮膚:skin_index_drawable_red
        return TextUtils.isEmpty(mSuffix) ? name : name + "_" + mSuffix;
    }

    //傳入資源名稱,根據包名和資源字尾名來确定傳回的資源
    public Drawable getDrawableByName(String name) {
        try {
            name = appendSuffix(name);
            //這段代碼的意思相當于在指定的包名下找到指定的資源檔案夾名字然後找到指定的資源名稱。參數是相反的。
            //參數:1,資源名;2,資源類型;3,包名
            return mResources.getDrawable(mResources.getIdentifier(name, DEFTYPE_DRAWABLE, mPluginPackageName));
        } catch (Resources.NotFoundException e) {
            try {
                //如果在圖檔中沒有找到資源就在顔色資源裡找
                return mResources.getDrawable(mResources.getIdentifier(name, DEFTYPE_COLOR, mPluginPackageName));
            } catch (Resources.NotFoundException e2) {
                e.printStackTrace();
                return null;
            }
        }
    }

    //根據資源名獲得顔色
    public int getColorByName(String name) {
        try {
            name = appendSuffix(name);
            return mResources.getColor(mResources.getIdentifier(name, DEFTYPE_COLOR, mPluginPackageName));
        } catch (Resources.NotFoundException e) {
            e.printStackTrace();
            return -;
        }
    }

    //根據資源名獲得顔色集合
    public ColorStateList getColorStateList(String name) {
        try {
            name = appendSuffix(name);
            return mResources.getColorStateList(mResources.getIdentifier(name, DEFTYPE_COLOR, mPluginPackageName));
        } catch (Resources.NotFoundException e) {
            e.printStackTrace();
            return null;
        }
    }

    //根據資源名獲得圖檔集合
    public ColorStateList getColorStateListtDrawable(String name) {
        try {
            name = appendSuffix(name);
            return mResources.getColorStateList(mResources.getIdentifier(name, DEFTYPE_DRAWABLE, mPluginPackageName));
        } catch (Resources.NotFoundException e) {
            e.printStackTrace();
            return null;
        }
    }
}
           

ResourceManager類用于儲存目前app的資源對象或者從插件中擷取的資源對象,用于得到資源對象中的資源。實際的換皮膚的資源和方法都由它管理。

5,重點方法2:skin

從項目結構圖可以看出,skin目錄下有四個java檔案。

他們建立的順序是:

1,SkinAttrType 具體修改皮膚的枚舉

2,SkinAttr 儲存資源名和資源類型,一一對應

3,SkinView 儲存需要更改皮膚的view和view下的所有資源,一一對應

4,SkinAttrSupport 獲得一個view下所有資源集合的工具類

1,SkinAttrType

/**
 * 3,資源類型枚舉
 */
public enum SkinAttrType {
    BACKGROUD("background")//背景,将給傳入的view設定新的背景
            {
                @Override
                public void apply(View view, String resName) {
                    //背景可能是圖檔也可能隻是顔色
                    Drawable drawable = getResourceManager().getDrawableByName(resName);
                    if (drawable == null) return;
                    view.setBackgroundDrawable(drawable);
                    LogUtils.i("背景:" + resName + "view的id:" + view.getId());
                }
            },
    TEXT_COLOR("textColor")//字型顔色,将給傳入的view設定新的字型顔色
            {
                @Override
                public void apply(View view, String resName) {
                    if (view instanceof TextView) {
                        ColorStateList colorlist = getResourceManager().getColorStateList(resName);
                        if (colorlist == null) return;
                        ((TextView) view).setTextColor(colorlist);
                        LogUtils.i("字型顔色:" + resName + "view的id:" + view.getId());
                    }
                }
            },
    SRC("src")//src圖檔,将給傳入的view指定新的圖檔
            {
                @Override
                public void apply(View view, String resName) {
                    if (view instanceof ImageView) {
                        Drawable drawable = getResourceManager().getDrawableByName(resName);
                        if (drawable == null) return;
                        ((ImageView) view).setImageDrawable(drawable);
                        LogUtils.i("src圖檔:" + resName + "view的id:" + view.getId());
                    }
                }
            };

    private String attrType;//資源類型

    //提供外部調用方法,獲得目前資源的類型
    public String getAttrType() {
        return attrType;
    }

    //構造方法
    SkinAttrType(String attrType) {
        this.attrType = attrType;
    }

    //抽象方法傳入需要改變的view和資源名
    protected abstract void apply(View view, String resName);

    //獲得資料總管
    public ResourceManager getResourceManager() {
        ResourceManager resourceManager = SkinManagerOutdated.getInstance().getResourceManager();
        return resourceManager;
    }
}
           

2,SkinAttr

/**
 * 2,該類儲存了一個view下的資源名和資源類型,對應關系
 */
public class SkinAttr {
    private String resName;//資源名
    private SkinAttrType attrType;//資源類型

    //構造方法中傳入資源類型執行個體和資源名
    public SkinAttr(SkinAttrType attrType, String resName) {
        this.resName = resName;
        this.attrType = attrType;
    }

    //執行換膚,對于傳進來的view換膚成相對應傳進來的資源名
    protected void apply(View view) {
        //枚舉中有具體的換膚操作
        attrType.apply(view, resName);
    }
}
           

3,SkinView

/**
 * 1,該類提供了一個activity所有view的資源替換方法
 */
public class SkinView {
    private View view;//需要改變皮膚的view
    private List<SkinAttr> attrs;//這個view下所有的資源執行個體

    //傳入view和所有資源
    public SkinView(View view, List<SkinAttr> skinAttrs) {
        this.view = view;
        this.attrs = skinAttrs;
    }

    //執行換膚,将該view下的所有資源都進行換膚
    public void apply() {
        if (view == null) return;
        for (SkinAttr attr : attrs) {
            //skinattr中有針對單個資源的換膚
            attr.apply(view);
        }
    }
}
           

4,SkinAttrSupprot

注意:這個SkinAttrSupprot類我将兩種換膚方式的方法都寫在其中了,以便于更好了解。真正實際用到的,隻有通過資源截取或者tag樣式擷取。兩者二選一。因為這兩個方法屬于不同的換膚方式。

/**
 * 4,皮膚屬性相容類
 * 因為每個activity都會使用到該方法是以直接将它設定成靜态的,以免多次執行個體化和釋放造成記憶體壓力過大
 */
public class SkinAttrSupport {

    /**
     * 通過資源截取
     *
     * @param attrs
     * @param context
     * @return
     */
    public static List<SkinAttr> getSkinAttrs(AttributeSet attrs, Context context) {
        List<SkinAttr> skinAttrs = new ArrayList<>();
        SkinAttr skinAttr;
        //在這裡循環周遊出這個activity中所包含的所有資源
        for (int i = ; i < attrs.getAttributeCount(); i++) {
            String attrName = attrs.getAttributeName(i);//屬性名
            String attrValue = attrs.getAttributeValue(i);//屬性值
            //通過屬性名獲得到屬性類型,這個屬性類型枚舉中已經包含了被查找到的枚舉屬性:例如包含了backage
            SkinAttrType attrType = getSupprotAttrType(attrName);
            if (attrType == null) continue;
            if (attrValue.startsWith("@")) {
                //通過屬性值獲得屬性id,以@開頭驗證
                int id = Integer.parseInt(attrValue.substring());
                //通過屬性id獲得這個屬性的名稱,例如R檔案id是0x7f050000;可以通過這個id得到它的命名:例如:skin_index_bg
                String entryName = context.getResources().getResourceEntryName(id);
                if (entryName.startsWith(SkinContacts.ATTR_PREFIX)) {
                    //我們驗證skin以後的資源名稱加上帶有資源類型的枚舉,例如:color類型的枚舉
                    skinAttr = new SkinAttr(attrType, entryName);
                    skinAttrs.add(skinAttr);
                    //LogUtils.i("添加資源SkinAttr:" + entryName);
                }
            }
        }
        return skinAttrs;
    }

    /**
     * tag的樣式,我們可以根據tag來截取
     *
     * @param tagStr 樣式:skin_left_menu_icon:src|skin_color_red:textColor
     * @return
     */
    public static List<SkinAttr> getSkinTags(String tagStr) {
        List<SkinAttr> skinAttrs = new ArrayList<>();
        if (TextUtils.isEmpty(tagStr)) return skinAttrs;
        //将string截取成一|分隔符的字元串數組
        String[] items = tagStr.split("\\|");
        for (String item : items) {
            //如果不包含辨別換膚的字首,則表示它不是需要換膚的
            if (!tagStr.startsWith(SkinContacts.ATTR_PREFIX))
                return skinAttrs;
            //截取出資源名和資源類型
            String[] resItems = item.split(":");
            String resName = resItems[];
            String resType = resItems[];

            //通過屬性名獲得到屬性類型,這個屬性類型枚舉中已經包含了被查找到的枚舉屬性:例如包含了backage
            SkinAttrType attrType = getSupprotAttrType(resType);
            if (attrType == null) continue;
            SkinAttr attr = new SkinAttr(attrType, resName);
            skinAttrs.add(attr);
        }
        return skinAttrs;
    }

    //傳入資源名得到資源類型執行個體
    private static SkinAttrType getSupprotAttrType(String attrName) {
        for (SkinAttrType attrType : SkinAttrType.values()) {
            //如果枚舉中有一個資源類型比對了,則傳回這個枚舉所帶有的資源類型執行個體
            if (attrType.getAttrType().equals(attrName))
                return attrType;
        }
        return null;
    }
}
           

6,重點方法3:SkinManager

這個類決定了你如何選擇皮膚,是調用換膚的核心。以上代碼是換膚的核心。

我們先來看看tag式換膚的SkinManager。

先将SkinManager設定成單例,然後定義屬性

public class SkinManager {
    private Context mContext;//這裡的上下文其實是appliaction的上下文,因為換膚全局有效
    private Resources mResources;//換膚的資源對象
    private ResourceManager mResourceManager;//換膚的資料總管
    private SkinSharedPreferencesUtils mPrefUtils;//使用者偏好設定
    private boolean usePlugin;//是否可以換膚辨別
    private String mSuffix = "";//應用内換膚的辨別字尾
    private String mCurPluginPath;//插件換膚的插件檔案路徑
    private String mCurPluginPkg;//插件的内置包名
    private Map<ISkinListener, List<SkinView>> mSkinViewMaps = new HashMap<>();//鍵值對指向哪一個view對應的它之下所有的需要更換的皮膚資源
    private List<ISkinListener> mActivities = new ArrayList<>();//儲存整個app被标記的要被換膚的對象

    private SkinManager() {
    }

    private static class SingletonHolder {
        static SkinManager sInstance = new SkinManager();
    }

    public static SkinManager getInstance() {
        return SingletonHolder.sInstance;
    }

    public ResourceManager getResourceManager() {
        if (!usePlugin) {
            mResourceManager = new ResourceManager(mContext.getResources(), mContext.getPackageName(), mSuffix);
        }
        return mResourceManager;
    }
           

init是在appliaction中調用的方法,為app第一次進來的時候,驗證使用者是否使用了插件皮膚,如果使用了則加載皮膚。

//使用者儲存的皮膚,初始化的時候就可以直接加載進去
    public void init(Context context) {
        mContext = context.getApplicationContext();
        mPrefUtils = new SkinSharedPreferencesUtils(context);
        String skinPluginPath = mPrefUtils.getPluginPath();
        String skinPluginPkg = mPrefUtils.getPluginPackage();
        mSuffix = mPrefUtils.getAttrSuffix();
        if (TextUtils.isEmpty(skinPluginPath))//如果沒有插件路徑則傳回
            return;
        File file = new File(skinPluginPath);
        if (!file.exists()) return;//如果插件檔案不存在,則傳回
        try {
            loadPlugin(skinPluginPath, skinPluginPkg, mSuffix);
            mCurPluginPath = skinPluginPath;
            mCurPluginPackage = skinPluginPkg;
        } catch (Exception e) {
            mPrefUtils.clear();
            e.printStackTrace();
        }
    }
           

loadPlugin 加載插件資源,這個方法沒得說。要想加載sd卡中的皮膚資源就這個方法。

//加載插件資源,建議在非ui線程工作;該方法是通用的,必須的方法
    private void loadPlugin(String skinPath, String skinPkgName, String suffix) throws Exception {
        //獲得系統資源管理類執行個體
        AssetManager assetManager = AssetManager.class.newInstance();
        //獲得系統資源管理類下的添加資源路徑方法
        Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
        //調用該方法,并且傳入皮膚所在的路徑
        addAssetPath.invoke(assetManager, skinPath);
        //獲得resources對象,現在獲得的還是目前app内的
        Resources superRes = mContext.getResources();
        //獲得一個資源管理,顯示名額,擷取配置
        //建立一個新的資源對象的一組現有的資産管理公司的資産。
        mResources = new Resources(assetManager, superRes.getDisplayMetrics(), superRes.getConfiguration());
        mResourceManager = new ResourceManager(mResources, skinPkgName, suffix);
        isUsePlugin = true;
    }
           

clearPluginInfo清理插件資訊,即回複預設皮膚

//清理插件資訊
    private void clearPluginInfo() {
        mCurPluginPath = null;
        mCurPluginPackage = null;
        isUsePlugin = false;
        mSuffix = null;
        mPrefUtils.clear();
    }
    //清除所有插件
    public void removeAnySkin() {
        clearPluginInfo();
        notifyChangedListeners();//通知所有的view,修改皮膚
    }
    //更新插件資訊
    private void updatePluginInfo(String skinPluginPath, String pkgName, String suffix) {
        mPrefUtils.setPluginPath(skinPluginPath);
        mPrefUtils.setPluginPackage(pkgName);
        mPrefUtils.setAttrSuffix(suffix);
        mCurPluginPackage = pkgName;
        mCurPluginPath = skinPluginPath;
        mSuffix = suffix;
    }
           

getSkinViews/putSkinViews;存取放在map中的對應view資源集合

//傳入一個繼承了換膚接口的activity,并且傳回這個activity中所有的可以換膚的SkinView對象,獲得一個需要換膚的view
    //該map裡面存入的是整個app被記錄的需要換膚的view,傳入接口,傳回對應的實作了接口的view的所有資源對象
    public List<SkinView> getSkinViews(ISkinListener listener) {
        return mSkinViewMaps.get(listener);
    }

    //将每個皮膚接口的所有view的資源都儲存在map中,然後友善存取對iang
    public void putSkinViews(ISkinListener listener, List<SkinView> skinViews) {
        mSkinViewMaps.put(listener, skinViews);
    }
           

注冊/反注冊;傳入接口資訊,并儲存,用于全局換膚;傳入view,用于更換資源。

/**
     * 注冊換膚
     */
    public void register(final ISkinListener listener, final View view) {
        mActivities.add(listener);
        //這裡将注入皮膚的操作添加到隊列中執行,要不然會擷取不到
        view.post(new Runnable() {
            @Override
            public void run() {
                injectSkin(listener, view);
            }
        });
    }

    //反注冊
    public void unregister(ISkinListener listener) {
        mActivities.remove(listener);
        mSkinViewMaps.remove(listener);
    }
           

injectSkin注入皮膚;該方法為初始化的時候所調用的

//注入皮膚,在activity初始化的時候進行
    public void injectSkin(ISkinListener iSkinListener, View view) {
        List<SkinView> skinViews = new ArrayList<>();
        addSkinViews(view, skinViews);
        putSkinViews(iSkinListener, skinViews);
        for (SkinView skinView : skinViews) {
            skinView.apply();
        }
    }
           

遞歸傳入view的資源到skinview集合中

//遞歸添加view的資源到skinview結合中
    public void addSkinViews(View view, List<SkinView> skinViews) {
        SkinView skinView = getSkinView(view);
        if (skinView != null) skinViews.add(skinView);
        if (view instanceof ViewGroup) {
            ViewGroup container = (ViewGroup) view;
            int n = container.getChildCount();
            for (int i = ; i < n; i++) {
                View child = container.getChildAt(i);
                addSkinViews(child, skinViews);
            }
        }

    }
 //獲得該view下所有的資源屬性
    public SkinView getSkinView(View view) {
    //獲得每個view所附帶的标簽,這是關鍵所在,後面會講到如何給view指派tag和拿到view的tag
        Object tag = view.getTag(SkinContacts.SKIN_TAG);
        if (tag == null)
            tag = view.getTag();
        if (tag == null) return null;
        if (!(tag instanceof String)) return null;
        List<SkinAttr> skinAttrs = SkinAttrSupport.getSkinTags(tag.toString());
        if (!skinAttrs.isEmpty()) {
            changeViewTag(view);
            return new SkinView(view, skinAttrs);
        }
        return null;
    }

      //修改view所持有的tag标簽
    private void changeViewTag(View view) {
        Object tag = view.getTag(SkinContacts.SKIN_TAG);
        if (tag == null) {
            tag = view.getTag();
            view.setTag(SkinContacts.SKIN_TAG, tag);
            //view.setTag(null);//我不知道為什麼鴻洋大神最後要在添加了tag後又置空,我把它注釋了,不影響運作。
        }
    }
           

改變皮膚的方法changeskin

/**
     * 應用内換膚,傳入資源差別的字尾
     */
    public void changeSkin(String suffix) {
        clearPluginInfo();
        mSuffix = suffix;
        mPrefUtils.setAttrSuffix(suffix);
        notifyChangedListeners();
    }
 /**
     * 更換插件皮膚,預設為""
     *
     * @param skinPluginPath
     * @param skinPluginPkg
     * @param callback
     */
    public void changeSkin(final String skinPluginPath, final String skinPluginPkg, ISkinCallback callback) {
        changeSkin(skinPluginPath, skinPluginPkg, "", callback);
    }


 /**
     * 根據suffix選擇插件内某套皮膚
     *
     * @param skinPluginPath
     * @param skinPluginPkg
     * @param suffix
     * @param callback
     */
    public void changeSkin(final String skinPluginPath, final String skinPluginPkg, final String suffix, final ISkinCallback callback) {
        if (callback == null) return;

        callback.onStart();

        try {
            checkPluginParamsThrow(skinPluginPath, skinPluginPkg);
        } catch (IllegalArgumentException e) {
            callback.onError(new RuntimeException("checkPlugin occur error"));
            return;
        }

        new AsyncTask<Void, Void, Integer>() {
            @Override
            protected Integer doInBackground(Void... params) {
                try {
                    loadPlugin(skinPluginPath, skinPluginPkg, suffix);
                    return ;
                } catch (Exception e) {
                    e.printStackTrace();
                    return ;
                }
            }

            @Override
            protected void onPostExecute(Integer res) {
                if (res == ) {
                    callback.onError(new RuntimeException("loadPlugin occur error"));
                    return;
                }
                try {
                    updatePluginInfo(skinPluginPath, skinPluginPkg, suffix);
                    notifyChangedListeners();
                    callback.onComplete();
                } catch (Exception e) {
                    e.printStackTrace();
                    callback.onError(e);
                }
            }
        }.execute();
    }
           

notifyChangedListeners發出換膚的通知

//通知所有注冊過的view進行換膚
    public void notifyChangedListeners() {
        for (ISkinListener listener : mActivities) {
            apply(listener);
        }
    }
           

ok。SkinManager的方法就這些。它主要是執行個體化了資料總管,得到資源執行個體,并且去調用skin檔案夾下的更換皮膚的流程。

接下來我們寫一個抽象activity,實作ISkinListener接口,然後供外部去調用。

public abstract class BaseSkinActivity extends AppCompatActivity implements ISkinListener {

    //抽象方法傳入目前activity 的資源id
    protected abstract int setContentView();

    protected abstract void initview();


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(setContentView());
        //獲得目前activity的最外層view
        ViewGroup content = (ViewGroup) findViewById(android.R.id.content);
        //在activity建立的時候添加換膚的監聽
        //鴻洋大神寫的是直接傳入一個activity執行個體,我這裡稍微的修改了一下,并不算是優化,隻能說友善限制和統一管理
        //我這裡不再傳入activity對象,而是傳入實作了這個接口的activity對象和它的view
        //目的就是為了當我們繼承這個抽象類的時候,其他地方如果用到了ViewGroup的話,我們也不用再次擷取了
        SkinManagerOutdated.getInstance().register(this, content);
        initview();
    }

    /**
     * 當程式要求app修改皮膚的時候就會調用這個方法
     * 該方法裡面寫實時的修改皮膚的方法
     */
    @Override
    public void onSkinChanged() {
        //因為在onCreateView方法中已經将參數聲明了,是以這裡直接調用
        //如果皮膚管理器向所有注冊過的view發出通知,改變皮膚;則就會出發該方法
        //而該方法中寫的就是單個的修改view的皮膚
        SkinManagerOutdated.getInstance().apply(this);
    }

    /**
     * 當activity被finish的時候我們從換膚集合中移除這個activity,下一次換膚就不會對該activity産生作用了
     */
    @Override
    protected void onDestroy() {
        super.onDestroy();
        //反注冊,從皮膚注冊集合中移除,以免造成記憶體洩漏
        SkinManagerOutdated.getInstance().unregister(this);
    }
}
           

這樣的話,隻要我們的activity繼承了這個baseskinactivity的話就可以實作換膚了,并且是無縫換膚和支援插件或者app内部換膚。

這裡有幾點需要注意:

1:插件換膚要聲明讀寫sd卡權限,要不然擷取不到插件。

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
           

2:每個需要換膚的資源需要在xml中指明tag。例如:

<TextView
        android:id="@+id/id_tv_tip"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBottom="@id/id_iv_icon"
        android:layout_toRightOf="@id/id_iv_icon"
        android:text="鴻洋"
        android:tag="skin:skin_item_text_color:textColor"
        android:textColor="@color/skin_item_text_color" />
           

其中tag就是用于換膚的辨別;有嚴格的格式規定。

以skin:開頭 +資源名+:+資源類型

skin:skin:skin_item_text_color:textColor

這是鴻洋大神寫的關于tag方式的換膚。

因為篇幅太長,我會在第二篇博文中繼續解析。這一片主要分析了tag方式的換膚。下一篇就分析一下侵入式換膚和tag式的差別以及不好之處。