Android換膚框架skin-support

一、依賴(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修改失敗 - -舆乔、");
        }
    }

}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末岳服,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子希俩,更是在濱河造成了極大的恐慌吊宋,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件颜武,死亡現(xiàn)場(chǎng)離奇詭異璃搜,居然都是意外死亡拖吼,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)这吻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)吊档,“玉大人,你說(shuō)我怎么就攤上這事唾糯〖” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵趾断,是天一觀的道長(zhǎng)拒名。 經(jīng)常有香客問(wèn)我,道長(zhǎng)芋酌,這世上最難降的妖魔是什么增显? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮脐帝,結(jié)果婚禮上同云,老公的妹妹穿的比我還像新娘。我一直安慰自己堵腹,他們只是感情好炸站,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著疚顷,像睡著了一般旱易。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上腿堤,一...
    開(kāi)封第一講書(shū)人閱讀 51,624評(píng)論 1 305
  • 那天阀坏,我揣著相機(jī)與錄音,去河邊找鬼笆檀。 笑死忌堂,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的酗洒。 我是一名探鬼主播士修,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼樱衷!你這毒婦竟也來(lái)了棋嘲?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤箫老,失蹤者是張志新(化名)和其女友劉穎封字,沒(méi)想到半個(gè)月后黔州,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體耍鬓,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡阔籽,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了牲蜀。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片笆制。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖涣达,靈堂內(nèi)的尸體忽然破棺而出在辆,到底是詐尸還是另有隱情,我是刑警寧澤度苔,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布匆篓,位于F島的核電站,受9級(jí)特大地震影響寇窑,放射性物質(zhì)發(fā)生泄漏鸦概。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一甩骏、第九天 我趴在偏房一處隱蔽的房頂上張望窗市。 院中可真熱鬧,春花似錦饮笛、人聲如沸咨察。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)摄狱。三九已至,卻和暖如春无午,著一層夾襖步出監(jiān)牢的瞬間二蓝,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工指厌, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留刊愚,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓踩验,卻偏偏與公主長(zhǎng)得像鸥诽,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子箕憾,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容