zoukankan      html  css  js  c++  java
  • 网易云音乐动态式换肤框架分析与手写实现<二>

    换肤处理:

    采集控件:

    继续接着上一次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根目录下:

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

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

    试下效果:

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

  • 相关阅读:
    phpexcel 相关知识
    php 相关的设置
    linux md5sum 常用用法
    mysql 修改group_concat的限制(row 20000 was cut by group_concat())
    mysql设置最大连接数 max_connections
    Mysql 需要修改的一些配置
    mysql设置远程访问,解决不能通过ip登录的问题(MySQL Error Number 2003,Can't connect to MySQL server )
    mysql 用户权限管理的粗略认识
    文字图片在wps中清晰化方法
    Linux 如何释放Hugepage 占用的内存
  • 原文地址:https://www.cnblogs.com/webor2006/p/12218582.html
Copyright © 2011-2022 走看看