美文网首页
Android换肤框架skin-support

Android换肤框架skin-support

作者: 遇见编程 | 来源:发表于2024-03-14 12:10 被阅读0次

一、依赖引用

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.png
2.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修改失败 - -、");
        }
    }

}

相关文章

网友评论

      本文标题:Android换肤框架skin-support

      本文链接:https://www.haomeiwen.com/subject/adcwzdtx.html