网易云音乐动态式换肤框架分析与手写实现<二>

换肤处理:

采集控件:

继续接着上一次https://www.cnblogs.com/webor2006/p/12201532.html的代码进行编写,上次已经在View工厂那块对View的创建进行处理了,接下来则需要处理View对应的具体属性,这也需要涉及到一个属性的过滤,这里新建一个类专门用来处理View的属性:

那哪些属性是需要进行换肤的呢?下面直接贴出来,如果实际项目中还有其它的可以再添加进去既可:

接下来则需要处理这个load的过滤方法了,当然就得进行属性的遍历了:

注意,此时还木有拿到属性的值,如下:

所以此时需要拿到具体的属性值,此时需要引进一个工具类:

public class SkinThemeUtils {

    public static int[] getResId(Context context, int[] attrs){
        int[] ints = new int[attrs.length];
        TypedArray typedArray = context.obtainStyledAttributes(attrs);
        for (int i = 0; i < typedArray.length(); i++) {
            ints[i] =  typedArray.getResourceId(i, 0);
        }
        typedArray.recycle();
        return ints;
    }
}

为啥通过属性ID获得的值是一个数组呢?因为实际有可能一个属性里面对应多个值,好咱们应用一下些工具方法:

好,还有一种情况,就是我们平常经常使用的:

也就是"@+resId"的形式,此时就比较简单了:

此时如果过滤到了相关的属性了,则应该缓存下来,待之后换肤时用:

另外还得做一个缓存,就是最终应用属性时肯定是应用到相关的View上,所以咱们需要将当前过滤之后的View也缓存一下,如下:

public class SkinAttribute {

    private static final List<String> ATTRIBUTES = new ArrayList<>();
    private List<SkinView> skinViews = new ArrayList<>();

    static {
        ATTRIBUTES.add("background");
        ATTRIBUTES.add("src");

        ATTRIBUTES.add("textColor");
        ATTRIBUTES.add("drawableLeft");
        ATTRIBUTES.add("drawableTop");
        ATTRIBUTES.add("drawableRight");
        ATTRIBUTES.add("drawableBottom");

        ATTRIBUTES.add("skinTypeface");
    }


    public void load(View view, AttributeSet attrs) {
        List<SkinPain> skinPains = new ArrayList<>();
        for (int i = 0; i < attrs.getAttributeCount(); i++) {
            //获取属性名字
            String attributeName = attrs.getAttributeName(i);
            if (ATTRIBUTES.contains(attributeName)) {
                //获取属性对应的值
                String attributeValue = attrs.getAttributeValue(i);
                if (attributeValue.startsWith("#")) {
                    //android:textColor="#ffffff",像这种写死的色值肯定是不希望进行动态换肤的
                    continue;
                }
                int resId;
                //判断前缀字符串 是否是"?"
                if (attributeValue.startsWith("?")) {
                    //系统属性值,比如:android:textColor="?colorAccent",则属性ID的名称是从属性值下标1的位置开始,注意:它在R中是一个int类型的
                    int attrId = Integer.parseInt(attributeValue.substring(1));
                    resId = SkinThemeUtils.getResId(view.getContext(), new int[]{attrId})[0];
                } else {
                    //android:textColor="@color/cardview_dark_background",是这种场景
                    resId = Integer.parseInt(attributeValue.substring(1));
                }
                if (resId != 0) {
                    SkinPain skinPain = new SkinPain(attributeName, resId);
                    skinPains.add(skinPain);
                }
            }
        }

        if (!skinPains.isEmpty()) {
            SkinView skinView = new SkinView(view, skinPains);
            skinViews.add(skinView);
        }
    }

    static class SkinView {
        View view;
        List<SkinPain> skinPains;

        public SkinView(View view, List<SkinPain> skinPains) {
            this.view = view;
            this.skinPains = skinPains;
        }
    }

    static class SkinPain {
        String attributeName;
        int resId;

        public SkinPain(String attributeName, int resId) {
            this.attributeName = attributeName;
            this.resId = resId;
        }
    }
}

好,有一个疑问?难道这块的方法是解析到一个View就会进行回调么?

 

