一、依赖引用
1、support library
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 控件支持[可选]
2、AndroidX
implementation 'skin.support:skin-support:4.0.5' // skin-support
implementation 'skin.support:skin-support-appcompat:4.0.5' // skin-support 基础控件支持
implementation 'skin.support:skin-support-design:4.0.5' // skin-support-design material design 控件支持[可选]
implementation 'skin.support:skin-support-cardview:4.0.5' // skin-support-cardview CardView 控件支持[可选]
implementation 'skin.support:skin-support-constraint-layout:4.0.5' // skin-support-constraint-layout ConstraintLayout 控件支持[可选]
这里有个坑,由于skin-support不再更新维护,Androidx的版本要设置1.2.0,bulild.gradle:
dependencies {
implementation('androidx.appcompat:appcompat') {
version {
strictly '1.2.0'
}
}
implementation 'com.google.android.material:material:1.9.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
implementation 'skin.support:skin-support:4.0.5' // skin-support
implementation 'skin.support:skin-support-appcompat:4.0.5' // skin-support 基础控件支持
implementation 'skin.support:skin-support-design:4.0.5' // skin-support-design material design 控件支持[可选]
implementation 'skin.support:skin-support-cardview:4.0.5' // skin-support-cardview CardView 控件支持[可选]
implementation 'skin.support:skin-support-constraint-layout:4.0.5' // skin-support-constraint-layout ConstraintLayout 控件支持[可选]
}
settings.gradle:
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
jcenter() // Warning: this repository is going to shut down soon
maven { url 'https://jitpack.io' }
}
}
rootProject.name = "huan-fu-demo"
include ':app'
二、框架需要在应用Application的oncreate中进行初始化。
1、support library
SkinCompatManager.withoutActivity(this) // 基础控件换肤初始化
.addInflater(new SkinMaterialViewInflater()) // material design 控件换肤初始化[可选]
.addInflater(new SkinConstraintViewInflater()) // ConstraintLayout 控件换肤初始化[可选]
.addInflater(new SkinCardViewInflater()) // CardView v7 控件换肤初始化[可选]
.setSkinStatusBarColorEnable(false) // 关闭状态栏换肤,默认打开[可选]
.setSkinWindowBackgroundEnable(false) // 关闭windowBackground换肤,默认打开[可选]
.loadSkin();
2、AndroidX
public class MyApp extends Application {
@Override
public void onCreate() {
super.onCreate();
SkinCompatManager.withoutActivity(this)
.addInflater(new SkinAppCompatViewInflater()) // 基础控件换肤初始化
.addInflater(new SkinMaterialViewInflater()) // material design 控件换肤初始化[可选]
.addInflater(new SkinConstraintViewInflater()) // ConstraintLayout 控件换肤初始化[可选]
.addInflater(new SkinCardViewInflater()) // CardView v7 控件换肤初始化[可选]
.setSkinStatusBarColorEnable(false) // 关闭状态栏换肤,默认打开[可选]
.setSkinWindowBackgroundEnable(false) // 关闭windowBackground换肤,默认打开[可选]
.loadSkin();
}
}
注意:如果项目中使用的Activity继承自AppCompatActivity,需要重载getDelegate()方法
@NonNull
@Override
public AppCompatDelegate getDelegate() {
return SkinAppCompatDelegateImpl.get(this, this);
}
最后别忘记使用自己的application
<application
android:name=".MyApp"
...>
三、创建需要换肤的资源
1.选中main -> 右键New->Directory 创建res-后缀名
86dc8eb52ba4464eb058db9753934064.png2.res-后缀名资源文件下创建对应的drawable、values等资源文件,如下图所示!!
注意,color等资源也要进行后缀添加
四、使用
public class MainActivity extends BaseActivity<ActivityMainBinding> {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding.btnDefault.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showToast("default");
SkinCompatManager.getInstance().restoreDefaultTheme();
}
});
binding.btnBlue.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showToast("blue");
SkinCompatManager.getInstance().loadSkin("blue", SkinCompatManager.SKIN_LOADER_STRATEGY_BUILD_IN);
}
});
binding.btnGreen.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showToast("green");
SkinCompatManager.getInstance()
.loadSkin("green", SkinCompatManager.SKIN_LOADER_STRATEGY_BUILD_IN);
}
});
binding.btnRed.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showToast("red");
SkinCompatManager.getInstance()
.loadSkin("red", SkinCompatManager.SKIN_LOADER_STRATEGY_BUILD_IN);
}
});
}
@NonNull
@Override
public AppCompatDelegate getDelegate() {
return SkinAppCompatDelegateImpl.get(this, this);
}
public void showToast(String msg) {
runOnUiThread(() -> Toast.makeText(this, msg, Toast.LENGTH_SHORT).show());
}
}
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/btnDefault"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/btn_bg"
android:text="默认皮肤"
android:textColor="@color/btn_color"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btnBlue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:background="@color/btn_bg"
android:text="蓝色皮肤"
android:textColor="@color/btn_color"
app:layout_constraintStart_toEndOf="@id/btnDefault"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btnGreen"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:background="@color/btn_bg"
android:text="绿色皮肤"
android:textColor="@color/btn_color"
app:layout_constraintStart_toEndOf="@id/btnBlue"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btnRed"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:background="@color/btn_bg"
android:text="红色皮肤"
android:textColor="@color/btn_color"
app:layout_constraintStart_toEndOf="@id/btnGreen"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tvTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="2"
android:padding="15dp"
android:text="文本,是指书面语言的表现形式,从文学角度说,通常是具有完整、系统含义(Message)的一个句子或多个句子的组合。一个文本可以是一个句子(Sentence)、一个段落(Paragraph)或者一个篇章(Discourse)。广义“文本”:任何由书写所固定下来的任何话语。(利科尔) 狭义“文本”:由语言文字组成的文学实体,代指“作品”,相对于作者、世界构成一个独立、自足的系统。"
android:textColor="@color/btn_bg"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/btnGreen" />
<ImageView
android:id="@+id/imageView"
android:layout_width="200dp"
android:layout_height="200dp"
android:src="@drawable/aaa_blue"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvTextView" />
</androidx.constraintlayout.widget.ConstraintLayout>
BaseActivity中就使用了viewbinding
public class BaseActivity<T extends ViewBinding> extends AppCompatActivity {
protected T binding;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Type superclass = getClass().getGenericSuperclass();
Class<?> aClass = (Class<?>) ((ParameterizedType) superclass).getActualTypeArguments()[0];
try {
Method method = aClass.getDeclaredMethod("inflate", LayoutInflater.class);
binding = (T) method.invoke(null, getLayoutInflater());
setContentView(binding.getRoot());
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
}
注意,使用com.google.android.material.tabs.TabLayout时候的错误点,有可能用的上= =
//获取TabLayout设置的字体颜色,包含tabTextColor及tabSelectedTextColor
ColorStateList colorStateList = tabLayout.getTabTextColors();
//对每个Tab 设置customView,设置为TextView,用于设置字体大小等
for (int i = 0; i < tabLayout.getTabCount(); i++) {
TabLayout.Tab tab = tabLayout.getTabAt(i);
assert tab != null;
String tabStr = Objects.requireNonNull(tab.getText()).toString();
if(tab.getCustomView() == null || !(tab.getCustomView() instanceof TextView)){
TextView tv = new TextView(tabLayout.getContext());
tv.setTextColor(colorStateList);
tv.setText(tabStr);
tv.setTextSize(tab.isSelected()?selectSize:unSelectSize);
tab.setCustomView(tv);
}
}
这里在网上找了一个工具类(跳转):
public class TabLayoutUtil {
private final TabLayout tabLayout;
private boolean enableChangeSize = false;
private int unSelectSize = 15,selectSize = 16;
private TabLayoutUtil(TabLayout tabLayout) {
this.tabLayout = tabLayout;
}
public static TabLayoutUtil build(TabLayout tabLayout){
return new TabLayoutUtil(tabLayout);
}
public TabLayoutUtil enableChangeStyle() {
this.enableChangeSize = true;
ColorStateList colorStateList = tabLayout.getTabTextColors();
for (int i = 0; i < tabLayout.getTabCount(); i++) {
TabLayout.Tab tab = tabLayout.getTabAt(i);
assert tab != null;
String tabStr = Objects.requireNonNull(tab.getText()).toString();
if(tab.getCustomView() == null || !(tab.getCustomView() instanceof TextView)){
TextView tv = new TextView(tabLayout.getContext());
//使用默认TabItem样式时,需要添加LayoutParams,否则会出现Tab文字不居中问题
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(-2,-2);
tv.setLayoutParams(params);
tv.setTextColor(colorStateList);
tv.setText(tabStr);
tv.setTextSize(tab.isSelected()?selectSize:unSelectSize);
tab.setCustomView(tv);
}
}
return this;
}
public TabLayoutUtil setTextSizes(int selectSize,int unSelectSize) {
this.selectSize = selectSize;
this.unSelectSize = unSelectSize;
return this;
}
public TabLayoutUtil setOnSelectedListener(OnSelectedListener onSelectedListener) {
tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
String tabStr = Objects.requireNonNull(tab.getText()).toString();
if(onSelectedListener!=null){
onSelectedListener.onSelected(tabStr);
}
if(enableChangeSize){
TextView tv = (TextView) tab.getCustomView();
assert tv != null;
tv.setTextSize(selectSize);
}
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
if(enableChangeSize){
TextView tv = (TextView) tab.getCustomView();
assert tv != null;
tv.setTextSize(unSelectSize);
}
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
return this;
}
public interface OnSelectedListener{
void onSelected(String tabStr);
}
}
五、如果美工给的图片都是一个名称,这里在单元测试中写了一个工具类
public class ExampleUnitTest {
@Test
public void main() {
// 修改为自己的res-xxx路径
String path = "F:\\AndroidProject\\huanfudemo\\app\\src\\main\\res-blue";
String[] parts = path.split("-"); // 分割字符串
String lastPart = parts[parts.length - 1];
String suffix = "_" + lastPart;
changeFileName(path, suffix);
}
/**
* @param path 文件夹路径
* @param suffix 后缀名称
* @description: 通过文件路径,修改该路径下所有文件的名字
*/
public static void changeFileName(String path, String suffix) {
File file_path = new File(path);
if (file_path.exists()) {
File[] fileList = file_path.listFiles();
if (null == fileList || fileList.length == 0) {
System.out.println("文件夹是空的!");
} else {
for (File file : fileList) {
if (file.isDirectory()) {
// 如果是文件夹就去递归
changeFileName(file.getAbsolutePath(), suffix);
} else {
// 文件路径
String filePath = file.getAbsolutePath();
// 将文件路径转成列表
String[] split = filePath.split("[\\\\/]+");
List<String> pathList = new ArrayList<>(Arrays.asList(split));
// 获取倒数第二个元素
if (pathList.size() > 1) {
String lastButOne = pathList.get(pathList.size() - 2);
// 排除values文件夹
if (!Objects.equals(lastButOne, "values")) {
// 设置文件名称
String fileName = filePath.substring(0, filePath.lastIndexOf(".")) + suffix + filePath.substring(filePath.lastIndexOf("."));
File oriFile = new File(filePath);
boolean b = oriFile.renameTo(new File(fileName));
if (b) System.out.println("修改成功~");
} else {
String colorsFile = pathList.get(pathList.size() - 1);
if (colorsFile.equals("colors.xml")) {
try {
modifyColorNamesInFile(new File(filePath), suffix);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
}
}
} else {
System.out.println("该路径不存在");
}
}
/**
* 修改color.xml文件name添加后缀
*
* @param file
* @param suffix
* @throws IOException
*/
private static void modifyColorNamesInFile(File file, String suffix) throws IOException {
// 读取文件
String content = new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8);
// 使用正则匹配
String regex = "<color name=\"([a-zA-Z0-9_]+)\">#[0-9a-fA-F]*</color>";
Pattern pattern = Pattern.compile(regex);
// 获取name
Matcher matcher = pattern.matcher(content);
StringBuilder sb = new StringBuilder();
sb.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + "<resources>" + "\n");
// 循环查找
while (matcher.find()) {
// 原来name
String originalName = matcher.group(1);
// 新的的name
String replacement = matcher.group(0).replace(originalName, originalName + suffix);
sb.append("\t" + replacement + "\n");
}
sb.append("</resources>");
try (FileOutputStream fos = new FileOutputStream(file)) {
fos.write(sb.toString().getBytes(StandardCharsets.UTF_8));
System.out.println("colors.xml修改成功~");
} catch (Exception e) {
System.out.println("colors.xml修改失败 - -、");
}
}
}
网友评论