Android换肤功能
- 什么是换肤?
- app的皮肤,比如说黑夜模式,切换之后整体风格改变成以黑色为主题色
- 换了什么?
- 背景、颜色、图片、字体等等
- 换肤的原理
- 加载另一个apk中的相同名字的图片颜色等资源
- 思路
- 监察当前apk中xml生成的加载过程
- 拿到所有具有换肤潜质的控件(包括自定义控件)
- 这些控件都通过统一的管理者(SkinManager)来设置颜色或者背景资源等
- 下载换肤apk、获取资源
- ** 开始换肤**
- 代码实现
- 新建BaseActivity通过
LayoutInflaterCompat.setFactory2(?,?)
监听xml的生成过程,里面需要我们传两个参数,第一个很简单直接 getLayoutInflater() 获取就可以,第二个参数需要我们传递一个 LayoutInflater.Factory2 这个类需要我们自己复写一遍好收集换肤的控件
- 新建BaseActivity通过
skinFactory = new SkinFactory();
LayoutInflaterCompat.setFactory2(getLayoutInflater(), skinFactory);
- 在SkinFactory中实现 LayoutInflater.Factory2 接口,在onCreateView方法中进行控件的收集
这里的name就是我们获取到的控件名,需要注意的是,自定义控件是全名比如 com.leary.MyTextVIew 二系统控件不是全名,所以需要们把它补充完整
private static final String[] prefixList = {"android.widget.", "android.view.", "android.webkit."};
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
//收集需要换肤的控件
View view = null;
if (name.contains(".")) {// 自定义控件
view = createView(context, attrs, name);
} else {//系统控件
for (String prefix : prefixList) {
view = createView(context, attrs, prefix + name);
if (view != null) {
break;
}
}
}
if (view != null) {
parseSkinView(context, attrs,view);
}
return view;
}
createView需要用到反射,我贴一下代码
Class viewClazz = context.getClassLoader().loadClass(name);
Constructor<? extends View> constructor = viewClazz.getConstructor(
new Class[]{Context.class, AttributeSet.class});
return constructor.newInstance(context, attrs);
当View被创建完成,这个时候就需要们完成收集了
private void parseSkinView(Context context, AttributeSet attrs, View view) {
List<SkinItem> list = new ArrayList<>();
for (int i=0;i<attrs.getAttributeCount();i++) {
String attrName = attrs.getAttributeName(i);
String attrValue = attrs.getAttributeValue(i);
if ("textColor".equals(attrName) || "background".equals(attrName)) {
//可换肤的控件
int id = Integer.parseInt(attrValue.substring(1));
String entry_name = context.getResources().getResourceEntryName(id);
String typeName = context.getResources().getResourceTypeName(id);
SkinItem skinItem = new SkinItem(attrName, entry_name, typeName, id);
list.add(skinItem);
}
}
if (!list.isEmpty()) {
SkinView skinView = new SkinView(view, list);
cacheList.add(skinView);
//xml加载过程中换肤
skinView.apply();
}
}
这里面SkinItem是收集的控件的属性,也不多解释看图吧
SkinView就是封装当前View和所有控件集合的对象
换肤的时候从SkinManger里边去拿
public void apply() {
//应用所有的换肤
for (SkinItem skinItem : list) {
if ("background".equals(skinItem.getAttrName())) {
if ("color".equals(skinItem.getAttrType())) {
view.setBackgroundColor(SkinManager.getInstance().getColor(skinItem.getAttrId()));
} else if ("drawable".equals(skinItem.getAttrType())) {
view.setBackgroundDrawable(SkinManager.getInstance().getDrawable(skinItem.getAttrId()));
}
}
}
}
SkinManager只需要干一个事情
- 获取资源(当前apk或者其他apk的资源),里面还会用到反射
public class SkinManager {
private static final SkinManager ourInstance = new SkinManager();
public static SkinManager getInstance() {
return ourInstance;
}
private SkinManager() {
}
//apk中的resources
private Resources skinResources;
private Context context;
//皮肤apk的包名
private String skinPackage;
public void init(Context context) {
this.context = context.getApplicationContext();
}
public Resources getSkinResources() {
return skinResources;
}
//获取resId
public int getColor(int resId) {
if (skinResources == null) {
return ContextCompat.getColor(context, resId);
}
String resName = context.getResources().getResourceEntryName(resId);
int skinId = skinResources.getIdentifier(resName, "color", skinPackage);
if (skinId == 0) {
return ContextCompat.getColor(context, resId);
}
if (resId == skinId) {
Log.e("leary", resName + " id一样 " + skinPackage);
}
return skinResources.getColor(skinId);
}
public Drawable getDrawable(int resId) {
if (skinResources != null) {
String resName = context.getResources().getResourceEntryName(resId);
int skinId = skinResources.getIdentifier(resName, "drawable", skinPackage);
if (skinId == 0) {
return ContextCompat.getDrawable(context, resId);
}
return skinResources.getDrawable(skinId);
}
return ContextCompat.getDrawable(context, resId);
}
//加载apk
public void loadApk(String path) {
try {
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager, path);
skinResources = new Resources(assetManager, context.getResources().getDisplayMetrics(),
context.getResources().getConfiguration());
PackageManager packageManager = context.getPackageManager();
//拿到皮肤的包名
skinPackage = packageManager.getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES).packageName;
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
- 现在我们只需要下载皮肤apk的资源,然后点击换肤即可
- 注意
- 皮肤apk中的color或者drawable需要和当前apk的名字相同
- 记得添加权限