在继续往下编写之前,咱们打个日志来看一下:

一运行就抛异常了。。啥原因呢?从咱们调用的地方来查找:

而这个字段是一个私有的,所以咱们在设置工厂之前应该将这个变量通过反射将其改为false才行,所以修复如下:

好,此时再运行,只以MainActivity界面加载为例,我们所关心的日志输出如下:

2020-01-26 16:59:11.264 14778-14778/com.android.changskin E/cexo: SkinLayoutFactory.onCreateView():android.widget.LinearLayout{32515c V.E...... ......I. 0,0-0,0}-->LinearLayout
2020-01-26 16:59:11.269 14778-14778/com.android.changskin E/cexo: SkinLayoutFactory.onCreateView():android.view.ViewStub{1a8cd3a G.E...... ......I. 0,0-0,0 #102018d android:id/action_mode_bar_stub}-->ViewStub
2020-01-26 16:59:11.271 14778-14778/com.android.changskin E/cexo: SkinLayoutFactory.onCreateView():android.widget.FrameLayout{8433deb V.E...... ......I. 0,0-0,0 #1020002 android:id/content}-->FrameLayout
2020-01-26 16:59:11.397 14778-14778/com.android.changskin E/cexo: SkinLayoutFactory.onCreateView():android.widget.LinearLayout{7f8aebf V.E...... ......I. 0,0-0,0}-->LinearLayout
2020-01-26 16:59:11.405 14778-14778/com.android.changskin E/cexo: SkinLayoutFactory.onCreateView():android.view.ViewStub{4d8a8d5 G.E...... ......I. 0,0-0,0 #102018d android:id/action_mode_bar_stub}-->ViewStub
2020-01-26 16:59:11.406 14778-14778/com.android.changskin E/cexo: SkinLayoutFactory.onCreateView():android.widget.FrameLayout{d6ef8ea V.E...... ......I. 0,0-0,0 #1020002 android:id/content}-->FrameLayout
2020-01-26 16:59:11.423 14778-14778/com.android.changskin E/cexo: SkinLayoutFactory.onCreateView():androidx.appcompat.widget.ActionBarOverlayLayout{9bbb9b6 V.E...... ......I. 0,0-0,0 #7f080033 app:id/decor_content_parent}-->androidx.appcompat.widget.ActionBarOverlayLayout
2020-01-26 16:59:11.426 14778-14778/com.android.changskin E/cexo: SkinLayoutFactory.onCreateView():androidx.appcompat.widget.ContentFrameLayout{bebe24 V.E...... ......I. 0,0-0,0 #7f080007 app:id/action_bar_activity_content}-->androidx.appcompat.widget.ContentFrameLayout
2020-01-26 16:59:11.440 14778-14778/com.android.changskin E/cexo: SkinLayoutFactory.onCreateView():androidx.appcompat.widget.ActionBarContainer{2fe1d8d V.ED..... ......I. 0,0-0,0 #7f080008 app:id/action_bar_container}-->androidx.appcompat.widget.ActionBarContainer
2020-01-26 16:59:11.474 14778-14778/com.android.changskin E/cexo: SkinLayoutFactory.onCreateView():androidx.appcompat.widget.Toolbar{380c5af V.E...... ......I. 0,0-0,0 #7f080006 app:id/action_bar}-->androidx.appcompat.widget.Toolbar
2020-01-26 16:59:11.488 14778-14778/com.android.changskin E/cexo: SkinLayoutFactory.onCreateView():androidx.appcompat.widget.ActionBarContextView{81e0dcb G.E...... ......I. 0,0-0,0 #7f08000e app:id/action_context_bar}-->androidx.appcompat.widget.ActionBarContextView
2020-01-26 16:59:11.517 14778-14778/com.android.changskin E/cexo: SkinLayoutFactory.onCreateView():android.widget.LinearLayout{5fdbba7 V.E...... ......ID 0,0-0,0}-->LinearLayout
2020-01-26 16:59:11.527 14778-14778/com.android.changskin E/cexo: SkinLayoutFactory.onCreateView():android.widget.Button{5b1c4dd VFED..C.. ......I. 0,0-0,0}-->Button
2020-01-26 16:59:11.544 14778-14778/com.android.changskin E/cexo: SkinLayoutFactory.onCreateView():com.android.changskin.widget.MyTabLayout{661a79e VFED..... ......I. 0,0-0,0 #7f0800a6 app:id/tabLayout}-->com.android.changskin.widget.MyTabLayout
2020-01-26 16:59:11.554 14778-14778/com.android.changskin E/cexo: SkinLayoutFactory.onCreateView():androidx.viewpager.widget.ViewPager{adab27f VFED..... ......I. 0,0-0,0 #7f0800c5 app:id/viewPager}-->androidx.viewpager.widget.ViewPager
2020-01-26 16:59:11.568 14778-14778/com.android.changskin E/cexo: SkinLayoutFactory.onCreateView():android.widget.ImageView{94db295 V.ED..... ......I. 0,0-0,0}-->ImageView
2020-01-26 16:59:11.572 14778-14778/com.android.changskin E/cexo: SkinLayoutFactory.onCreateView():android.widget.TextView{cd9d738 V.ED..... ......ID 0,0-0,0}-->TextView
2020-01-26 16:59:11.578 14778-14778/com.android.changskin E/cexo: SkinLayoutFactory.onCreateView():android.widget.ImageView{5a3e311 V.ED..... ......I. 0,0-0,0}-->ImageView
2020-01-26 16:59:11.580 14778-14778/com.android.changskin E/cexo: SkinLayoutFactory.onCreateView():android.widget.TextView{61d0c76 V.ED..... ......ID 0,0-0,0}-->TextView
2020-01-26 16:59:11.584 14778-14778/com.android.changskin E/cexo: SkinLayoutFactory.onCreateView():android.widget.ImageView{d6e7277 V.ED..... ......I. 0,0-0,0}-->ImageView
2020-01-26 16:59:11.586 14778-14778/com.android.changskin E/cexo: SkinLayoutFactory.onCreateView():android.widget.TextView{df12ee4 V.ED..... ......ID 0,0-0,0}-->TextView
2020-01-26 16:59:11.657 14778-14778/com.android.changskin E/cexo: SkinLayoutFactory.onCreateView():android.widget.LinearLayout{95344e V.E...... ......I. 0,0-0,0}-->LinearLayout
2020-01-26 16:59:11.679 14778-14778/com.android.changskin E/cexo: SkinLayoutFactory.onCreateView():androidx.recyclerview.widget.RecyclerView{7cb396f VFED..... ......I. 0,0-0,0 #7f080078 app:id/rel_view}-->androidx.recyclerview.widget.RecyclerView
2020-01-26 16:59:11.681 14778-14778/com.android.changskin E/cexo: SkinLayoutFactory.onCreateView():android.widget.TextView{7e1767c V.ED..... ......ID 0,0-0,0}-->TextView
2020-01-26 16:59:11.694 14778-14778/com.android.changskin E/cexo: SkinLayoutFactory.onCreateView():android.widget.LinearLayout{6fbfd8b V.E...... ......I. 0,0-0,0}-->LinearLayout
2020-01-26 16:59:11.696 14778-14778/com.android.changskin E/cexo: SkinLayoutFactory.onCreateView():android.widget.TextView{50d7e68 V.ED..... ......ID 0,0-0,0}-->TextView
2020-01-26 16:59:11.699 14778-14778/com.android.changskin E/cexo: SkinLayoutFactory.onCreateView():android.widget.LinearLayout{b2ae767 V.E...... ......I. 0,0-0,0}-->LinearLayout
2020-01-26 16:59:11.701 14778-14778/com.android.changskin E/cexo: SkinLayoutFactory.onCreateView():android.widget.TextView{4701914 V.ED..... ......ID 0,0-0,0 #7f0800c0 app:id/tv_name}-->TextView
2020-01-26 16:59:11.706 14778-14778/com.android.changskin E/cexo: SkinLayoutFactory.onCreateView():android.widget.LinearLayout{b9467b2 V.E...... ......I. 0,0-0,0}-->LinearLayout
2020-01-26 16:59:11.708 14778-14778/com.android.changskin E/cexo: SkinLayoutFactory.onCreateView():android.widget.TextView{b749303 V.ED..... ......ID 0,0-0,0 #7f0800c0 app:id/tv_name}-->TextView
2020-01-26 16:59:11.711 14778-14778/com.android.changskin E/cexo: SkinLayoutFactory.onCreateView():android.widget.LinearLayout{50230b9 V.E...... ......I. 0,0-0,0}-->LinearLayout
2020-01-26 16:59:11.713 14778-14778/com.android.changskin E/cexo: SkinLayoutFactory.onCreateView():android.widget.TextView{4dd4cfe V.ED..... ......ID 0,0-0,0 #7f0800c0 app:id/tv_name}-->TextView
2020-01-26 16:59:11.716 14778-14778/com.android.changskin E/cexo: SkinLayoutFactory.onCreateView():android.widget.LinearLayout{7ad76ac V.E...... ......I. 0,0-0,0}-->LinearLayout
2020-01-26 16:59:11.717 14778-14778/com.android.changskin E/cexo: SkinLayoutFactory.onCreateView():android.widget.TextView{40c1f75 V.ED..... ......ID 0,0-0,0 #7f0800c0 app:id/tv_name}-->TextView
2020-01-26 16:59:11.720 14778-14778/com.android.changskin E/cexo: SkinLayoutFactory.onCreateView():android.widget.LinearLayout{9cb5f7b V.E...... ......I. 0,0-0,0}-->LinearLayout
2020-01-26 16:59:11.722 14778-14778/com.android.changskin E/cexo: SkinLayoutFactory.onCreateView():android.widget.TextView{6d15198 V.ED..... ......ID 0,0-0,0 #7f0800c0 app:id/tv_name}-->TextView
2020-01-26 16:59:11.725 14778-14778/com.android.changskin E/cexo: SkinLayoutFactory.onCreateView():android.widget.LinearLayout{cfbfdd6 V.E...... ......I. 0,0-0,0}-->LinearLayout
2020-01-26 16:59:11.727 14778-14778/com.android.changskin E/cexo: SkinLayoutFactory.onCreateView():android.widget.TextView{8267857 V.ED..... ......ID 0,0-0,0 #7f0800c0 app:id/tv_name}-->TextView
2020-01-26 16:59:11.731 14778-14778/com.android.changskin E/cexo: SkinLayoutFactory.onCreateView():android.widget.LinearLayout{515c82d V.E...... ......I. 0,0-0,0}-->LinearLayout
2020-01-26 16:59:11.733 14778-14778/com.android.changskin E/cexo: SkinLayoutFactory.onCreateView():android.widget.TextView{d122162 V.ED..... ......ID 0,0-0,0 #7f0800c0 app:id/tv_name}-->TextView
2020-01-26 16:59:11.736 14778-14778/com.android.changskin E/cexo: SkinLayoutFactory.onCreateView():android.widget.LinearLayout{c64bbb0 V.E...... ......I. 0,0-0,0}-->LinearLayout
2020-01-26 16:59:11.737 14778-14778/com.android.changskin E/cexo: SkinLayoutFactory.onCreateView():android.widget.TextView{233ba29 V.ED..... ......ID 0,0-0,0 #7f0800c0 app:id/tv_name}-->TextView
2020-01-26 16:59:11.740 14778-14778/com.android.changskin E/cexo: SkinLayoutFactory.onCreateView():android.widget.LinearLayout{9021b4f V.E...... ......I. 0,0-0,0}-->LinearLayout
2020-01-26 16:59:11.743 14778-14778/com.android.changskin E/cexo: SkinLayoutFactory.onCreateView():android.widget.TextView{851e2dc V.ED..... ......ID 0,0-0,0 #7f0800c0 app:id/tv_name}-->TextView
2020-01-26 16:59:11.746 14778-14778/com.android.changskin E/cexo: SkinLayoutFactory.onCreateView():android.widget.LinearLayout{7227aba V.E...... ......I. 0,0-0,0}-->LinearLayout
2020-01-26 16:59:11.748 14778-14778/com.android.changskin E/cexo: SkinLayoutFactory.onCreateView():android.widget.TextView{ef1d6b V.ED..... ......ID 0,0-0,0 #7f0800c0 app:id/tv_name}-->TextView

对比下MainActivity的布局文件:

确实是对于布局中的每一个元素都会执行到布局工厂的onCreateView()中,所以在这里面来写元素过滤妥妥的。好,继续往下编写,过滤了元素之后,接下来则需要进行换肤应用了,咱们可以写在这:

static class SkinView {
        View view;
        List<SkinPain> skinPains;

        public SkinView(View view, List<SkinPain> skinPains) {
            this.view = view;
            this.skinPains = skinPains;
        }

        public void applySkin() {
            for (SkinPain skinPair : skinPains) {
                Drawable left = null, top = null, right = null, bottom = null;
                switch (skinPair.attributeName) {
                    case "background":
                        Object background = SkinResources.getInstance().getBackground(
                                skinPair.resId);
                        //Color
                        if (background instanceof Integer) {
                            view.setBackgroundColor((Integer) background);
                        } else {
                            ViewCompat.setBackground(view, (Drawable) background);
                        }
                        //摸摸唱
                        break;
                    case "src":
                        background = SkinResources.getInstance().getBackground(skinPair
                                .resId);
                        if (background instanceof Integer) {
                            ((ImageView) view).setImageDrawable(new ColorDrawable((Integer)
                                    background));
                        } else {
                            ((ImageView) view).setImageDrawable((Drawable) background);
                        }
                        break;
                    case "textColor":
                        ((TextView) view).setTextColor(SkinResources.getInstance().getColorStateList
                                (skinPair.resId));
                        break;
                    case "drawableLeft":
                        left = SkinResources.getInstance().getDrawable(skinPair.resId);
                        break;
                    case "drawableTop":
                        top = SkinResources.getInstance().getDrawable(skinPair.resId);
                        break;
                    case "drawableRight":
                        right = SkinResources.getInstance().getDrawable(skinPair.resId);
                        break;
                    case "drawableBottom":
                        bottom = SkinResources.getInstance().getDrawable(skinPair.resId);
                        break;
                    default:
                        break;
                }
                if (null != left || null != right || null != top || null != bottom) {
                    ((TextView) view).setCompoundDrawablesWithIntrinsicBounds(left, top, right,
                            bottom);
                }
            }
        }
    }

其中SkinResources代码如下,目前没有涉及到换肤的逻辑,不过未来会有:

public class SkinResources {

    private static SkinResources instance;

    private Resources mAppResources;

    private SkinResources(Context context) {
        mAppResources = context.getResources();
    }

    public static void init(Context context) {
        if (instance == null) {
            synchronized (SkinResources.class) {
                if (instance == null) {
                    instance = new SkinResources(context);
                }
            }
        }
    }

    public static SkinResources getInstance() {
        return instance;
    }


    public int getColor(int resId) {
        return mAppResources.getColor(resId);
    }

    public ColorStateList getColorStateList(int resId) {
        return mAppResources.getColorStateList(resId);
    }

    public Drawable getDrawable(int resId) {
        //如果有皮肤  isDefaultSkin false 没有就是true
        return mAppResources.getDrawable(resId);
    }

    /**
     * 可能是Color 也可能是drawable
     */
    public Object getBackground(int resId) {
        String resourceTypeName = mAppResources.getResourceTypeName(resId);

        if (resourceTypeName.equals("color")) {
            return getColor(resId);
        } else {
            // drawable
            return getDrawable(resId);
        }
    }
}

关于这个类中的方法比较好理解,就是利用资源管理器来加载相关的东东,不多解释了,然后该资源类得进行初始化一下:

最后咱们还得在采集控件处调用一下这个换肤的方法:

加载皮肤包:

对于皮肤包长啥样,其实它就是一个apk,里面就是定义了一些跟UI相关的资源,木有任何代码,如:

其实有点像插件的感觉,所以这种换肤确实并非人人都能想到,还是比较高级滴,接下来就得来加载皮肤包并且应用到咱们app中达到动态换肤的目的,这里首先得要定义一个SharedPreferences,因为实际可能会有多种风格的皮肤包,用它得记录一下当前使用的哪套皮肤:

 

public class SkinPreference {
    private static final String SKIN_SHARED = "skins";

    private static final String KEY_SKIN_PATH = "skin-path";
    private static SkinPreference instance;
    private final SharedPreferences pref;

    public static void init(Context context) {
        if (instance == null) {
            synchronized (SkinPreference.class) {
                if (instance == null) {
                    instance = new SkinPreference(context.getApplicationContext());
                }
            }
        }
    }

    public static SkinPreference getInstance() {
        return instance;
    }

    private SkinPreference(Context context) {
        pref = context.getSharedPreferences(SKIN_SHARED, Context.MODE_PRIVATE);
    }

    public void setSkin(String skinPath) {
        pref.edit().putString(KEY_SKIN_PATH, skinPath).apply();
    }

    public String getSkin() {
        return pref.getString(KEY_SKIN_PATH, null);
    }

}

然后在Application中进行初始化一下:

好,接下来则需要处理加载皮肤的逻辑呢?怎么能够从离线的皮肤的apk中来加载样式呢?这里先看一张Android资源加载的流程图:

也就是最终是通过AssetManager来进行加载的,那有个问题,这个不是用来加载Assets目录中的文件的嘛,很明显咱们这种方案得从服务器将皮肤的apk下载到手机的sdcard上来,然后再来加载,难道AssetManager也能加载sdcard上的文件?是的~~它里面有一个私有的方法可以进行文件目录的设置:

所以这里用反射的方式来调用:

说到调用hide的方法,就会想到Android9.0对这块的限制,关于这块这里就不探究了,待之后再研究,接下来则需要构建皮肤包对应的Resources对象,跟加载插件中的资源差不多,具体如下:

接着则需要应用一下皮肤,很显然如果是默认皮肤和非默认皮肤的Resources对象肯定是不一样的,所以,咱们得要在SkinResources根据咱们传进来的皮肤情况来用变量区分一下,如下:

此时这个类中的各个获取方法也得根据这个isDefaultSkin字段来进行判断处理了,直接贴代码了:

public class SkinResources {

    private static SkinResources instance;

    private Resources mAppResources;
    private Resources skinResources;
    private String skinPkgName;
    private boolean isDefaultSkin = true;

    private SkinResources(Context context) {
        mAppResources = context.getResources();
    }

    public static void init(Context context) {
        if (instance == null) {
            synchronized (SkinResources.class) {
                if (instance == null) {
                    instance = new SkinResources(context);
                }
            }
        }
    }

    public static SkinResources getInstance() {
        return instance;
    }

    public void applySkin(Resources resources, String pkgName) {
        skinResources = resources;
        skinPkgName = pkgName;
        //是否使用默认皮肤
        isDefaultSkin = TextUtils.isEmpty(pkgName) || resources == null;
    }


    public int getColor(int resId) {
        if (isDefaultSkin) {
            return mAppResources.getColor(resId);
        }
        int skinId = getIdentifier(resId);
        if (skinId == 0) {
            return mAppResources.getColor(resId);
        }
        return skinResources.getColor(skinId);
    }

    public ColorStateList getColorStateList(int resId) {
        if (isDefaultSkin) {
            return mAppResources.getColorStateList(resId);
        }
        int skinId = getIdentifier(resId);
        if (skinId == 0) {
            return mAppResources.getColorStateList(resId);
        }
        return skinResources.getColorStateList(skinId);
    }

    public Drawable getDrawable(int resId) {
        if (isDefaultSkin) {
            return mAppResources.getDrawable(resId);
        }
        int skinId = getIdentifier(resId);
        if (skinId == 0) {
            return mAppResources.getDrawable(resId);
        }
        return skinResources.getDrawable(skinId);
    }

    /**
     * 可能是Color 也可能是drawable
     */
    public Object getBackground(int resId) {
        String resourceTypeName = mAppResources.getResourceTypeName(resId);

        if (resourceTypeName.equals("color")) {
            return getColor(resId);
        } else {
            // drawable
            return getDrawable(resId);
        }
    }

    public int getIdentifier(int resId) {
        if (isDefaultSkin) {
            return resId;
        }
        //在皮肤包中不一定就是 当前程序的 id
        //获取对应id 在当前的名称 colorPrimary
        //R.drawable.ic_launcher
        String resName = mAppResources.getResourceEntryName(resId);//ic_launcher   /colorPrimaryDark
        String resType = mAppResources.getResourceTypeName(resId);//drawable
        int skinId = skinResources.getIdentifier(resName, resType, skinPkgName);
        return skinId;
    }
}

其中换肤的关键代码为getIdentifier()方法,好,接下来还得处理另一个条件分支,也就是默认皮肤包的情况:

这个代码就比较简单了,直接将皮肤恢复成默认的既可:

以上就是加载皮肤包的代码,不用记,有个大概的印象既可。

处理皮肤改变的通知:

好,目前加载完皮肤包之后,那接下来要干嘛呢?想一下,一个APP有这么多页面,那很显然得通知每个界面进行皮肤的更换,用广播?用EventBus?其实都可以,但是这种处理方式不太优雅,这里采用观察者模式【具体JDK的观察者的细节就不多说了】,具体做法如下:

先定义一个观察者:

 

然后当被观察者有变化时,则此时就会回调这个方法:

此时applySkin()的方法的实现为:

好,接下来则需要定义观察者,那在哪定义比较合适呢?很显然应该是在下载皮肤包成功之后需要给观察者一个通知,所以,咱们应该是在这:

然后,还需要进行注册一下,在这:

但是想一个问题,很明显在所有打开的Activity都得要注册观察者,在Activity退出时肯定得注册这个监听,所以此时咱们得缓存一下,具体处理如下:

好,关于换肤的逻辑代码就先写到这,先来验证一下是否好使。

制作皮肤包看看运行效果:

咱们先回到换肤界面来调用一下换肤:

也就是它:

代码修改如下:

从sdcard上来加载皮肤包,至于皮肤包怎么做稍后再提一下,先加一下sdcard的权限:

说到权限问题,由于在6.0以后的权限是需要主动使用时申请的,而目前DEMO的targetSdkVersion=29,也就是用的Android10,所以需要特别注意,不主动申请的话默认是没有sdcard读写权限的,这里为了方便就不加权限申请的代码了,主动在应用设置里来将权限打开,如下:

咱们手动改为“允许”既可:

好,接下来则需要制作皮肤包了,这里新建一个Module,专门用来制作皮肤包用的:

下面来弄几个换肤的地方,商业项目得根据自身实际的情况按规则来制定皮肤包既可,如下:

①、colors.xml:

②、t_window_bg.jpg:https://files.cnblogs.com/files/webor2006/t_window_bg.jpg.zip

③、text_drawable_left.png:

它是指换肤界面的这个图片:

④、两个selector:

selector_color_test.xml:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:color="@color/colorPrimary" android:state_pressed="true"/>
    <item android:color="@color/colorAccent"/>
</selector>

它主要是作用于界面的这块:

tab_selector.xml:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:color="@color/tabSelectedTextColor" android:state_selected="true"/>
    <item android:color="@color/colorAccent"/>
</selector>

它主要是作用于:

好,暂且只换肤这么些,然后将这资源都放好之后,接下来直接编译一个包出来:

好,此时将其放到咱们手机的sdcard根目录下:

好,接下来咱们运行看一下:

嗯,如我们的预期,成功得到的换肤,这就是整个换肤的一个实现思路,还是比较复杂的,为了方便后续的实验,这里先将皮肤的还原给加上,比较简单:

试下效果:

嗯~~效果杠杠滴,不过目前只是一个换肤的初步,还有很有细节上的东东待处理,下次继续。

posted on 2020-01-20 16:50  cexo  阅读(764)  评论(0编辑  收藏  举报

导航