讓你的Android應用能使用多種主題 ( Part 2 )

讓你的Android應用能使用多種主題 ( Part 2 )

上一篇博文 中,我們創建了一個明亮風格的主題,并且為實現使用多種主題作了一些前期的準備,而今天呢,我打算在這篇博文中接著上一篇博文繼續為大家講解,而我今天要講的內容大概是以下三個部分:使 Android 應用能夠使用多種主題,創建一個灰暗風格的主題,以及允許 Android 應用在運行時自由地切換不同的主題。

在理想的情況下,如果我們把主題的設置看作是一項配置,那么我們應該能夠在類似 "theme-qualifier" 的目錄下指定我們想要的特定主題,例如:values-dark 就是我們想要的灰暗風格主題;而values-light 則是明亮風格的主題。但很遺憾,在這篇博文所要講述的實現方法里,這種方法并沒有成為實現方式之一。

那么我們要怎么為不同的主題指定相應的資源文件呢?如果我們有了解過 appcompat 是怎么使用資源文件的話,對 Android 系統是如何管理和使用資源文件會有一個粗略的認識。毫無疑問,Materialistic 中使用的方法就是類似于 Android 系統使用的方法。

主題設置

values/styles.xml

    <style name="AppTheme" parent="Theme.AppCompat.Light">
    <!-- original theme attributes -->
    ...
    </style>

    <style name="AppTheme.Dark" parent="Theme.AppCompat">
         <item name="colorPrimary">@color/colorPrimaryInverse</item>
         <item name="colorPrimaryDark">@color/colorPrimaryDarkInverse</item>
         <item name="colorAccent">@color/colorAccentInverse</item>
         <item name="android:textColorPrimary">@color/textColorPrimaryInverse</item>
         <item name="android:textColorSecondary">@color/textColorSecondaryInverse</item>
         <item name="android:textColorPrimaryInverse">@color/textColorPrimary</item>
         <item name="android:textColorSecondaryInverse">@color/textColorSecondary</item>
    ...
    </style>

values/color.xml

    <!-- original color palette -->
    ...
    <!-- alternative color palette -->
    <color name="colorPrimaryInverse">...</color>
    <color name="colorPrimaryDarkInverse">...</color>
    <color name="colorAccentInverse">...</color>

在上面的操作中我們創建了一個名叫 AppTheme.Dark 的灰暗風格主題,此外,為了保持 style 和 color 的一致性,我們的 AppTheme.Dark 主題衍生于 appcompat 的 Theme.AppCompat 主題(一個 Android 自帶的灰暗風格主題)。然而,由于我們的兩個主題(明亮風格和灰暗風格)衍生于不同的基礎主題,因此這兩個主題之間并不能夠進行屬性的共享(在JAVA中,類只能進行單繼承)。

這兩個主題理應有一些恰當的屬性值,能同時用于設置基本的 Android 和 appcompat的主題屬性,例如:在灰暗風格中,android:textColorPrimary 應該被設置為明亮的,而在明亮風格中,android:textColorPrimary則應該是灰暗的。按照常用的命名習慣,我們在這里將用相反的后綴來區分可替代的主題顏色。

溫馨小提示

在某些情況下,一種顏色能同時在明亮風格和灰暗風格的主題中被使用,這當然是喜聞樂見的情況,但是在大部分主題中這并不能夠實現。所以我希望你在設計主題的過程中,通過在 AndroidManifest.xml 中短暫地切換你應用里正在使用的可替代主題,以此確定你的主題是否需要添加其他的 colors/style 文件來滿足你的主題設計需求。

特定的主題資源文件

到了現在,我相信我們都能很輕松地為我們的 App 設計出美如畫的灰暗風格主題,但這里還存在一些小麻煩,例如:用于美化 action bar 菜單選項的 drawables 資源文件。灰暗風格的 action bar 需要用明亮的顏色修飾它的菜單選項,反之亦然。為了讓 Android 能夠在不同的App主題下區分不同的 drawables 資源文件,我們創建了能夠指定正確資源文件的 自定義屬性 引用,并且在不同的主題下提供了不同的 drawable 引用,將其值賦給特定的自定義屬性。(溫婉如妻,appcompat 庫貼心地為我們準備了類似 colorPrimary 的自定義屬性值)

values/attrs.xml


    <attr name="themedMenuStoryDrawable" format="reference" />
    <attr name="themedMenuCommentDrawable" format="reference" />
    ...

values/styles.xml


    <style name="AppTheme" parent="Theme.AppCompat.Light">
    <!-- original theme attributes -->
    ...
      <item name="themedMenuStoryDrawable">@drawable/ic_subject_white_24dp</item>
      <item name="themedMenuCommentDrawable">@drawable/ic_mode_comment_white_24dp</item>
    </style>

    <style name="AppTheme.Dark" parent="Theme.AppCompat">
    <!-- alternative theme attributes -->
    ...
         <item name="themedMenuStoryDrawable">@drawable/ic_subject_black_24dp</item>
         <item name="themedMenuCommentDrawable">@drawable/ic_mode_comment_black_24dp</item>
    </style>

menu/my_menu.xml

    <menu xmlns:android="http://schemas.android.com/apk/res/android">
        <item android:id="@id/menu_comment"
    android:icon="?attr/themedMenuCommentDrawable" />
        <item android:id="@id/menu_story"
    android:icon="?attr/themedMenuStoryDrawable" />
        <item android:id="@id/menu_share"
    app:actionProviderClass="android.support.v7.widget.ShareActionProvider" />
    </menu>

根據你實際的主題設計需要,類似的實現也能被用于為大多數自定義屬性指定相應的資源值。但這個方法存在一個問題:根據實際的需要從 drawable 資源文件中解析相應的屬性值,并應用于主題的方法在API 21之前的版本似乎都不可行。舉例來說明這個問題吧:如果你有一個 layer-list 中包含了各種你所需要的 color 的 drawable 資源文件,在API 21之前的版本中,這些 color 的值都應該是固定的,而不是能夠在App運行過程中不斷變化的。這個問題在 Google I/O 2014 大會上有被提出,并要求給出相應的解決辦法。(詳情參見 Click Me!)。

此外,為了避免在不同的主題中重復使用相同的資源文件,我們可以利用 drawable 的 tint 屬性解決這個需求。雖然這個屬性可以在API 21之后的版本中使用。但 Dan Lew 在他的博客中為我們介紹了怎么在所有的 API 版本中使用 tint 屬性。但就個人偏好來說,如果可以的話,我會更傾向于選擇不受 View 邏輯影響的 Java 實現,所以我選擇為每一個主題提供不同的 drawable 資源文件。

動態主題切換

現在我們已經有兩個可以使用的主題了,接下來我們需要做的就是讓用戶能夠在使用 App 時能夠自如地根據他們的個人偏好切換不同的主題。要實現這個功能,我們可以通過使用 SharedPreferences 來實現,通過改變 pref_dark_theme 的值去存儲當前被選擇的主題并決定我們的 App 將要被切換成什么主題。但從實際情況來考慮,主題切換后,App 所有 Activity 的 View 在被創建之前都應該被改變,所以我們只需要在 onCreate()方法中實現我們的邏輯。

BaseActivity.java

    public abstract class BaseActivity extends ActionBarActivity {
         @Override
         protected void onCreate(Bundle savedInstanceState) {
             if (PreferenceManager.getDefaultSharedPreferences(this)
                 .getBoolean("pref_dark_theme"), false)) {
               setTheme(R.style.AppTheme_Dark);
            }
        super.onCreate(savedInstanceState);
        }
    }

在這里,因為我們的 App 已經使用了默認的明亮風格主題,所以我們只需要檢查默認的引用是否被重載,是否被用于重載灰暗風格的主題。為了默認的引用能夠被所有 Activity共享,其中的邏輯已經在 "base" Activity中被寫好了。

值得注意的是,這個方法只能被用于改變沒有處在 back stack 中的 Acitivity 的主題。而那些已經在 back stack 中的 Activity,仍然會顯示為之前的主題,因為當我們結束當前 Activity,返回到上一個 Activity,只會觸發 onResume() 方法,而不是我們期望的 onCreate()方法。因此,考慮到實際的產品功能設計需求,我們當然要解決這些“過時”的 Activity 了,我在這里為大家提供了兩種解決辦法,都挺簡單的:一方面,我們可以清空我們的 back stack;另一方面,一旦 preference 被改變,我們就在 back stack 中按照順序讓所有 Acitivty 出棧后重新加載,將所有 Activity 的主題改變后再重新入棧。在這里為了簡便,我們選擇的實現方法是:當主題被改變,我們就簡單地清空 back stack,然后重啟當前的 Activity。

SettingsFragment.java

    public class SettingsFragment extends PreferenceFragment {
        ...

        @Override
        public void onActivityCreated(Bundle savedInstanceState) {
            super.onActivityCreated(savedInstanceState);

            mListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
            @Override
            public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
                if (!key.equals("pref_dark_theme")) {
                    return;
                }

            getActivity().finish();

            final Intent intent = getActivity().getIntent();
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | IntentCompat.FLAG_ACTIVITY_CLEAR_TASK);
            getActivity().startActivity(intent);
            }
        };
    }

    ...

雖然結束得有些突然,但我們今天的講解就到此結束啦。現在我們的 App 擁有了兩個這么優雅的主題,就算是挑剔的文藝小清新也不會嫌棄我們的 App 很 low 了吧!如果你想要了解整個 Materialistic 的具體實現,或者是這個功能的源碼,可以來我的 GitHub 上獲取哦~


所屬標簽

無標簽

25选5玩法中奖