参考使用Android-skin-support生成换肤包Android快速换肤之App内部换肤 本文的Demo也是基于该参考作者的demo,需要换肤的控件需要换背景的颜色、透明度或者图片都需要用background设置背景,这样有利于换肤。
所用框架是 Android-skin-support
demo基于support库:
导入:
//skin.support:skin 3.x.x是给support准备的,如果没有androidx,则不要导入4.0.x,否则无法编译。
implementation 'skin.support:skin-support:3.1.4' // skin-support 基础控件支持
implementation 'skin.support:skin-support-design:3.1.4' // skin-support-design material design 控件支持[可选]
implementation 'skin.support:skin-support-cardview:3.1.4' // skin-support-cardview CardView 控件支持[可选]
implementation 'skin.support:skin-support-constraint-layout:3.1.4' // skin-support-constraint-layout ConstraintLayout 控件支持[可选]
主工程添加读取sd卡权限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
皮肤管理类
public class SkinManager {
private static final String TAG = "SkinManager";
private static SkinManager skinManager;
private Context context;
//皮肤包在创建的时候包名应和本项目主包名有区别
private String skinPackageName = "com.itfitness.skin";
private String skinFileNameInAssets = "night.skin";
private String skinResourceSuffixName = "night";
public static SkinManager getInstance() {
if (skinManager == null) {
skinManager = new SkinManager();
}
return skinManager;
}
public String getSkinPackageName() {
return skinPackageName;
}
public void setSkinPackageName(String skinPackageName) {
this.skinPackageName = skinPackageName;
}
/**
* 在application中的oncreate()中初始化
* @param applicationContext
*/
public void init(Application applicationContext) {
context = applicationContext;
SkinCompatManager.withoutActivity(applicationContext) // 基础控件换肤初始化
.addInflater(new SkinMaterialViewInflater()) // material design 控件换肤初始化[可选]
.addInflater(new SkinConstraintViewInflater()) // ConstraintLayout 控件换肤初始化[可选]
.addInflater(new SkinCardViewInflater()) // CardView v7 控件换肤初始化[可选]
.setSkinStatusBarColorEnable(false) // 关闭状态栏换肤,默认打开[可选]
.setSkinWindowBackgroundEnable(false)
//.addStrategy(new MyApkLoader()) //添加自定义策略,apk安装策略
.addStrategy(new MySDcardLoader()) //添加自定义策略,zip解压策略
.loadSkin();
}
/**
* 根据R获取资源的名字
* @param resId
* @return
*/
private String getResName(int resId) {
String name = context.getResources().getResourceEntryName(resId);
Log.e(TAG, "getResName: name==" + name);
return name;
}
/**
* 重置默认皮肤
*/
public void resetDefaultSkin() {
SkinCompatManager.getInstance().restoreDefaultTheme();
}
/**
* 根据sd卡目录下的zip压缩包加载皮肤
*/
public void loadSkinBySdcardZip() {
//skinName 是皮肤包包名
SkinCompatManager.getInstance().loadSkin(skinPackageName, loaderListener, MySDcardLoader.STRATEGY);//自定义加载策略,这里是zip换肤
Log.e(TAG, "loadSkinBySdcardZip: string==" + getStringByZip(R.string.app_name));
}
/**
* 从zip包中获取对应的drawable
* @param resId 资源id
* @return
*/
public Drawable getDrawableByZip(int resId) {
Resources resources = SkinCompatManager.getInstance().getSkinResources(MySDcardLoader.skinPath);
if (resources != null) {
int id = resources.getIdentifier(getResName(resId), "drawable", skinPackageName);
if (id != 0)
return resources.getDrawable(id);
}
return context.getResources().getDrawable(resId);
}
/**
* 从zip包中获取对应的string
* @param resId 资源id
* @return
*/
public String getStringByZip(int resId) {
Resources resources = SkinCompatManager.getInstance().getSkinResources(MySDcardLoader.skinPath);
if (resources != null) {
int id = resources.getIdentifier(getResName(resId), "string", skinPackageName);
if (id != 0)
return resources.getString(id);
}
return context.getResources().getString(resId);
}
/**
* 从zip包中获取对应的color
* @param resId
* @return
*/
public int getColorByZip(int resId) {
Resources resources = SkinCompatManager.getInstance().getSkinResources(MySDcardLoader.skinPath);
if (resources != null) {
int id = resources.getIdentifier(getResName(resId), "color", skinPackageName);
if (id != 0)
return resources.getColor(id);
}
return context.getResources().getColor(resId);
}
/**
* 根据安装的皮肤apk包加载皮肤
*/
public void loadSkinByApk() {
//skinName 是皮肤包包名
SkinCompatManager.getInstance().loadSkin(skinPackageName, loaderListener, MyApkLoader.STRATEGY);//自定义加载策略,这里是apk换肤
}
/**
* 从安装好的皮肤apk包中获取对应的drawable
* @param resId 资源id
* @return
*/
public Drawable getDrawableByApk(int resId) {
Resources resources = null;
try {
resources = context.createPackageContext(skinPackageName, 0).getResources();
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
if (resources != null) {
int id = resources.getIdentifier(getResName(resId), "drawable", skinPackageName);
if (id != 0)
return resources.getDrawable(id);
}
return context.getResources().getDrawable(resId);
}
/**
* 从安装好的皮肤apk包中获取对应的string
* @param resId 资源id
* @return
*/
public String getStringByApk(int resId) {
Resources resources = null;
try {
resources = context.createPackageContext(skinPackageName, 0).getResources();
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
if (resources != null) {
int id = resources.getIdentifier(getResName(resId), "string", skinPackageName);
if (id != 0)
return resources.getString(id);
}
return context.getResources().getString(resId);
}
/**
* 从安装好的皮肤apk包中获取对应的color
* @param resId
* @return
*/
public int getColorByApk(int resId) {
Resources resources = null;
try {
resources = context.createPackageContext(skinPackageName, 0).getResources();
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
if (resources != null) {
int id = resources.getIdentifier(getResName(resId), "color", skinPackageName);
if (id != 0)
return resources.getColor(id);
}
return context.getResources().getColor(resId);
}
/**
* 根据本项目assets下的皮肤包加载皮肤
* @param skinFileNameInAssets 文件名
*/
public void loadSkinBySkinPackage(String skinFileNameInAssets) {
this.skinFileNameInAssets = skinFileNameInAssets;
//skinName 是皮肤包打包后放在本项目assets下的文件名,如"night.skin"
SkinCompatManager.getInstance().loadSkin(skinFileNameInAssets, loaderListener, SkinCompatManager.SKIN_LOADER_STRATEGY_ASSETS);//module打包成apk放到assets,加载皮肤包
}
/**
* 根据本项目assets下的皮肤包加载对应的颜色资源,可以自己类比着改写加载对应的drawable或string等资源
* @param resId
* @return
*/
public int getColorBySkinPackage(int resId){
String skinPath = new File(SkinFileUtils.getSkinDir(context), skinFileNameInAssets).getAbsolutePath();
Resources resources = SkinCompatManager.getInstance().getSkinResources(skinPath);
if (resources != null) {
int id = resources.getIdentifier(getResName(resId), "color", skinPackageName);
if (id != 0)
return resources.getColor(id);
}
return context.getResources().getColor(resId);
}
/**
* 根据本项目新建的皮肤res的皮肤包加载皮肤
* @param skinResourceSuffixName 皮肤资源文件名后缀
*/
public void loadSkinByRes(String skinResourceSuffixName) {
this.skinResourceSuffixName = skinResourceSuffixName;
//skinName 是对应皮肤资源文件名后缀,比如"night",意思是另在项目下新建res文件夹,名字叫res-night,文件夹下面对应资源文件夹后缀"_night",color、string文件里每个对应名称加后缀"_night"
SkinCompatManager.getInstance().loadSkin(skinResourceSuffixName, loaderListener, SkinCompatManager.SKIN_LOADER_STRATEGY_BUILD_IN);//另外创建res文件夹,对应资源文件名后缀night,后缀加载
}
/**
* 根据本项目新建的皮肤res的皮肤包加载对应的颜色资源 ,可以自己类比着改写加载对应的drawable或string等资源
* @param resId
* @return
*/
public int getColorByRes(int resId) {
String targetResourceEntryName = new SkinBuildInLoader().getTargetResourceEntryName(context, skinResourceSuffixName, resId);
int id = context.getResources().getIdentifier(targetResourceEntryName, "color", context.getPackageName());
if (id != 0)
return context.getResources().getColor(id);
return context.getResources().getColor(resId);
}
private SkinCompatManager.SkinLoaderListener loaderListener = new SkinCompatManager.SkinLoaderListener() {
@Override
public void onStart() {
Log.e(TAG, "onStart: ");
}
@Override
public void onSuccess() {
Log.e(TAG, "onSuccess: ");
}
@Override
public void onFailed(String errMsg) {
Log.e(TAG, "onFailed:errMsg=== " + errMsg);
}
};
}
在主工程自定义的application的onCreate()方法中初始化:
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
SkinManager.getInstance().init(this);
}
}
准备皮肤包有4种模式:
1.在主工程下直接创建新的res文件夹,加上后缀,如res-night,这是待换的的皮肤资源文件夹
res-night文件夹下的所有资源文件的名字都和主工程原来带有res下的所有资源文件的名字只差一个后缀,如:“_night”
color.xml和string.xml则是文件里面的颜色或string资源名相差一个后缀:
主项目里build.gradle 加上声明sourceSets
android {
compileSdkVersion 27 //不是重点
defaultConfig {
.........//不是重点
}
buildTypes {
.......//不是重点
}
sourceSets {main {res.srcDirs = ['src/main/res', 'src/main/res-night']}}
}
在需要换肤的地方只需要调用
SkinManager.getInstance().loadSkinByRes("night");
2.生成皮肤包文件放到assets文件夹中,进行换肤
①创建皮肤包module:
点击File-----New--------New Module
选择Phone & Tablet Module
点击下一步
输入mudule命名,注意修改包名,不要和主工程的包名一致。比如主工程包名
com.itfitness.skindemo ,那么皮肤包module就可以是 com.itfitness.skin。点击下一步
选择 Add No Activity
②把对应的资源放进皮肤包
打开对应的Module,找到res—
把需要换的资源文件放进去,注意资源文件名都要和主工程的里对应的资源文件的名字完全一致。③打包皮肤
在androidStudio使用Terminal 命令行来输入
gradlew :module的名称:assembleDebug,这个命令是打Debug包的,当然也可以通过gradlew :module的名称:assembleRelease命令打 Release包。
打包完成后,在对应的皮肤module下找到输出的apk,复制到桌面,重命名,修改后缀为.skin
我这里改成了night.skin,在主工程的res文件夹下创建assets文件夹,把这个打包好的皮肤包复制进去
在需要换肤的地方上代码:
SkinManager.getInstance().loadSkinBySkinPackage("night.skin");
3.安装皮肤包apk,进行换肤
这个方法和上面的法2基本一样,只是区别在不需要重命名生成的皮肤包apk,只需要把生成的皮肤包apk安装到机器上即可。不过在生成皮肤包之前,先在皮肤module的AndroidManifest.xml文件修改为如下:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.itfitness.skin"
android:sharedUserId="主工程包名"
>
<application
android:allowBackup="true"
/>
</manifest>
android:sharedUserId=“主工程包名” 即让皮肤包和主工程运行在同一进程中
还有,需要增加一个自定义的加载策略:
在初始化的代码里增加apk安装策略
public void init(Application applicationContext) {
context = applicationContext;
SkinCompatManager.withoutActivity(applicationContext) // 基础控件换肤初始化
.addInflater(new SkinMaterialViewInflater()) // material design 控件换肤初始化[可选]
.addInflater(new SkinConstraintViewInflater()) // ConstraintLayout 控件换肤初始化[可选]
.addInflater(new SkinCardViewInflater()) // CardView v7 控件换肤初始化[可选]
.setSkinStatusBarColorEnable(false) // 关闭状态栏换肤,默认打开[可选]
.setSkinWindowBackgroundEnable(false)
.addStrategy(new MyApkLoader()) //添加自定义策略,apk安装策略
// .addStrategy(new MySDcardLoader()) //添加自定义策略,zip解压策略
.loadSkin();
}
在需要换肤的地方调用:
SkinManager.getInstance().loadSkinByApk()
4.读取sd卡中皮肤包zip压缩包,进行换肤
这个方法和上面的法2基本一样,只是区别在重命名生成的皮肤包的后缀改为.zip,让它变成压缩包,然后传到机器里。只要有它的绝对路径,就可以实现换肤。
需要增加一个自定义的加载策略:
在初始化的代码里增加apk安装策略
public void init(Application applicationContext) {
context = applicationContext;
SkinCompatManager.withoutActivity(applicationContext) // 基础控件换肤初始化
.addInflater(new SkinMaterialViewInflater()) // material design 控件换肤初始化[可选]
.addInflater(new SkinConstraintViewInflater()) // ConstraintLayout 控件换肤初始化[可选]
.addInflater(new SkinCardViewInflater()) // CardView v7 控件换肤初始化[可选]
.setSkinStatusBarColorEnable(false) // 关闭状态栏换肤,默认打开[可选]
.setSkinWindowBackgroundEnable(false)
// .addStrategy(new MyApkLoader()) //添加自定义策略,apk安装策略
.addStrategy(new MySDcardLoader()) //添加自定义策略,zip解压策略
.loadSkin();
}
在需要换肤的地方调用:
SkinManager.getInstance().loadSkinBySdcardZip();
关于自定义换肤加载策略的代码如下:
其中每个自定义策略都对应唯一 的一个STRATEGY 策略号,因为皮肤框架随时更新,所以我们的自定义策略号最好定大一点,这里我用了Short.MAX_VALUE 和Short.MAX_VALUE-1
public class MySDcardLoader extends SkinSDCardLoader {
private static final String TAG = "MySDcardLoader";
public static final int STRATEGY = Short.MAX_VALUE -1;
public static String skinPath = Environment.getExternalStorageDirectory().getAbsolutePath()+"/skin.zip";
@Override
protected String getSkinPath(Context context, String skinName) {
Log.e(TAG, "getSkinPath: skinName=="+skinName );
Log.e(TAG, "getSkinPath:111=="+Environment.getExternalStorageDirectory().getAbsolutePath()+"/skin.zip" );
Log.e(TAG, "getSkinPath222: =="+Environment.getExternalStorageDirectory().getAbsolutePath()+"/"+skinName.replace("com.itfitness.","")+".zip" );
// return Environment.getExternalStorageDirectory().getAbsolutePath()+"/skin.zip";
return Environment.getExternalStorageDirectory().getAbsolutePath()+"/"+skinName.replace("com.itfitness.","")+".zip";
}
@Override
public int getType() {
return STRATEGY;
}
}
public class MyApkLoader implements SkinCompatManager.SkinLoaderStrategy {
public static final int STRATEGY = Short.MAX_VALUE;
@Override
public String loadSkinInBackground(Context context, String skinName) {
try {
Context con = context.createPackageContext(skinName, CONTEXT_IGNORE_SECURITY);
Resources skinResources = con.getResources();
SkinCompatResources.getInstance().setupSkin(
skinResources,
skinName,
skinName,
this);
return skinName;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Override
public ColorStateList getColor(Context context, String skinName, int resId) {
return null;
}
@Override
public ColorStateList getColorStateList(Context context, String skinName, int resId) {
return null;
}
@Override
public Drawable getDrawable(Context context, String skinName, int resId) {
return null;
}
@Override
public int getType() {
return STRATEGY;
}
@Override
public String getTargetResourceEntryName(Context context, String skinName, int resId) {
return null;
}
}