# 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)
[](https://gitee.com/AWeiLoveAndroid/AndroidSkinChange)    [](LICENSE)

----
## 一、功能介绍
### 支持的功能
* [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,容易和同类库产生冲突。
----
## 二、更新记录
|版本更新说明|
|----|
||
|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名称的后缀或前缀保持一致**。文字不方便具体描述,直接看图所示:

* 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)。
* > 如果线上没有解决您的问题,欢迎加入技术交流群:

----
## (七)License MIT
[LICENSE](LICENSE)