前面已经介绍了换肤的基本逻辑和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();
这里只是简单的把代码逻辑走了一遍,后面需要更多适配工作,现在换肤逻辑走完了