iT邦幫忙

0

Android一鍵更換主題套件

前言

前陣子看了bilibili上的一些技術相關影片,碼牛學院的公開課程
Android动态加载技术的高级进阶,手写实现网易云主题换肤框架

影片中的講者用Java初步實作了一個修改主題的框架
學習的過程動作做是個滿重要的階段
因此我把影片中的這個框架跟著教學手刻一次

刻完套用在我自己的測試專案上時發現還有些問題
接著就依照遇到的問題進行進一步的修改

在教學影片中看得到的邏輯規則,本篇文章就不贅述,我會直接說明我基於教學的框架增加或修改的部分
不想浪費時間看文章可以直接看我的Repository ChangeThemeSample

問題集

- 第一個遇到的問題是,依照我過去的開發習慣,專案經常會大量的使用到style來設定相同規格的物件

例如這樣統一設定TextView的風格

<style name="style_button_text">
    <item name="android:layout_width">match_parent</item>
    <item name="android:layout_height">wrap_content</item>
    <item name="android:textColor">@color/white</item>
    <item name="android:layout_gravity">center</item>
    <item name="android:gravity">center</item>
    <item name="android:background">@drawable/selector_btn_circle_default</item>
    <item name="android:paddingBottom">10dp</item>
</style>

 <TextView
    style="@style/style_button_text" />

在使用style的情況下,透過AttributeSet getAttributeName只會取到"style"
而style_button_text裡面有設定的textColor, background是取不到的
這樣在後續設定主題時,使用style的寫法就會失效

因此需要針對style的況狀更深入的處理
首先我定義了,方便foreach確認屬性是否存在

private static final int[] NATIVE_ATTRIBUTE_ID = {
        android.R.attr.textColor,
        android.R.attr.background,
        android.R.attr.src
};

逐一確認style中是否能夠取到我們對應的屬性

TypedArray typedArray = view.getContext().obtainStyledAttributes(attrs.getStyleAttribute(), NATIVE_ATTRIBUTE_ID);
if (typedArray.length() > 0){
    for(int ti = 0; ti < typedArray.length(); ti++){
        try{
            if(typedArray.hasValue(ti)){
                int resId = typedArray.getResourceId(ti, -1);
                if(resId != -1) {
                    addSkinItem(view, NATIVE_ATTRIBUTE_NAME[ti], resId, skinItems);
                }
            }
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}
typedArray.recycle();

這邊有遇到一個問題還沒深入去找原因,NATIVE_ATTRIBUTE_ID中將textColor及background順序調換後,textColor會失效取不到resourceId,之後有空必須要查查

- 第二個問題是,不支援專案正在使用的物件或屬性

解決方案當然就是要把物件及相應的屬性也加入到判斷的清單

private static final String[] THIRDPARTY_ATTRIBUTE_NAME = {
        "tabIndicator",
        "tabIndicatorColor"
};

private static final String[] THIRDPARTY_VIEW = {
        "com.google.android.material.tabs.TabLayout"
};

利用反射的方式將數值置換

Method setSelectedTabIndicator = view.getClass().getDeclaredMethod("setSelectedTabIndicator", Drawable.class);
                        setSelectedTabIndicator.invoke(view, SkinManager.getInstance().getDrawable(skinItem.getResId()));

- 第三個問題則是由第二個問題衍伸

Android的物件、屬性千百種,實在不太可能每一個都加入我們的判斷規則中,就算都整理進來多少也會有效能上的問題,例如每個onCreateView都要foreach跑一次包含2000項目的array大概受不了

因此需要增加一個可以讓人擴充判斷及設定的功能
這邊以ProgressBar及progressDrawable屬性作為範例
首先需要生成一個callback(CustomViewAttributeApplyListener),這個callback會將當下要置換主題的view、屬性及目前取到的資源回傳,開發人員收到此回傳實再進行相對應的設定

這邊比較需要注意點的點是,進行設定要取得資源時必須透過SkinManager中的getDrawable、getColor相關方法取得,如果直接使用當下的conetext取回資源則會取到原始APK的資源,而非主題包

CustomViewAttributeApplyListener listener = (view, fieldName, resId) -> {
    String viewName = view.getClass().getSimpleName();
    switch (viewName){
    case "ProgressBar":
        ((ProgressBar)view.findViewById(R.id.progressBar)).setProgressDrawable(SkinManager.getInstance().getDrawable(resId));
        break;
    }
};

SkinManager.getInstance().addCustomView(new SkinCustomView(ProgressBar.class.getSimpleName(), ProgressBar.class.getName(), new String[]{"progressDrawable"}, listener));

- 最後一個問題

大概是每個專案都一定會遇到的狀況,某些UI上效果需要依照API回傳的狀態呈現
例如狀態1文字使用顏色A、背景使用AA;狀態2文字使用顏色B、背景使用BB

這個狀況再設定時就需要參考問題三
同樣是使用SkinManager中的getDrawable、getColor相關方法取得資源

總結

個人認為看完公開課程還算滿有收穫的,也算是有個現成的可以學習,也講解得滿詳細的,後面進階課銷售相關的話真的很多。不過也可以透過他講述的一些狀況了解內地的行情可能也不是壞事

這個套件肯定還有許多需要優化的地方,以及Bug需要修改,有任何建議歡迎提出或加入一起維護


尚未有邦友留言

立即登入留言