android 换肤框架

参考使用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;
    }
}


  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值