一、依賴(lài)引用
1、support library
implementation 'skin.support:skin-support:3.1.4' // skin-support 基礎(chǔ)控件支持
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 基礎(chǔ)控件支持
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 控件支持[可選]
這里有個(gè)坑,由于skin-support不再更新維護(hù)胆胰,Androidx的版本要設(shè)置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 基礎(chǔ)控件支持
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'
二狞贱、框架需要在應(yīng)用Application的oncreate中進(jìn)行初始化。
1蜀涨、support library
SkinCompatManager.withoutActivity(this) // 基礎(chǔ)控件換膚初始化
.addInflater(new SkinMaterialViewInflater()) // material design 控件換膚初始化[可選]
.addInflater(new SkinConstraintViewInflater()) // ConstraintLayout 控件換膚初始化[可選]
.addInflater(new SkinCardViewInflater()) // CardView v7 控件換膚初始化[可選]
.setSkinStatusBarColorEnable(false) // 關(guān)閉狀態(tài)欄換膚瞎嬉,默認(rèn)打開(kāi)[可選]
.setSkinWindowBackgroundEnable(false) // 關(guān)閉windowBackground換膚蝎毡,默認(rèn)打開(kāi)[可選]
.loadSkin();
2、AndroidX
public class MyApp extends Application {
@Override
public void onCreate() {
super.onCreate();
SkinCompatManager.withoutActivity(this)
.addInflater(new SkinAppCompatViewInflater()) // 基礎(chǔ)控件換膚初始化
.addInflater(new SkinMaterialViewInflater()) // material design 控件換膚初始化[可選]
.addInflater(new SkinConstraintViewInflater()) // ConstraintLayout 控件換膚初始化[可選]
.addInflater(new SkinCardViewInflater()) // CardView v7 控件換膚初始化[可選]
.setSkinStatusBarColorEnable(false) // 關(guān)閉狀態(tài)欄換膚氧枣,默認(rèn)打開(kāi)[可選]
.setSkinWindowBackgroundEnable(false) // 關(guān)閉windowBackground換膚沐兵,默認(rèn)打開(kāi)[可選]
.loadSkin();
}
}
注意:如果項(xiàng)目中使用的Activity繼承自AppCompatActivity,需要重載getDelegate()方法
@NonNull
@Override
public AppCompatDelegate getDelegate() {
return SkinAppCompatDelegateImpl.get(this, this);
}
最后別忘記使用自己的application
<application
android:name=".MyApp"
...>
三便监、創(chuàng)建需要換膚的資源
1.選中main -> 右鍵New->Directory 創(chuàng)建res-后綴名
86dc8eb52ba4464eb058db9753934064.png
2.res-后綴名資源文件下創(chuàng)建對(duì)應(yīng)的drawable、values等資源文件,如下圖所示!!
注意弹灭,color等資源也要進(jìn)行后綴添加
四剂习、使用
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="默認(rèn)皮膚"
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="藍(lán)色皮膚"
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="文本,是指書(shū)面語(yǔ)言的表現(xiàn)形式逊移,從文學(xué)角度說(shuō)预吆,通常是具有完整、系統(tǒng)含義(Message)的一個(gè)句子或多個(gè)句子的組合胳泉。一個(gè)文本可以是一個(gè)句子(Sentence)拐叉、一個(gè)段落(Paragraph)或者一個(gè)篇章(Discourse)。廣義“文本”:任何由書(shū)寫(xiě)所固定下來(lái)的任何話語(yǔ)胶背。(利科爾) 狹義“文本”:由語(yǔ)言文字組成的文學(xué)實(shí)體巷嚣,代指“作品”,相對(duì)于作者钳吟、世界構(gòu)成一個(gè)獨(dú)立廷粒、自足的系統(tǒng)。"
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時(shí)候的錯(cuò)誤點(diǎn)坝茎,有可能用的上= =
//獲取TabLayout設(shè)置的字體顏色,包含tabTextColor及tabSelectedTextColor
ColorStateList colorStateList = tabLayout.getTabTextColors();
//對(duì)每個(gè)Tab 設(shè)置customView,設(shè)置為T(mén)extView暇番,用于設(shè)置字體大小等
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);
}
}
這里在網(wǎng)上找了一個(gè)工具類(lèi)(跳轉(zhuǎn)):
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());
//使用默認(rèn)TabItem樣式時(shí)嗤放,需要添加LayoutParams,否則會(huì)出現(xiàn)Tab文字不居中問(wèn)題
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);
}
}
五壁酬、如果美工給的圖片都是一個(gè)名稱(chēng)次酌,這里在單元測(cè)試中寫(xiě)了一個(gè)工具類(lèi)
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 后綴名稱(chēng)
* @description: 通過(guò)文件路徑,修改該路徑下所有文件的名字
*/
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();
// 將文件路徑轉(zhuǎn)成列表
String[] split = filePath.split("[\\\\/]+");
List<String> pathList = new ArrayList<>(Arrays.asList(split));
// 獲取倒數(shù)第二個(gè)元素
if (pathList.size() > 1) {
String lastButOne = pathList.get(pathList.size() - 2);
// 排除values文件夾
if (!Objects.equals(lastButOne, "values")) {
// 設(shè)置文件名稱(chēng)
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");
// 循環(huán)查找
while (matcher.find()) {
// 原來(lái)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修改失敗 - -舆乔、");
}
}
}