# AndroidSkinChange **Repository Path**: AWeiLoveAndroid/AndroidSkinChange ## Basic Information - **Project Name**: AndroidSkinChange - **Description**: 一款 Android 换肤框架, 极低的学习成本, 极好的用户体验。只需要一行代码, 就可以实现换肤, 你值得拥有!!! - **Primary Language**: Java - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-05-28 - **Last Updated**: 2024-05-28 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # AndroidSkinChange [中文文档](README.md) | [EngLish Documents](README_EN.md) [![release](img/release-v5.0.0-green.png)](https://gitee.com/AWeiLoveAndroid/AndroidSkinChange) ![build](img/build-passing-green.png) ![platform](img/platform-Android-green.png) ![languages](img/languages-Java-green.png) [![license](img/license-mit-green.png)](LICENSE) ![logo](img/logo.png) ---- ## 一、功能介绍 ### 支持的功能 * [x] 支持布局中用到的资源换肤。 * [x] 支持代码中设置的资源换肤。 * [x] 默认支持大部分基础控件,Material Design换肤。 * [x] 支持动态设置主题颜色值,支持选择sdcard上的图片作为drawable换肤资源。 * [x] 支持多种加载策略 (应用内部换肤 / 插件式换肤 / 自定义SD卡路径换肤 / Zip文件换肤 等)。 * [x] 支持资源加载优先级: 用户动态设置资源(最高)->加载策略中的资源->插件式换肤/应用内换肤->应用资源。 * [x] 支持定制化,选择需要的模块加载。 * [x] 支持矢量图(vector/svg)换肤。 * [x] 支持AndroidX ### TODO * [x] 解耦androidx * [ ] 支持更多原生组件换肤 * [ ] 支持更多语言。 * [ ] 支持更多字体。 * [ ] 支持Preference。 * [ ] skin-mobile 实现: * [ ] 动态修改主题颜色值 * [ ] 控件使用案例 * [x] 关于页面 * [ ] Wiki * [ ] 支持第三方常用UI相关控件换肤(只收录常用的作为代表) ### 缺点 * 同一个LayoutInflater只能设置一次Factory,容易和同类库产生冲突。 ---- ## 二、更新记录 |版本更新说明| |----| |![release](img/release-v5.0.0-green.png)| |1. 优化性能,减少反射的和使用。| |2. 重构包结构,去除冗余的代码。| |3.统一了android_views和androidx_views的相关API,减少使用和迁移成本。| |4. sample项目更加完善,每个控件都做了换肤处理。| |5.待补充...| ---- ## 三、模块以及支持的控件 ### 模块描述 模块名称|描述 ----|---- android_views|Android原生View换肤功能 androidx_views|`androidx.appcompat`,`com.google.android.material`,`androidx.constraintlayout`,`androidx.cardview`等支持库的View换肤功能。 third_party_views|第三放开源库的换肤功能,目前仅支持`de.hdodenhof:circleimageview` 和 `io.github.h07000223:flycoTabLayout`。 ### `android_views` 支持的原生控件 * android.widget.View * android.widget.ViewGroup * android.widget.FrameLayout * android.widget.LinearLayout * android.widget.RelativeLayout * android.widget.ScrollView(继承自FrameLayout) * android.widget.TextView及其子类 * android.widget.EditText(继承自TextView) * android.widget.AutoCompleteTextView(继承自EditText) * android.widget.MultiAutoCompleteTextView(继承自AutoCompleteTextVieww) * android.widget.Button(继承自TextView) * android.widget.CompoundButton及其子类(继承自Button) * android.widget.CheckBox(继承自CompoundButton) * android.widget.CheckedTextView(继承自TextView) * android.widget.RadioButton(继承自CompoundButton) * android.widget.RadioGroup(继承自LinearLayout) * android.widget.ImageView及其子类 * android.widget.ImageButton(继承自ImageView) * android.widget.ProgressBar及其子类 * android.widget.RatingBar(继承自AbsSeekBar,AbsSeekBar继承自ProgressBar) * android.widget.SeekBar(继承自AbsSeekBar,AbsSeekBar继承自ProgressBar) ### `androidx_views` 支持的原生控件 #### 1.支持的androix.appcompat控件(除了Spinner和Toolbar之外,其他的和 `android_views`支持的控件一样。) * android.view.View * android.view.ViewGroup * android.view.FrameLayout * android.view.LinearLayout * android.view.RelativeLayout * androidx.appcompat.widget.ButtonBarLayout(继承自LinearLayout) * androidx.appcompat.widget.ScrollView(继承自FrameLayout) * androidx.appcompat.widget.TextView及其子类 * androidx.appcompat.widget.EditText(继承自TextView) * androidx.appcompat.widget.AutoCompleteTextView(继承自EditText) * androidx.appcompat.widget.MultiAutoCompleteTextView(继承自AutoCompleteTextVieww) * androidx.appcompat.widget.Button(继承自TextView) * androidx.appcompat.widget.CompoundButton及其子类(继承自Button) * androidx.appcompat.widget.CheckBox(继承自CompoundButton) * androidx.appcompat.widget.CheckedTextView(继承自TextView) * androidx.appcompat.widget.RadioButton(继承自CompoundButton) * androidx.appcompat.widget.RadioGroup(继承自LinearLayout) * androidx.appcompat.widget.ImageView及其子类 * androidx.appcompat.widget.ImageButton(继承自ImageView) * androidx.appcompat.widget.ProgressBar及其子类 * androidx.appcompat.widget.RatingBar(继承自AbsSeekBar,AbsSeekBar继承自ProgressBar) * androidx.appcompat.widget.SeekBar(继承自AbsSeekBar,AbsSeekBar继承自ProgressBar) * **androidx.appcompat.widget.AppCompatSpinner(继承自Spinner,Spinner继承自AbsSpinner,AbsSpinner继承自AdapterView,AdapterView继承自ViewGroup)** * **androidx.appcompat.widget.Toolbar(继承自ViewGroup)** #### 2.支持的androix.cardview控件 * androidx.cardview.widget.CardView(继承自FrameLayout) #### 3.支持的androix.constraintlayout控件 * androidx.constraintlayout.widget.ConstraintLayout(继承自FrameLayout) #### 4.支持的androidx.coordinatorlayout控件 * androidx.coordinatorlayout.widget.CoordinatorLayout #### 5.支持的material design(com.google.android.material)控件 * com.google.android.material.appbar.AppBarLayout * com.google.android.material.bottomnavigation.BottomNavigationView * com.google.android.material.navigation.NavigationView * com.google.android.material.appbar.CollapsingToolbarLayout * com.google.android.material.floatingactionbutton.FloatingActionButton * com.google.android.material.tabs.TabLayout * com.google.android.material.textfield.TextInputEditText * com.google.android.material.textfield.TextInputLayout ---- ## 四、基本使用 ### (一)引入方式 拷贝 `artifact` 目录的相关aar包到您项目模块的libs目录下。 **说明:** * 如果只使用基本控件,请拷贝`artifact` 目录的 [android_views-release-V5.0.0.aar](artifact/android_views-release-V5.0.0.aar)。 * 如果使用到了 `androidx`库里面的控件,请拷贝`artifact` 目录的 [androidx_views-release-V5.0.0.aar](artifact/androidx_views-release-V5.0.0.aar) 以及 [material-release-V5.0.0.aar](artifact/material-release-V5.0.0.aar)。 * 如果使用到了第三方控件,请拷贝`artifact` 目录的 [third_party_views-release-V5.0.0.aar](artifact/third_party_views-release-V5.0.0.aar),这里的第三方库目前仅支持`de.hdodenhof:circleimageview` 和 `io.github.h07000223:flycoTabLayout`,后续会根据用的多的开源库做一些适配。 ### (二)初始化 **PS: 为了让初始化保持API的一致性,我在源码的基础上把`android_views`里面相关缺失的代码做了完善。** #### 1.项目中只使用基础控件(例如:TextView) > 初始化操作如下: ```java import androidx.appcompat.app.AppCompatDelegate; import com.lzw.skinchange.android.utils.Slog; import com.lzw.skinchange.android.SkinCompatManager; import com.lzw.skinchange.android.app.SkinCompatViewInflater; // 框架换肤日志打印 Slog.DEBUG = BuildConfig.DEBUG; SkinCompatManager.getInstance() .addStrategy(new CustomSDCardLoader()) // 自定义加载策略,指定SDCard路径 .addStrategy(new ZipSDCardLoader()) // 自定义加载策略,获取zip包中的资源 .addInflater(new SkinCompatViewInflater()) // 基础控件换肤 .setSkinStatusBarColorEnable(true) // 关闭状态栏换肤 .setSkinWindowBackgroundEnable(false) // 关闭windowBackground换肤 .setSkinAllActivityEnable(false) // true: 默认所有的Activity都换肤; false: 只有实现SkinCompatSupportable接口的Activity换肤 .loadSkin(); ``` > CustomSDCardLoader: 自定义加载策略,指定SDCard路径 ```java import com.lzw.skinchange.android.load.SkinSDCardLoader; import com.lzw.skinchange.android.utils.SkinFileUtils; import android.content.Context; public class CustomSDCardLoader extends SkinSDCardLoader { public static final int SKIN_LOADER_STRATEGY_SDCARD = Integer.MAX_VALUE; @Override protected String getSkinPath(Context context, String skinName) { return new File(SkinFileUtils.getSkinDir(context), skinName).getAbsolutePath(); } @Override public int getType() { return SKIN_LOADER_STRATEGY_SDCARD; } } ``` > ZipSDCardLoader:自定义加载策略,获取zip包中的资源 ```java import com.lzw.skinchange.android.load.SkinSDCardLoader; import com.lzw.skinchange.android.utils.SkinFileUtils; import android.content.Context; import android.graphics.drawable.Drawable; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; public class ZipSDCardLoader extends SkinSDCardLoader { public static final int SKIN_LOADER_STRATEGY_ZIP = Integer.MAX_VALUE - 1; @Override public String loadSkinInBackground(Context context, String skinName) { try { InputStream is = context.getAssets().open("zip_res.zip"); String dir = SkinFileUtils.getSkinDir(context); File zipFile = new File(dir, "zip_res.zip"); OutputStream os = new FileOutputStream(zipFile); int byteCount; byte[] bytes = new byte[1024]; while ((byteCount = is.read(bytes)) != -1) { os.write(bytes, 0, byteCount); } os.close(); is.close(); unzipBackgroundPng(dir); } catch (IOException e) { e.printStackTrace(); } return super.loadSkinInBackground(context, skinName); } private void unzipBackgroundPng(String dir) throws IOException { File zipBackground = new File(dir, "zip_background.png"); ZipFile zipFile = new ZipFile(new File(dir, "zip_res.zip")); ZipEntry entry = zipFile.getEntry("zip_background.png"); InputStream is = zipFile.getInputStream(entry); OutputStream os = new FileOutputStream(zipBackground); int byteCount; byte[] bytes = new byte[1024]; while ((byteCount = is.read(bytes)) != -1) { os.write(bytes, 0, byteCount); } os.close(); is.close(); } @Override protected String getSkinPath(Context context, String skinName) { return new File(SkinFileUtils.getSkinDir(context), skinName).getAbsolutePath(); } @Override public Drawable getDrawable(Context context, String skinName, int resId) { String resName = context.getResources().getResourceEntryName(resId); String resType = context.getResources().getResourceTypeName(resId); if ("drawable".equalsIgnoreCase(resType) && "zip_background".equalsIgnoreCase(resName)) { String dir = SkinFileUtils.getSkinDir(context); File zipBackground = new File(dir, "zip_background.png"); if (zipBackground.exists()) { return Drawable.createFromPath(zipBackground.getAbsolutePath()); } return null; } return super.getDrawable(context, skinName, resId); } @Override public int getType() { return SKIN_LOADER_STRATEGY_ZIP; } } ``` #### 2.项目中用到了androidx控件(例如:AppCompatTextView) > 初始化操作如下: * 这里的`CustomSDCardLoader`代码和上面是一样的,只是它继承自`com.lzw.skinchange.androidx.core.load.SkinSDCardLoader`。 * 这里的`ZipSDCardLoader`代码和上面是一样的,只是它继承自`com.lzw.skinchange.androidx.core.load.SkinSDCardLoader`。 ```java import com.lzw.skinchange.androidx.appcompat.app.SkinAppCompatViewInflater; import com.lzw.skinchange.androidx.cardview.SkinCardViewInflater; import com.lzw.skinchange.androidx.constraintlayout.SkinConstraintViewInflater; import com.lzw.skinchange.androidx.core.SkinCompatManager; import com.lzw.skinchange.androidx.core.utils.Slog; import com.lzw.skinchange.androidx.design.app.SkinMaterialViewInflater; import com.lzw.skinchange.sample.loader.CustomSDCardLoader; import com.lzw.skinchange.sample.loader.ZipSDCardLoader; import com.lzw.skinchange.third_party.circleimageview.SkinCircleImageViewInflater; import com.lzw.skinchange.third_party.flycotablayout.SkinFlycoTabLayoutInflater; import androidx.appcompat.app.AppCompatDelegate; SkinCompatManager.withoutActivity(this) // 必须调用此方法,否则会报错 .addStrategy(new CustomSDCardLoader()) // 自定义加载策略,指定SDCard路径 .addStrategy(new ZipSDCardLoader()) // 自定义加载策略,获取zip包中的资源 .addInflater(new SkinAppCompatViewInflater()) // androidx.appcompat控件 .addInflater(new SkinMaterialViewInflater()) // material design控件 .addInflater(new SkinConstraintViewInflater()) // ConstraintLayout控件 .addInflater(new SkinCardViewInflater()) // androidx.cardview.widget.CardView控件 .addInflater(new SkinCircleImageViewInflater()) // hdodenhof/CircleImageView控件 .addInflater(new SkinFlycoTabLayoutInflater()) // H07000223/FlycoTabLayout控件 .setSkinStatusBarColorEnable(true) // 开启状态栏换肤 .setSkinWindowBackgroundEnable(false) // 关闭windowBackground换肤 //.setSkinAllActivityEnable(false) // true: 默认所有的Activity都换肤; false: 只有实现SkinCompatSupportable接口的Activity换肤 .loadSkin(); AppCompatDelegate.setCompatVectorFromResourcesEnabled(true); ``` **重点:** 项目中的Activity必须继承自`com.lzw.skinchange.androidx.appcompat.app.SkinAppCompatDelegateActivity`。 ### (三)配置全局换肤,以及设置单个页面不跟随全局换肤而换肤 #### 1.全局初始化时可以设置所有页面通用的换肤配置 ```java SkinCompatManager.withoutActivity(this) // 必须调用此方法,否则会报错 .setSkinStatusBarColorEnable(true) // 开启状态栏换肤 .setSkinWindowBackgroundEnable(false) // 关闭windowBackground换肤 .setSkinAllActivityEnable(false) // true: 默认所有的Activity都换肤; false: 只有实现SkinCompatSupportable接口的Activity换肤 .loadSkin(); ``` #### 2.部分页面始终保持原样,不跟随全局换肤,如何处理? > 如果项目中有特殊需求。例如, 股票控件: 控件颜色始终为红色或绿色, 不需要随着模式切换而换肤。那么可以使用类似的方法, 直接设置drawable:如下所示: * 代码动态设置如下所示:(注意:只能传入Drawable对象) ```java xxxxx.xxx.xxxxView stockView = findViewById(R.id.stockView); ColorDrawable colorDrawable = new ColorDrawable(Color.parseColor("#ce3d3a")); stockView.setBackgroundDrawable(colorDrawable); // ↓ 错误示范: // xxxxx.xxx.xxxxView stockView = findViewById(R.id.stockView); // stockView.setBackgroundResource(R.drawable.red); ``` * xml里面的设置如下所示: ```xml (注意:只能写具体的颜色值) ``` ### (四)加载皮肤包 **注意:这里只是展示了API,后文有详细案例使用。** #### 1.使用默认皮肤 ```java SkinCompatManager.getInstance().restoreDefaultTheme(); ``` #### 2.使用指定皮肤插件包(这里有几种加载策略) > 1.使用loadSkin(String, SkinLoaderListener, int) 方法加载皮肤包 ```java SkinCompatManager.getInstance().loadSkin(String skinName, SkinLoaderListener listener, int strategy); ``` > 2.loadSkin(String, SkinLoaderListener, int) 方法参数3表示加载策略,默认的有4种策略: ```java SkinCompatManager.SKIN_LOADER_STRATEGY_NONE = -1; // 无策略 SkinCompatManager.SKIN_LOADER_STRATEGY_ASSETS = 0; // assets资源加载,一般用于加载插件皮肤包 SkinCompatManager.SKIN_LOADER_STRATEGY_BUILD_IN = 1; // 后缀加载,一般用于应用内换肤 SkinCompatManager.SKIN_LOADER_STRATEGY_PREFIX_BUILD_IN = 2; // 前缀加载,一般用于应用内换肤 ``` ### (五)换肤方式 #### 1.应用内部换肤 * 1.应用内换肤,需要在res同级目录准备多套资源文件,并且资源名称不能重复。资源包名称后缀或前缀**必须跟res名称的后缀或前缀保持一致**。文字不方便具体描述,直接看图所示: ![应用内换肤](img/应用内换肤.png) * 2.加载策略: > 如果资源包添加了自定义后缀,那就使用下面这个, `dark`就是添加的后缀名称,就是图中橙色横杠标注的那个: SkinCompatManager.getInstance().loadSkin("dark", SkinCompatManager.**SKIN_LOADER_STRATEGY_BUILD_IN**); // 后缀加载 > 如果资源包添加了自定义前缀,那就使用下面这个, `dark`是添加的前缀名称。 SkinCompatManager.getInstance().loadSkin("dark", SkinCompatManager.**SKIN_LOADER_STRATEGY_PREFIX_BUILD_IN**); // 前缀加载 * 3.Android Studio配置src资源目录: ```groovy sourceSets { main { res.srcDirs = [ 'src/main/res', 'src/main/res_dark' ] } } ``` #### 2.插件式换肤 * 1.皮肤工程包名不能和宿主应用包名相同。 * 2.把需要换肤的资源放到插件工程的res目录(这里**资源名称必须和宿主应用保持一致**)。 * 3.打包apk,重命名为`xxx.skin`,防止用户误安装。 * 4.把插件包放到宿主项目的 `assets/skins` 目录下,然后在宿主的代码中加载皮肤插件,示例如下: ```java SkinCompatManager.getInstance().loadSkin("dark.skin", SkinCompatManager.SKIN_LOADER_STRATEGY_ASSETS); ``` ### (六)获取当前使用皮肤 使用 [SkinPreference.java](androidx_views/src/main/java/com/lzw/skinchange/androidx/core/utils/SkinPreference.java) 相关API可以获取当前使用的皮肤,可以获取皮肤包名称,使用的主题,以及加载策略。 示例代码如下: ```java import com.lzw.skinchange.androidx.core.utils.SkinPreference; String skin = SkinPreference.getInstance().getSkinName(); int strategy = SkinPreference.getInstance().getSkinStrategy(); String userTheme = SkinPreference.getInstance().getUserTheme(); ``` ---- ## 五、最佳实践: 前面两个是经常用到的,很重要,值得重点关注。 ### ※(一)用户自定义颜色或者图片作为颜色资源或者背景资源 #### 1.代码里面动态设置颜色资源 详情请查看代码: [ColorPickerActivity.java](sample/src/main/java/com/lzw/skinchange/sample/picker/ColorPickerActivity.java) ```java SkinCompatUserThemeManager.get().addColorState(R.color.colorPrimary, #ffffffff); SkinCompatUserThemeManager.get().addColorState(R.color.colorPrimary, new ColorState.ColorBuilder().addXxx().build()); // 在设置完颜色及图片后,需要调用`apply()`方法来保存设置。 SkinCompatUserThemeManager.get().apply(); // 清除所有已有颜色值。 SkinCompatUserThemeManager.get().clearColors(); ``` #### 2.代码里面动态设置图片资源 详情请查看代码: [DrawablePickerActivity.java](sample/src/main/java/com/lzw/skinchange/sample/picker/DrawablePickerActivity.java) ```java SkinCompatUserThemeManager.get().addDrawablePath(R.drawable.windowBackground, "/sdcard/DCIM/Camera/xxx.jpg"); // 要换肤的资源id,图片路径,图片旋转角度(默认为0) SkinCompatUserThemeManager.get().addDrawablePath(R.drawable.windowBackground, "/sdcard/DCIM/Camera/xxx.jpg", 90); // 在设置完颜色及图片后,需要调用`apply()`方法来保存设置。 SkinCompatUserThemeManager.get().apply(); // 清除所有已有图片路径。 SkinCompatUserThemeManager.get().clearDrawables(); ``` ### ※(二)用户自定义皮肤包加载路径 **注意:资源加载策略不只是皮肤包,还可以是任意资源方式,如:Zip、APK、Json等。下面介绍了两种方式:(.skin文件的APK格式,以及Zip包格式资源文件。)** #### 1.自定义SD卡路径换肤 详细代码也可以查看:[CustomSDCardLoader.java](sample/src/main/java/com/lzw/skinchange/sample/loader/CustomSDCardLoader.java) * 1.皮肤包存放到SD卡的`data/data/项目包名/cache/`目录。 * 2.继承自`SkinSDCardLoader`,通过`getSkinPath`方法指定皮肤加载路径,通过`getType`方法指定加载器type。 **注意:自定义加载器type 值最好从整数最大值开始递减,框架的type值从小数开始递增,以免将来框架升级造成type 值冲突。** ```java import android.content.Context; import com.lzw.skinchange.androidx.core.load.SkinSDCardLoader; import com.lzw.skinchange.androidx.core.utils.SkinFileUtils; import java.io.File; public class CustomSDCardLoader extends SkinSDCardLoader { public static final int SKIN_LOADER_STRATEGY_SDCARD = Integer.MAX_VALUE; @Override protected String getSkinPath(Context context, String skinName) { return new File(SkinFileUtils.getSkinDir(context), skinName).getAbsolutePath(); } @Override public int getType() { return SKIN_LOADER_STRATEGY_SDCARD; } } ``` * 3.在Application中,添加自定义加载策略: ```java SkinCompatManager.withoutActivity(this) .addStrategy(new CustomSDCardLoader()); // 自定义加载策略 ``` * 4.使用自定义加载器加载皮肤: (参数1为皮肤包的名称) ```java SkinCompatManager.getInstance().loadSkin("dark.skin", null, CustomSDCardLoader.SKIN_LOADER_STRATEGY_SDCARD); ``` #### 2.自定义zip资源换肤 详细代码也可以查看:[ZipSDCardLoader.java](sample/src/main/java/com/lzw/skinchange/sample/loader/ZipSDCardLoader.java) * 1.继承自`SkinSDCardLoader`,在`loadSkinInBackground`方法中解压资源,在`getDrawable`等方法中返回加压后的资源。 ```java import com.lzw.skinchange.androidx.core.load.SkinSDCardLoader; import com.lzw.skinchange.androidx.core.utils.SkinFileUtils; import android.content.Context; import android.graphics.drawable.Drawable; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; public class ZipSDCardLoader extends SkinSDCardLoader { public static final int SKIN_LOADER_STRATEGY_ZIP = Integer.MAX_VALUE - 1; @Override public String loadSkinInBackground(Context context, String skinName) { // 这里演示了解压zip包中的资源 try { InputStream is = context.getAssets().open("zip_res.zip"); String dir = SkinFileUtils.getSkinDir(context); File zipFile = new File(dir, "zip_res.zip"); OutputStream os = new FileOutputStream(zipFile); int byteCount; byte[] bytes = new byte[1024]; while ((byteCount = is.read(bytes)) != -1) { os.write(bytes, 0, byteCount); } os.close(); is.close(); unzipBackgroundPng(dir); } catch (IOException e) { e.printStackTrace(); } return super.loadSkinInBackground(context, skinName); } private void unzipBackgroundPng(String dir) throws IOException { File zipBackground = new File(dir, "zip_background.png"); ZipFile zipFile = new ZipFile(new File(dir, "zip_res.zip")); ZipEntry entry = zipFile.getEntry("zip_background.png"); InputStream is = zipFile.getInputStream(entry); OutputStream os = new FileOutputStream(zipBackground); int byteCount; byte[] bytes = new byte[1024]; while ((byteCount = is.read(bytes)) != -1) { os.write(bytes, 0, byteCount); } os.close(); is.close(); } @Override protected String getSkinPath(Context context, String skinName) { // TODO 返回皮肤包路径,如果自需要使用zip包,则返回"" return new File(SkinFileUtils.getSkinDir(context), skinName).getAbsolutePath(); } // 这里演示了根据resId来判断是否使用zip包中的资源 @Override public Drawable getDrawable(Context context, String skinName, int resId) { String resName = context.getResources().getResourceEntryName(resId); String resType = context.getResources().getResourceTypeName(resId); if ("drawable".equalsIgnoreCase(resType) && "zip_background".equalsIgnoreCase(resName)) { String dir = SkinFileUtils.getSkinDir(context); File zipBackground = new File(dir, "zip_background.png"); if (zipBackground.exists()) { return Drawable.createFromPath(zipBackground.getAbsolutePath()); } return null; } return super.getDrawable(context, skinName, resId); } @Override public int getType() { return SKIN_LOADER_STRATEGY_ZIP; } } ``` * 2.在Application中,添加自定义加载策略: ```java SkinCompatManager.withoutActivity(this) .addStrategy(new ZipSDCardLoader()); // 自定义加载策略 ``` * 3.使用自定义加载器加载皮肤: ```java SkinCompatManager.getInstance().loadSkin("dark.skin", null, ZipSDCardLoader.SKIN_LOADER_STRATEGY_ZIP); ``` ### (三)沉浸式状态栏 #### 1.(Actionbar以及Toolbar) #### 2.第三方沉浸式状态栏适配 ### (四)Window窗体以及弹窗菜单中的实践 #### 1.AlertDialog中的实践 #### 2.PopupWindow中的实践 #### 3.自定义Toast中的实践 #### 4.自定义Snackbar中的实践 #### 5.使用WindowManager自定义Window窗体 #### 6.安卓3种常见menu的实践(OptionMenu,ContextMenu,PopupMenu) ### (五)WebView中的实践 ### (六)VideoView中的实践(待完善) ### (七)SurfaceView中的实践(待完善) ---- ## 六、建议和反馈: * > 常见问题和解答请查看[wiki 常见问题和解答](https://gitee.com/AWeiLoveAndroid/AndroidSkinChange/wikis/%E7%9B%AE%E5%BD%95/F&Q/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98%E5%92%8C%E8%A7%A3%E7%AD%94) * > 更多详细文档请查看 [wiki](https://gitee.com/AWeiLoveAndroid/AndroidSkinChange/wikis/) * > 如果您在使用的过程中发现bug,欢迎提出 [issues](https://gitee.com/AWeiLoveAndroid/AndroidSkinChange/issues)。 * > 如果您有更好的思路和方案,欢迎提出 [PR](https://gitee.com/AWeiLoveAndroid/AndroidSkinChange/pulls)。 * > 如果您的项目中正在使用该框架,欢迎提供宝贝的应用名称和logo,已经使用该库的应用:[who-use-it](img/who-use-it)。 * > 如果线上没有解决您的问题,欢迎加入技术交流群: ![](img/qq_group.png) ---- ## (七)License MIT [LICENSE](LICENSE)