前面已经介绍了换肤的基本逻辑和LayoutInflater源码解析传送门如下
android 换肤框架详解2-LayoutInflater源码解析-CSDN博客
android 换肤框架详解3-自动换肤原理梳理-CSDN博客
接下来就是整体自动换肤逻辑了
代码如下
- 自定义LayoutInflater
import android.content.Context;
import android.view.LayoutInflater;
public class SkinLayoutInflaterT extends LayoutInflater {
protected SkinLayoutInflaterT(LayoutInflater original, Context newContext) {
super(original, newContext);
//创建解析接口监听类
setFactory2(new SkinLayoutInflaterFactoryT());
}
@Override
public LayoutInflater cloneInContext(Context newContext) {
return new SkinLayoutInflaterT(this, newContext);
}
public static LayoutInflater from(Context context) {
LayoutInflater original = LayoutInflater.from(context);
return new SkinLayoutInflaterT(original, context);
}
}
- 创建监听类LayoutInflater.Factory2
import android.content.Context;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import java.lang.reflect.Field;
import java.util.HashMap;
public class SkinLayoutInflaterFactoryT implements LayoutInflater.Factory2 {
public static final String TAG = "SkinLayoutInflaterFactoryT";
/**
* 在解析创建的时候,会执行这方法,在这个方法创建View并且保存对应信息
*/
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
Log.d(TAG, "SkinInflaterFactory onCreateView(), create view name=" + name + " ");
//自己解析创建View
View view = createView(context, name, attrs);
//获取xml中所有属性,进行解析
int count = attrs.getAttributeCount();
HashMap<String, SkinAttrT> hashMap = null;
for (int i = 0; i < count; i++) {
//解析属性
String attributeName = attrs.getAttributeName(i);// 属性名,如 "layout_width"
String attributeValue = attrs.getAttributeValue(i); // 属性值,如 "match_parent"
//资源文件获取到的值前面会加一个@,这里判断属性值是否是资源文件
if (!attributeValue.startsWith("@")) {
continue;
}
//判断是否在自己已经支持的View设置方法
if (isSupport(attributeName)) {
//拿到Res值
int resId = Integer.parseInt(attributeValue.substring(1));
if (resId == 0) {
continue;
}
if (hashMap == null) {
hashMap = new HashMap<>();
}
//返回格式:ic_launcher(仅资源名)R.drawable.ic_launcher,R.color.ic_launcher
String attrValueName = context.getResources().getResourceEntryName(resId);
//资源类型类型drawable,color
String attrValueType = context.getResources().getResourceTypeName(resId);
//创建需要保存的属性和对应的值
//这里保存设置方法,ResId值,resId的名字,resId的类型
SkinAttrT skinAttrT = new SkinAttrT(attributeName, resId, attrValueName, attrValueType);
//一个View的xml中可能有多个设置方法和resId,这里设置
hashMap.put(attributeName, skinAttrT);
}
Log.d(TAG, "attributeName: " + attributeName + " = " + attributeValue);
}
if (view != null && hashMap != null) {
//将属View和其对应的属性值进行保存
SkinManagerT.getInstance().saveSkinView(view, hashMap);
}
return view;
}
public boolean isSupport(String attributeName) {
return SkinConstantT.getSkinConstantSet().contains(attributeName);
}
@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
return null;
}
private View createView(Context context, String name, AttributeSet attrs) {
View view = null;
try {
LayoutInflater inflater = LayoutInflater.from(context);
assertInflaterContext(inflater, context);
if (-1 == name.indexOf('.')) {
if ("View".equals(name) || "ViewStub".equals(name) || "ViewGroup".equals(name)) {
view = inflater.createView(name, "android.view.", attrs);
}
if (view == null) {
view = inflater.createView(name, "android.widget.", attrs);
}
if (view == null) {
view = inflater.createView(name, "android.webkit.", attrs);
}
} else {
view = inflater.createView(name, null, attrs);
}
} catch (Exception ex) {
Log.e(TAG, "createView(), create view failed" + ex);
view = null;
}
return view;
}
private void assertInflaterContext(LayoutInflater inflater, Context context) {
Context inflaterContext = inflater.getContext();
if (inflaterContext == null) {
setField(inflater, "mContext", context);
}
//设置mConstructorArgs的第一个参数context
Object[] constructorArgs = (Object[]) getField(inflater, "mConstructorArgs");
if (null == constructorArgs || constructorArgs.length < 2) {
//异常,一般不会发生
constructorArgs = new Object[2];
setField(inflater, "mConstructorArgs", constructorArgs);
}
//如果mConstructorArgs的第一个参数为空,则设置为mContext
if (null == constructorArgs[0]) {
constructorArgs[0] = inflater.getContext();
}
}
public static Object setField(Object receiver, String fieldName, Object value) {
try {
Field field;
field = findField(receiver.getClass(), fieldName);
if (field == null) {
return null;
}
field.setAccessible(true);
Object old = field.get(receiver);
field.set(receiver, value);
return old;
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
return null;
}
private static Field findField(Class<?> clazz, String name) {
try {
return clazz.getDeclaredField(name);
} catch (NoSuchFieldException e) {
if (clazz.equals(Object.class)) {
e.printStackTrace();
return null;
}
Class<?> base = clazz.getSuperclass();
return findField(base, name);
}
}
//获取类的实例的变量的值
public static Object getField(Object receiver, String fieldName) {
return getField(null, receiver, fieldName);
}
private static Object getField(String className, Object receiver, String fieldName) {
Class<?> clazz = null;
Field field;
if (!TextUtils.isEmpty(className)) {
try {
clazz = Class.forName(className);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
} else {
if (receiver != null) {
clazz = receiver.getClass();
}
}
if (clazz == null) return null;
try {
field = findField(clazz, fieldName);
if (field == null) return null;
field.setAccessible(true);
return field.get(receiver);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (NullPointerException e) {
e.printStackTrace();
}
return null;
}
}
- 创建属性值字段SkinConstantT
import java.util.HashSet;
/*
*已经适配的属性值
*/
public class SkinConstantT {
public static final String BACKGROUND = "background";
public static final String TEXT_COLOR = "textColor";
public static final String COLOR = "color";
public static final HashSet<String> skinConstantSet = new HashSet<>();
static {
skinConstantSet.add(BACKGROUND);
skinConstantSet.add(TEXT_COLOR);
}
public static HashSet<String> getSkinConstantSet() {
return skinConstantSet;
}
}
- 创建属性值保存类SkinAttrT
import android.util.Log;
public class SkinAttrT {
public static final String TAG = "SkinAttrT";
/**
* 方法名称,比如android:background="@color/content",这里是background
* 这里用于代码中调用对应的方法,比如调用setBackground
*/
public String attrName;
/**
* ResID,比如R文件中的Id,R.color.red=0x-- ,R.drawable.--,这里就是0x--
* 这里用于默认Resource进行修改值,默认res直接通过这个拿到资源文件
*/
public int attrValueRefId;
/**
* ResID对应的名称,比如android:background="@color/content",这里就是content
* 这个是换肤资源resource拿对应 的resId,要和attrValueTypeName一起用
* getIdentifier(
* attrValueRefName,// 资源名
* attrValueTypeName,// 资源类型
* "应用包名"
* );
*/
public String attrValueRefName;
/**
* ResID类型名称,比如比如android:background="@color/content",这里就是color
* 这个是换肤资源resource拿对应 的resId,要和attrValueRefId一起用
* getIdentifier(
* attrValueRefName,// 资源名
* attrValueTypeName,// 资源类型
* "应用包名"
* );
*/
public String attrValueTypeName;
public Object[] attrValueFormat;
public SkinAttrT(String attrName, int attrValueRefId, String attrValueRefName, String attrValueTypeName) {
Log.d(TAG, attrName + " attrValueRefId = " +
attrValueRefId + " attrValueRefName = " + attrValueRefName + " attrValueTypeName = " + attrValueTypeName);
this.attrName = attrName;
this.attrValueRefId = attrValueRefId;
this.attrValueRefName = attrValueRefName;
this.attrValueTypeName = attrValueTypeName;
}
public SkinAttrT setAttrValueFormat(Object[] format) {
this.attrValueFormat = format;
Log.d(TAG, attrName + "");
return this;
}
@Override
public String toString() {
return "SkinAttr \n[\nattrName=" + attrName + ", \n"
+ "attrValueRefId=" + attrValueRefId + ", \n"
+ "attrValueRefName=" + attrValueRefName + ", \n"
+ "attrValueTypeName=" + attrValueTypeName
+ "\n]";
}
}
创建换肤关了类SkinManagerT
import android.content.Context;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.util.Log;
import android.view.View;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class SkinManagerT {
public static final String TAG = "SkinManagerT";
public volatile static SkinManagerT instance;
private Context mContext;
private Resources currentResources;
private Resources themeResources;
private final ConcurrentHashMap<View, HashMap<String, SkinAttrT>> mSkinAttrMap = new ConcurrentHashMap<>();
public void init(Context context) {
mContext = context;
currentResources = mContext.getResources();
//获取换肤资源Resource
themeResources = getThemeResources(mContext);
}
private SkinManagerT() {
}
public static SkinManagerT getInstance() {
if (instance == null) {
synchronized (SkinManagerT.class) {
if (instance == null) {
instance = new SkinManagerT();
}
}
}
return instance;
}
//将View保存到被监听的view列表中,使得在换肤时能够及时被更新
public void saveSkinView(View view, HashMap<String, SkinAttrT> viewAttrs) {
if (view == null || viewAttrs == null || viewAttrs.size() == 0) {
return;
}
HashMap<String, SkinAttrT> originalSkinAttr = mSkinAttrMap.get(view);
if (originalSkinAttr != null && originalSkinAttr.size() > 0) {
originalSkinAttr.putAll(viewAttrs);
mSkinAttrMap.put(view, originalSkinAttr);
} else {
mSkinAttrMap.put(view, viewAttrs);
}
}
public Resources getDefaultResource() {
return mContext.getResources();
}
public void restoreToDefaultSkin() {
//换肤的时候切换resource
currentResources = getDefaultResource();
notifySkinChanged();
}
public void restoreToThemeSkin() {
//换肤的时候切换resource
currentResources = themeResources;
notifySkinChanged();
}
/**
* 遍历部署资源文件,将对应的资源部署到对应View上
* 更换皮肤时,通知view更换资源
*/
private void notifySkinChanged() {
View view;
HashMap<String, SkinAttrT> viewAttrs;
Iterator<Map.Entry<View, HashMap<String, SkinAttrT>>> iter = mSkinAttrMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<View, HashMap<String, SkinAttrT>> entry = iter.next();
view = entry.getKey();
viewAttrs = entry.getValue();
if (view != null) {
deployViewSkinAttrs(view, viewAttrs);
}
}
Log.d(TAG, "notifySkinChanged skinSize " + mSkinAttrMap.size());
}
public void deployViewSkinAttrs(View view, HashMap<String, SkinAttrT> viewAttrs) {
if (view == null || viewAttrs == null || viewAttrs.size() == 0) {
return;
}
Iterator<Map.Entry<String, SkinAttrT>> iter = viewAttrs.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, SkinAttrT> entry = iter.next();
SkinAttrT attr = entry.getValue();
//判断当前保存的资源文件设置到哪个方法上的
if (attr.attrName.equals(SkinConstantT.BACKGROUND)) {
//判断保存的支援文件是哪个类型
if (attr.attrValueTypeName.equals(SkinConstantT.COLOR)) {
//判断当前是说你哪个资源主题模式
if (currentResources == getDefaultResource()) {
//如果是默认,直接部署上去
view.setBackgroundColor(
getDefaultResource().getColor(attr.attrValueRefId, null)
);
} else {
//如果是主题资源,通过对应方法部署上去
view.setBackgroundColor(getThemeResourcesColorResId(attr, currentResources));
}
}
}
}
}
public int getThemeResourcesColorResId(SkinAttrT viewAttrs, Resources resources) {
//通过资源名称,获取到资源ID
int newResId = resources.getIdentifier(
viewAttrs.attrValueRefName,// 资源名
viewAttrs.attrValueTypeName, // 资源类型
"com.kx.skin" // 应用包名
);
Log.d(TAG, "getThemeResourcesColor newResId " + newResId);
//通过资源ID,获取到对应资源的值
int newColorResId = resources.getColor(newResId, null);
Log.d(TAG, "getThemeResourcesColor newColorResId " + newColorResId);
return newColorResId;
}
public Resources getThemeResources(Context context) {
try {
//通过反射创建AssetManager
// AssetManager assetManager = AssetManager.class.newInstance();
// Method add = assetManager.getClass().getDeclaredMethod("addAssetPath", String.class);
//通过反射加载路径
// int cookie = (int) add.invoke(assetManager, "/sdcard/skin/skin.skin");
//将assets中的文件拷贝到自己私有的文件中
File skinFile = copyAssetToFiles(context,
"skin-debug.apk", // assets 下的相对路径
"skin-debug.apk"); // 目标文件名
boolean exists = skinFile.exists(); // true 表示存在
Log.e(TAG, "加载文件 exists " + exists + " getAbsolutePath " + skinFile.getAbsolutePath());
// 创建资源Resources
AssetManager assetManager = new AssetManager();
int cookie = assetManager.addAssetPath(skinFile.getAbsolutePath());
if (cookie == 0) {
Log.e(TAG, "加载失败,路径无效或权限不足");
}
Resources oldRes = context.getResources();
Resources newRes = new Resources(assetManager,
oldRes.getDisplayMetrics(),
oldRes.getConfiguration());
return newRes;
} catch (Throwable e) {
e.printStackTrace();
Log.d(TAG, "Throwable " + e);
}
return null;
}
public File copyAssetToFiles(Context ctx, String assetPath, String fileName) throws IOException {
File outFile = new File(ctx.getFilesDir(), fileName);
try (InputStream in = ctx.getAssets().open(assetPath);
OutputStream out = Files.newOutputStream(outFile.toPath())) {
byte[] buf = new byte[8192];
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
}
return outFile;
}
}
使用
setContentView(SkinLayoutInflaterT.from(this).inflate(R.layout.activity_theme, null));
SkinManagerT.getInstance().restoreToThemeSkin();
这里只是简单的把代码逻辑走了一遍,后面需要更多适配工作,现在换肤逻辑走完了