简介

前面两个项目

  1. android架构师之路——修改app字体讲解了LayoutInflaterCompat.setFactory2的使用方式
  2. android架构师之路——以修改一个图片文件方式讲解APP换肤原理讲解了换肤的原理

现在我们就在具体实践中,讲解一下更多的使用场景

项目结构

  • app:主项目目录
  • app_skin:资源项目,生成的apk放在sdcard目录
  • lib_skin:lib包,主要的换肤代码就在这里

实现方式

主入口MyApplication

public class MyApplication extends Application {    @Override    public void onCreate() {        super.onCreate();        SkinManager.init(this);    }}

SkinManager是实现换肤的管理类方法

  • skinManager构造方法 
    private SkinManager(Application application) {        mContext = application;        //共享首选项 用于记录当前使用的皮肤        SkinPreference.init(application);        //资源管理类 用于从 app/皮肤 中加载资源        SkinResources.init(application);        //注册Activity生命周期        skinActivityLifecycle = new SkinActivityLifecycle();        application.registerActivityLifecycleCallbacks(skinActivityLifecycle);        //加载皮肤        loadSkin(SkinPreference.getInstance().getSkin());    }
  • 加载制定路径的资源文件
  /**     * 记载皮肤并应用     *     * @param skinPath 皮肤路径 如果为空则使用默认皮肤     */    public void loadSkin(String skinPath) {        if (TextUtils.isEmpty(skinPath)) {            //记录使用默认皮肤            SkinPreference.getInstance().setSkin("");            //清空资源管理器 皮肤资源属性            SkinResources.getInstance().reset();        } else {            try {                //反射创建AssetManager 与 Resource                AssetManager assetManager = AssetManager.class.newInstance();                //资源路径设置 目录或压缩包                Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);                addAssetPath.invoke(assetManager, skinPath);                Resources appResource = mContext.getResources();                //根据当前的显示与配置(横竖屏、语言等)创建Resources                Resources skinResource = new Resources(assetManager, appResource.getDisplayMetrics(), appResource.getConfiguration());                //记录                SkinPreference.getInstance().setSkin(skinPath);                //获取外部Apk(皮肤包) 包名                PackageManager mPm = mContext.getPackageManager();                PackageInfo info = mPm.getPackageArchiveInfo(skinPath, PackageManager.GET_ACTIVITIES);                String packageName = info.packageName;                SkinResources.getInstance().applySkin(skinResource, packageName);            } catch (Exception e) {                e.printStackTrace();            }        }        //通知采集的View 更新皮肤        //被观察者改变 通知所有观察者        setChanged();        notifyObservers(null);    }
SkinPreference主要用来记录资源文件地址
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 mPref;    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) {        mPref = context.getSharedPreferences(SKIN_SHARED, Context.MODE_PRIVATE);    }    public void setSkin(String skinPath) {        mPref.edit().putString(KEY_SKIN_PATH, skinPath).apply();    }    public String getSkin() {        return mPref.getString(KEY_SKIN_PATH, null);    }}
SkinResources皮肤资源加载类
  • 获得资源包的R文件中的id
    /**     * 获得资源的R文件中的id     * @param resId     * @return     */    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 = mSkinResources.getIdentifier(resName, resType, mSkinPkgName);        return skinId;    }
  • 获得资源APP的color
   public int getColor(int resId) {        if (isDefaultSkin) {            return mAppResources.getColor(resId);        }        int skinId = getIdentifier(resId);        if (skinId == 0) {            return mAppResources.getColor(resId);        }        return mSkinResources.getColor(skinId);    }
  • 获得drawable资源
    public Drawable getDrawable(int resId) {        //如果有皮肤  isDefaultSkin false 没有就是true        if (isDefaultSkin) {            return mAppResources.getDrawable(resId);        }        int skinId = getIdentifier(resId);        if (skinId == 0) {            return mAppResources.getDrawable(resId);        }        return mSkinResources.getDrawable(skinId);    }
  • 获得background资源,可能是Color 也可能是drawable
    public Object getBackground(int resId) {        String resourceTypeName = mAppResources.getResourceTypeName(resId);        if (resourceTypeName.equals("color")) {            return getColor(resId);        } else {            // drawable            return getDrawable(resId);        }    }
  • 获得string
    public String getString(int resId) {        try {            if (isDefaultSkin) {                return mAppResources.getString(resId);            }            int skinId = getIdentifier(resId);            if (skinId == 0) {                return mAppResources.getString(skinId);            }            return mSkinResources.getString(skinId);        } catch (Resources.NotFoundException e) {        }        return null;    }
  • 获得type
    public Typeface getTypeface(int resId) {        String skinTypefacePath = getString(resId);        if (TextUtils.isEmpty(skinTypefacePath)) {            return Typeface.DEFAULT;        }        try {            Typeface typeface;            if (isDefaultSkin) {                typeface = Typeface.createFromAsset(mAppResources.getAssets(), skinTypefacePath);                return typeface;            }            typeface = Typeface.createFromAsset(mSkinResources.getAssets(), skinTypefacePath);            return typeface;        } catch (RuntimeException e) {        }        return Typeface.DEFAULT;    }
SkinActivityLifecycle文件是activity生命周期管理类,对每一个activity进行处理,修改资源文件
public class SkinActivityLifecycle implements Application.ActivityLifecycleCallbacks {    HashMap factoryHashMap = new HashMap<>();    @Override    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {        /**         *  更新状态栏         */        SkinThemeUtils.updataStatusBarColor(activity);        //获得Activity的布局加载器        LayoutInflater layoutInflater = LayoutInflater.from(activity);        try {            //Android 布局加载器 使用 mFactorySet 标记是否设置过Factory            //如设置过抛出一次            //设置 mFactorySet 标签为false            Field mFactorySet = LayoutInflater.class.getDeclaredField("mFactorySet");            mFactorySet.setAccessible(true);            mFactorySet.setBoolean(layoutInflater, false);        } catch (Exception e) {            e.printStackTrace();        }        //添加自定义创建View 工厂        SkinLayoutFactory factory = new SkinLayoutFactory(activity);        layoutInflater.setFactory2(factory);        //注册观察者        SkinManager.getInstance().addObserver(factory);        factoryHashMap.put(activity, factory);    }    @Override    public void onActivityDestroyed(Activity activity) {        //删除观察者        SkinLayoutFactory remove = factoryHashMap.remove(activity);        SkinManager.getInstance().deleteObserver(remove);    }}
SkinLayoutFactory替换资源文件,就在这里统一处理的
public class SkinLayoutFactory implements LayoutInflater.Factory2 , Observer{    private static final String[] mClassPrefixlist = {            "android.widget.",            "android.view.",            "android.webkit."    };    private static final Class[] mConstructorSignature =            new Class[]{Context.class, AttributeSet.class};    //记录对应View的构造函数    private static final HashMap> mConstructor =            new HashMap>();    // 当选择新皮肤后需要替换View与之对应的属性    // 页面属性管理器    private SkinAttribute skinAttribute;    private Activity activity;    public SkinLayoutFactory(Activity activity) {        this.activity = activity;        skinAttribute = new SkinAttribute();    }    /**     * 创建对应布局并返回     *     * @param parent  当前TAG 父布局     * @param name    在布局中的TAG 如:TextView, android.support.v7.widget.Toolbar     * @param context 上下文     * @param attrs   对应布局TAG中的属性 如: android:text android:src     * @return View    null则由系统创建     */    @Override    public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {        //换肤就是在需要时候替换 View的属性(src、background等)        //所以这里创建 View,从而修改View属性        // 反射 classLoader        View view = createViewFromTag(name, context, attrs);        // 自定义View        if(null ==  view){            view = createView(name, context, attrs);        } else  {            //筛选符合属性View            skinAttribute.load(view, attrs);        }        return view;    }    private View createViewFromTag(String name, Context context, AttributeSet attrs) {        //包含自定义控件        if (-1 != name.indexOf(".")) {            return null;        }        //        View view = null;        for (int i = 0; i < mClassPrefixlist.length; i++) {            view = createView(mClassPrefixlist[i] + name, context, attrs);            if(null != view){                break;            }        }        return view;    }    private View createView(String name, Context context, AttributeSet attrs) {        Constructor<? extends View> constructor = mConstructor.get(name);        if (constructor == null) {            try {                //通过全类名获取class                Class<? extends View> aClass = context.getClassLoader().loadClass(name).asSubclass(View.class);                //获取构造方法                constructor = aClass.getConstructor(mConstructorSignature);                mConstructor.put(name, constructor);            } catch (Exception e) {                e.printStackTrace();            }        }        if (null != constructor) {            try {                return constructor.newInstance(context, attrs);            } catch (Exception e) {                e.printStackTrace();            }        }        return null;    }    @Override    public View onCreateView(String name, Context context, AttributeSet attrs) {        return null;    }    @Override    public void update(Observable o, Object arg) {        SkinThemeUtils.updataStatusBarColor(activity);        //更换皮肤        skinAttribute.applySkin();    }}
SkinAttribute 换肤的实现方法
public class SkinAttribute {    private static final List mAttributes = new ArrayList<>();    static {        mAttributes.add("background");        mAttributes.add("src");        mAttributes.add("textColor");        mAttributes.add("drawableLeft");        mAttributes.add("drawableTop");        mAttributes.add("drawableRight");        mAttributes.add("drawableBottom");    }    private List skinViews = new ArrayList<>();    public void load(View view, AttributeSet attrs) {        List skinPains = new ArrayList<>();        for (int i = 0; i < attrs.getAttributeCount(); i++) {            //获取属性名字            String attributeName = attrs.getAttributeName(i);            if (mAttributes.contains(attributeName)) {                //获取属性对应的值                String attributeValue = attrs.getAttributeValue(i);                if (attributeValue.startsWith("#")) {                    continue;                }                int resId;                //判断前缀字符串 是否是"?"                //attributeValue  = "?2130903043"                if (attributeValue.startsWith("?")) {  //系统属性值                    //字符串的子字符串  从下标 1 位置开始                    int attrId = Integer.parseInt(attributeValue.substring(1));                    resId = SkinThemeUtils.getResId(view.getContext(), new int[]{attrId})[0];                } else {                    //@1234564                    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);            skinView.applySkin();            skinViews.add(skinView);        }    }    static class SkinView {        View view;        List skinPains;        public SkinView(View view, List skinPains) {            this.view = view;            this.skinPains = skinPains;        }        public void applySkin() {            applySkinSupport();            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;                    case "skinTypeface":                        applyTypeFace(SkinResources.getInstance().getTypeface(skinPair.resId));                        break;                    default:                        break;                }                if (null != left || null != right || null != top || null != bottom) {                    ((TextView) view).setCompoundDrawablesWithIntrinsicBounds(left, top, right, bottom);                }            }        }        //自定义View        private void applySkinSupport() {            if(view instanceof SkinViewSupport){                ((SkinViewSupport)view).applySkin();            }        }        private void applyTypeFace(Typeface typeface) {            if (view instanceof TextView) {                ((TextView) view).setTypeface(typeface);            }        }    }    static class SkinPain {        String attributeName;        int resId;        public SkinPain(String attributeName, int resId) {            this.attributeName = attributeName;            this.resId = resId;        }    }    /**     * 换皮肤     */    public void applySkin() {        for (SkinView mSkinView : skinViews) {            mSkinView.applySkin();        }    }}

这里需要重点提示一下自定义view的修改,需要implements SkinViewSupport实现applySkin方法

public class CircleView extends View implements SkinViewSupport {    private AttributeSet attrs;    //画笔    private Paint mTextPain;    //半径    private int radius;    private int corcleColorResId;    public CircleView(Context context) {        this(context, null, 0);    }    public CircleView(Context context, @Nullable AttributeSet attrs) {        this(context, attrs, 0);    }    public CircleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        this.attrs = attrs;        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleView);        corcleColorResId = typedArray.getResourceId(R.styleable.CircleView_corcleColor, 0);        typedArray.recycle();        mTextPain = new Paint();        mTextPain.setColor(getResources().getColor(corcleColorResId));        //开启抗锯齿,平滑文字和圆弧的边缘        mTextPain.setAntiAlias(true);        //设置文本位于相对于原点的中间        mTextPain.setTextAlign(Paint.Align.CENTER);    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        //获取宽度一半        int width = getWidth() / 2;        //获取高度一半        int height = getHeight() / 2;        //设置半径为宽或者高的最小值        radius = Math.min(width, height);        //利用canvas画一个圆        canvas.drawCircle(width, height, radius, mTextPain);    }    public void setCorcleColor(@ColorInt int color) {        mTextPain.setColor(color);        invalidate();    }    @Override    public void applySkin() {        if (corcleColorResId != 0) {            int color = SkinResources.getInstance().getColor(corcleColorResId);            setCorcleColor(color);        }    }}
SkinThemeUtils主要用来修改topbar的
/** * 修改状态栏 */public class SkinThemeUtils {    private static int[] APPCOMPAT_COLOR_PRIMARY_DARK_ATTRS = {            android.support.v7.appcompat.R.attr.colorPrimaryDark    };    private static int[] STATUSBAR_COLOR_ATTRS = {android.R.attr.statusBarColor, android.R.attr            .navigationBarColor};    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;    }    //替换状态栏    public static void updataStatusBarColor(Activity activity){        //5.0 以上才能修改        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {            return;        }        //获取statusBarColor与navigationBarColor  颜色值        int[] statusBarId = getResId(activity, STATUSBAR_COLOR_ATTRS);        //如果statusBarColor 配置颜色值, 就换肤        if(statusBarId[0] != 0){                activity.getWindow().setStatusBarColor(SkinResources.getInstance().getColor(statusBarId[0]));        } else {            //获取colorPrimaryDark            int resId = getResId(activity, APPCOMPAT_COLOR_PRIMARY_DARK_ATTRS)[0];            if(resId != 0){                activity.getWindow().setStatusBarColor(SkinResources.getInstance().getColor(resId));            }        }        if(statusBarId[1] != 0){            activity.getWindow().setNavigationBarColor(SkinResources.getInstance().getColor(statusBarId[1]));        }    }}

x项目代码没有贴出完整,只贴出了重要几个,具体可以查看demo:APP换肤原理

更多相关文章

  1. Android(安卓)Resource介绍和使用
  2. Android开发——Android搜索框架(二)
  3. Android(安卓)组件资源库
  4. Android(安卓)Studio & ADT 快捷键配置文件所在目录,自定义后可导
  5. [Android] ACTION_GET_CONTENT与ACTION_PICK的区别
  6. Android(安卓)UI系列 - 布局 - 目录
  7. android 目录结构,adb环境变量配置
  8. android Manifest.xml选项-android:ConfigChanges
  9. linearLayout 和 relativeLayout的属性区别

随机推荐

  1. Google Android操作系统内核编译图文教程
  2. 国内最全的Android市场,最全Android软件商
  3. Android(安卓)蓝牙调色灯/zigbee调色灯学
  4. Android(安卓)onTouchEvent, onClick及on
  5. Android简单语音控制应用的实现
  6. Android中_TextView属性的XML详解 包括单
  7. Linux ubuntu repo安装方法
  8. Android多点触控技术
  9. Android(安卓)-- Layout布局文件里的andr
  10. 移动开发参考书之Android篇