前言
上一篇文章,不但获取到了所有的 View,还将需要换肤的 View 进行了筛选并且保存在了 List 中,那么接下来,就需要获取皮肤包中的资源,然后让这些 View 加载这些资源就 OK 了。
生成皮肤包
加载皮肤包的资源,首先要生成皮肤包,当然,按理来说这个应该是从网上下载下来的,但是我们现在自己造一个皮肤包,保存到 sdcard 中。
继续看我们 MainActivity 的布局文件,现在只有一个 LinearLayout 和第二个 TextView 可以换肤(该 TextView 可以换背景和字体颜色),其中,background 属性对应的值是 ?attr/colorAccent 和 @color/black ,textColor 属性对应的值是 @color/black。
接下来我们新建一个项目,用这个新项目生成皮肤包,该项目不需要写什么代码,就将上述这些对应的资源进行设置即可,假如说我们将背景设置成绿色,将字体设置成红色,然后生成 apk,导入到 sdcard 中。
cd sdcard/
app-skin-debug.apk
获取皮肤包中的资源
导入成功后,我们就先要加载这个 apk,然后获取其资源,具体怎么获取呢?首先我们要获取路径,然后将路径设置到 AssetManager 中,然后根据这个 AssetManager 获取皮肤包中的 Resources,获取皮肤包的包名。接下来我们需要将主包中的资源和皮肤包中的资源进行转换, 怎么转换?假如属性的值是 R.drawable.ic_launcher,我们先获取他的名字 ic_launcher,再获取他的类型 drawable,然后通过 Resources.getIdentifier(name,type,packageName);
就可以获得类型为 drawable 的 ic_launcher 在 packageName 中的资源 id 的值。
import android.app.Application;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.text.TextUtils;
import android.widget.Toast;
import com.radish.android.skin_core.util.SkinPreference;
import com.radish.android.skin_core.util.SkinResources;
import java.io.File;
import java.lang.reflect.Method;
import java.util.Observable;
/**
* 皮肤管理器
*/
public class SkinManager extends Observable {
private static SkinManager instance = null;
public static void init(Application application){
if(instance == null){
synchronized (SkinManager.class){
if(instance == null){
instance = new SkinManager(application);
}
}
}
}
public static SkinManager getInstance(){
return instance;
}
private Application application;
private SkinManager(Application application){
this.application = application;
application.registerActivityLifecycleCallbacks(new SkinActivityLifecycle());
SkinResources.init(application);
SkinPreference.init(application);
}
/**
* 加载皮肤包
* @param path 皮肤包的路径
*/
public void loadSkin(String path) throws Exception {
if(TextUtils.isEmpty(path)){
//如果是空,就加载默认资源,清空皮肤包的配置
SkinPreference.getInstance().setSkin("");
SkinResources.getInstance().reset();
}else{
File file = new File(path);
if(!file.exists()){
throw new Exception("您加载的资源包不存在");
}
/*
这里,由于 AssetMaanger 的限制比较大,我们通过反射来获取 AssetManager 实例对象,
然后给他设置路径的方法是被隐藏了,我们继续使用反射的方式来获取。
*/
AssetManager assetManager = AssetManager.class.newInstance();
Method method = assetManager.getClass().getMethod("addAssetPath", String.class);
method.setAccessible(true);
method.invoke(assetManager,path);
//这个是我们应用的 Resources
Resources resources = application.getResources();
//这个是皮肤包对应的 Resources
Resources skinResources = new Resources(assetManager,resources.getDisplayMetrics(),resources.getConfiguration());
//获取包名
PackageManager packageManager = application.getPackageManager();
PackageInfo info = packageManager.getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES);
if(info != null){
String packageName = info.packageName;
//设置后可以获取对应皮肤包中的资源了
SkinResources.getInstance().applySkin(skinResources,packageName);
//设置完成后,记得保存一下皮肤包的路径
SkinPreference.getInstance().setSkin(path);
}else{
Toast.makeText(application, "加载皮肤资源包失败", Toast.LENGTH_SHORT).show();
}
}
//通知观察者进行更新
setChanged();
notifyObservers();
}
}
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
/**
* 资源转换工具类
*/
public class SkinResources {
private static SkinResources instance;
private String packageName;
private SkinResources(Context context){
this.mAppResources = context.getResources();
}
public static void init(Context context){
if(instance == null){
synchronized (SkinResources.class){
if(instance == null){
instance = new SkinResources(context);
}
}
}
}
private Resources mSkinResources;
private Resources mAppResources;
/**
* 用于表示是否加载默认皮肤
*/
private boolean isDefaultSkin = true;
public static SkinResources getInstance(){
return instance;
}
public void applySkin(Resources resources,String packageName){
this.mSkinResources = resources;
this.packageName = packageName;
isDefaultSkin = TextUtils.isEmpty(packageName) || (resources == null);
}
public int getIdentifier(int resId){
String resourceEntryName = mAppResources.getResourceEntryName(resId);
String resourceTypeName = mAppResources.getResourceTypeName(resId);
return mSkinResources.getIdentifier(resourceEntryName, resourceTypeName, packageName);
}
public int getColor(int resId){
if(isDefaultSkin){
return resId;
}
int skinId = getIdentifier(resId);
if(skinId == 0){
return mAppResources.getColor(resId);
}
return mSkinResources.getColor(skinId);
}
public ColorStateList getColorStateList(int resId){
if(isDefaultSkin){
return mAppResources.getColorStateList(resId);
}
int skinId = getIdentifier(resId);
if(skinId== 0){
return mAppResources.getColorStateList(resId);
}
return mSkinResources.getColorStateList(skinId);
}
public Drawable getDrawable(int resId){
if(isDefaultSkin){
return mAppResources.getDrawable(resId);
}
int skinId = getIdentifier(resId);
if(skinId == 0){
return mAppResources.getDrawable(resId);
}
return mSkinResources.getDrawable(skinId);
}
/**
* 这里返回值写成 Object 是因为 background 可能是
* color 也可能是 drawable
*/
public Object getBackground(int resId){
String resourceTypeName = mAppResources.getResourceTypeName(resId);
if("color".equals(resourceTypeName)){
return getColor(resId);
}else{
return getDrawable(resId);
}
}
public String getString(int resId){
if(isDefaultSkin){
return mAppResources.getString(resId);
}
int skinId = getIdentifier(resId);
if(skinId == 0){
return mAppResources.getString(resId);
}
return mSkinResources.getString(skinId);
}
public void reset() {
mSkinResources = null;
packageName = "";
isDefaultSkin = true;
}
}
/**
* 保存 Util
*/
public class SkinPreference {
private static final String SKIN_SHARED = "skins";
private static final String KEY_SKIN_PATH = "skin_path";
private static SkinPreference instance;
private SharedPreferences mPref;
private SkinPreference(Context context){
mPref = context.getSharedPreferences(SKIN_SHARED,Context.MODE_PRIVATE);
}
public static void init(Context context){
if(instance == null){
synchronized (SkinPreference.class){
if(instance == null){
instance = new SkinPreference(context);
}
}
}
}
public static SkinPreference getInstance(){
return instance;
}
public void setSkin(String path){
mPref.edit().putString(KEY_SKIN_PATH,path).apply();
}
public String getSkin(){
return mPref.getString(KEY_SKIN_PATH,null);
}
}
import android.app.Activity;
import android.app.Application;
import android.os.Bundle;
import android.view.LayoutInflater;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
/**
* Actiity 声明周期回调监听
*/
public class SkinActivityLifecycle implements Application.ActivityLifecycleCallbacks {
private Map<Activity,SkinLayoutFactory> mLayoutFactoryMap = new HashMap<>();
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
LayoutInflater layoutInflater = LayoutInflater.from(activity);
try {
/*
这里需要注意一点,在调用 setFactory2的时候,源码有一个判断,
if (mFactorySet) {
throw new IllegalStateException("A factory has already been set on this LayoutInflater");
}
这个意思就是 Android 布局加载器通过 mFactorySet 标记是否设置过 Factory,如果设置过就会抛出异常
那么我们就强制这个 mFactorySet 是 false
*/
Field field = LayoutInflater.class.getDeclaredField("mFactorySet");
field.setAccessible(true);
field.setBoolean(layoutInflater,false);
} catch (Exception e) {
e.printStackTrace();
}
SkinLayoutFactory skinLayoutFactory = new SkinLayoutFactory();
layoutInflater.setFactory2(skinLayoutFactory);
//注册观察者
SkinManager.getInstance().addObserver(skinLayoutFactory);
mLayoutFactoryMap.put(activity,skinLayoutFactory);
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityResumed(Activity activity) {
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity activity) {
//删除观察者
SkinLayoutFactory skinLayoutFactory = mLayoutFactoryMap.get(activity);
SkinManager.getInstance().deleteObserver(skinLayoutFactory);
}
}
应用皮肤包中的资源
现在皮肤包中的资源已经能获取到了,需要换肤的 View 也有了,我们只需要将需要换肤的 View 中某个属性的值换成皮肤包中的资源,就行了。
首先要初始化我们的框架,SkinManager.init(application);这个写到Application 的 onCreate()方法里面即可。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.tv_click).setOnClickListener(v -> {
//开始换肤
try {
SkinManager.getInstance().loadSkin("/sdcard/app-skin-debug.apk");
} catch (Exception e) {
e.printStackTrace();
}
});
findViewById(R.id.tv_other).setOnClickListener(v -> {
//还原
try {
SkinManager.getInstance().loadSkin("");
} catch (Exception e) {
e.printStackTrace();
}
});
}
}
刚打开 app 我们的界面是这样的
点击换肤按钮
如果我们这时候关闭应用,重新打开,界面还是这样的,因为我们保存了。
还原按钮点击后
那么简单的初始化换肤就完成了。