Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 讓你的Android應用能使用多種主題 ( Part 2 )

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

編輯:關於Android編程

 

In the first part of this post, we have created a light theme and made initial preparation to support multiple themes. In this blog post, we will continue that effort, creating another theme and allowing dynamic switching of themes during runtime.

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

Ideally, if we treat theme as a configuration, we should be able to specify theme-specific resources under a ‘theme-qualifier’ resources directory, e.g. values-dark for dark theme resources and values-light for light theme resources. Unfortunately, this is not yet an option at the time of this post.

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

So how should we specify resources for multiple themes? If we look at how resources are organized in appcompat, we will have a rough idea of how the Android team organize their theme specific resources. Materialistic also employs a similar approach.

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

Theming

主題設置

values/styles.xml


values/color.xml


...

...
...
...

Here we add a new dark theme called AppTheme.Dark, and for style and color consistency, we extend from appcompat’s theme Theme.AppCompat (a dark theme). Unfortunately, since our two themes extend two different base themes, we cannot share any common attributes (the same way a class in Java cannot extend two or more classes).

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

The two themes should have appropriate (different if applicable) values for base Android and appcompat theme attributes, e.g. android:textColorPrimary for dark theme should be light, and for light theme should be dark. By convention, here we suffix alternative theme colors with Inverse.

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

Tip

Try out your alternative theme by temporary switching android:theme for application in AndroidManifest.xml to see what extra colors/style you need to create. For certain cases a color may look okay in both dark and light theme.

溫馨小提示

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

Theme-specific resources

At this point, we should have a pretty decent dark theme for our app, except for some anomalies here and there, e.g. drawables used for action bar menu items. A dark action bar expects light-color menu items, and vice versa. In order to tell Android to use different drawables for different app themes, we create custom attributes that allow specifying reference to the correct drawable, and provide different drawable references as values for these custom attributes under different themes (the same way appcompat library provides custom attributes such as colorPrimary).

特定的主題資源文件

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

values/attrs.xml



...

values/styles.xml




	   

Similar implementation can be used to specify most custom attributes you need for theme specific resource values. One hiccup to this approach is that attribute resolving in drawable resources seems to be broken before API 21. For example, if you have a drawable which is a layer-list of colors, their values must be fixed for API <21. See this commit from Google I/O 2014 app for a fix.

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

An alternative approach to avoid duplicating drawable resources for different themes is to use drawable tint. This attribute is available from API 21. Dan Lew in his blog shows how to do this for all API levels. Personally I would prefer to keep my Java implementation free of view logic if possible, so I choose to have different drawable resources per theme.

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

Dynamic theme switching

Now that we have two polished themes ready to be used, we need to allow users to choose which one they prefer and switch theme dynamically during runtime. This can be done by having a SharedPreferences, says pref_dark_theme to store theme preference and use its value to decide which theme to apply. Application of theme should be done for all activies, before their views are created, so onCreate() is our only option to put the logic.

動態主題切換

現在我們已經有兩個可以使用的主題了,接下來我們需要做的就是讓用戶能夠在使用 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);
    }
}

Here, since our app already has a default light theme, we only need to check if default preference has been overriden to override dark theme. The logic is put in the ‘base’ activity so it can be shared by all activities.

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

Note that this approach will only apply theme for activities that are not in the back stack. For those that are already in current stack, they will still exhibit previous theme, as going back will only trigger onResume(). Depends on product requirements, the implementation to handle these ‘stale’ screens can be as simple as clearing the back stack, or restarting every single activity in the back stack upon preference change. Here we simply clear back stack and restart current activity upon theme change.

值得注意的是,這個方法只能被用於改變沒有處在 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);
        }
    };
}

...

So that’s it. Now we have an app with two polished themes for even the most picky users! Head over to hidroh/materialistic GitHub repository to checkout complete implementation!

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

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved