前面两个项目
现在我们就在具体实践中,讲解一下更多的使用场景
主入口MyApplication
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
SkinManager.init(this);
}
}
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换肤原理