Android 樣式系統 | 主題背景和樣式

Android 提供了功能強大的樣式系統 (Android styling system) 來實現應用的視覺設計,但它也容易被誤用。正確地使用樣式系統會讓您在開發應用的時候更容易維護主題與樣式,在開發新功能的時候少一些抓狂,而且還可以支持深色模式。

 

本系列文章將由 Android 開發者關係團隊的工程師 Nick Butcher 和 Chris Banes 共同撰寫,與各位開發者們共同揭開 Android 樣式系統的神祕面紗,幫助您高效編寫時尚的應用界面。

在本系列的第一篇文章中,我會介紹樣式系統的基礎部件: 主題背景與樣式。

  • Android styling system

    https://developer.android.google.cn/guide/topics/ui/look-and-feel/themes

主題背景 != 樣式

主題背景與樣式都使用相同的 <style> 語法,但是它們所服務的目的截然不同,您可以把它們理解爲使用鍵值對 (Key-Value) 來存儲數據,其中鍵 (Key) 代表屬性,值 (Values) 代表資源,我們分別來看一下。

樣式 (Style) 裏有什麼?

樣式是 View 屬性 (View Attributes) 值的集合,您可以把它們理解爲 Map<view attribute, resource> 的結構。其中,一組鍵 (Key) 代表了所有的 View 屬性,這裏的 View 屬性指的是可以在佈局文件使用的 Widget 定義的屬性。一個樣式對應一種類型的 Widget,這是因爲不同的部件支持不同的屬性集合:

樣式是 View 屬性 (View Attributes) 值的集合;一個樣式對應一種類型的 Widget

<!-- Copyright 2019 Google LLC.  
   SPDX-License-Identifier: Apache-2.0 -->
<style name="Widget.Plaid.Button.InlineAction" parent="…">
  <item name="android:gravity">center_horizontal</item>
  <item name="android:textAppearance">@style/TextAppearance.CommentAuthor</item>
  <item name="android:drawablePadding">@dimen/spacing_micro</item>
</style>

正如您所見,樣式中的每一個鍵 (Key) 其實就是您可以在佈局中設置的內容:

<!-- Copyright 2019 Google LLC.  
   SPDX-License-Identifier: Apache-2.0 -->
<Button …
  android:gravity="center_horizontal"
  android:textAppearance="@style/TextAppearance.CommentAuthor"
  android:drawablePadding="@dimen/spacing_micro"/

把這些提煉成樣式,可以讓您方便地在多個 View 中複用同一個樣式,而且還容易維護。

使用方法

佈局文件中的每一個獨立的 View 都可以使用樣式:

<!-- Copyright 2019 Google LLC.  
   SPDX-License-Identifier: Apache-2.0 -->
<Button …
  style="@style/Widget.Plaid.Button.InlineAction"/>

一個 View 只能使用一個樣式,可以將其與 Web 技術中使用到的 CSS 樣式系統相比較,CSS 樣式系統可以允許一個組件使用多個 CSS 類。

範圍

樣式只有在使用它的 View 上才起作用,如果該 View 包含子 View,那麼在這些子 View 上樣式是無效的。舉個例子,如果您的 ViewGroup 有三個按鈕,設置 InlineAction 樣式到此 ViewGroup 時,只針對這個 ViewGroup 有效,而對它的三個按鈕來說是無效的。樣式中定義的值與佈局文件中設置的值會融合在一起 (解決方法見這篇文章: 使用樣式優先級順序)。

  • 使用樣式優先級順序

    https://medium.com/androiddevelopers/whats-your-text-s-appearance-f3a1729192d

什麼是主題背景?

主題背景是一組命名的資源的集合,這些資源可以被樣式或者佈局文件等引用。它們提供了一種對 Android 資源的語義名稱 (Sematic name),能夠讓您在其他地方引用這些資源。例如 colorPrimary 就是對一個給定顏色的語義名稱。

<!-- Copyright 2019 Google LLC.  
   SPDX-License-Identifier: Apache-2.0 -->
<style name="Theme.Plaid" parent="…">
  <item name="colorPrimary">@color/teal_500</item>
  <item name="colorSecondary">@color/pink_200</item>
  <item name="android:windowBackground">@color/white</item>
</style>

主題背景是由 Map<theme attribute, resource> 結構組成,這些標有名字的資源被稱爲主題背景屬性。主題背景屬性跟 View 屬性不一樣,這是因爲它們不是特定 view 類型的屬性而是對一個值的命名,其在應用中有更廣泛的用途。主題背景屬性爲這些標有名字的資源提供了具體的值,在上面的例子中 colorPrimary 屬性爲這個主題背景設置了具體的值,也就是青綠色 (teal)。通過把主題背景中的資源抽象化,我們可以爲不同的主題背景提供不同的值,比如: colorPrimary=orange。

主題背景是一個命名的資源集合,在應用中有更廣泛的用途

主題背景類似於接口 (Interface),在接口的編程中它允許您爲公共接口提供不同的實現方法。主題扮演了一個類似的角色,針對主題屬性編寫佈局和樣式,我們可以在不同的主題下使用它們,從而提供不同的具體資源。

簡化的僞代碼如下:

/* Copyright 2019 Google LLC.  
   SPDX-License-Identifier: Apache-2.0 */
interface ColorPalette {
  @ColorInt val colorPrimary
  @ColorInt val colorSecondary
}


class MyView(colors: ColorPalette) {
  fab.backgroundTint = colors.colorPrimary
}

這會讓您使用同一套代碼可渲染出不同的 MyView 效果,而無需新建構建變體。

/* Copyright 2019 Google LLC.  
   SPDX-License-Identifier: Apache-2.0 */
val lightPalette = object : ColorPalette { … }
val darkPalette = object : ColorPalette { … }
val view = MyView(if (isDarkTheme) darkPalette else lightPalette)

使用方法

您可以把一個主題背景設置給一個組件,這個組件可以包含 Context 或者它本身就是 Context,比如: Activity 或者是 View/ViewGroups。

<!-- Copyright 2019 Google LLC.  
   SPDX-License-Identifier: Apache-2.0 -->


<!-- AndroidManifest.xml -->
<application …
  android:theme="@style/Theme.Plaid">
<activity …
  android:theme="@style/Theme.Plaid.About"/>


<!-- layout/foo.xml -->
<ConstraintLayout …
  android:theme="@style/Theme.Plaid.Foo">

您還可以使用 ContextThemeWrapper 類把一個主題背景設置到已經存在的 Context 上,這時候您可使用 inflate 方法創建佈局。

  • ContextThemeWrapper

    https://developer.android.google.cn/reference/android/view/ContextThemeWrapper.html#ContextThemeWrapper(android.content.Context,%20int)

  • inflate

    https://developer.android.google.cn/reference/android/view/LayoutInflater.html#from(android.content.Context)

主題背景的使用效果取決於您的使用方式,您可以通過引用主題背景屬性來創建靈活的 Widget。不同的主題背景可以在未來再提供具體的值,比如爲 View 層級結構中的某個部分設置背景顏色。

<!-- Copyright 2019 Google LLC.  
   SPDX-License-Identifier: Apache-2.0 -->
<ViewGroup …
  android:background="?attr/colorSurface">

除了用常量值設置一個顏色 (#ffffff 或者 @color 資源),我們還可以通過 ?attr/themeAttributeName 語法委託給主題背景來完成。

這個語法表示通過指定的屬性名稱,從主題背景中獲取相應的值。這種級別的解耦方式可以讓我們提供不同的程序行爲 (比如: 在深色模式與淺色模式下提供不同的背景顏色),而不用創建多個相似但僅有一小部分不一樣的佈局或者樣式,它將主題中的可變元素分離了出來。

通過使用 ?attr/themeAttributeName 語法獲得此主題背景中的語義屬性代表的值

範圍

任何一個帶有 Context (如 Activity, View or ViewGroup) 的對象 (Object) 都可以通過訪問 Context 的屬性來訪問主題背景。這些對象以樹的形式組織而成,比如 Activity 包含 ViewGroup,而 ViewGroup 又包含 View。把主題背景設置到一個樹狀結構的任意一層,此層及下一層都會受到影響。比如把主題背景設置給一個 ViewGroup,此 ViewGroup 包含的所有子 View 都會受到這個主題背景的影響。(而樣式恰好相反,它只對被設置的 View 起作用)

<!-- Copyright 2019 Google LLC.  
   SPDX-License-Identifier: Apache-2.0 -->
<ViewGroup …
  android:theme="@style/Theme.App.SomeTheme">
  <! - SomeTheme also applies to all child views. -->
</ViewGroup>
  • Context

    https://developer.android.google.cn/reference/android/content/Context

  • 主題背景

    https://developer.android.google.cn/reference/android/content/res/Resources.Theme.html

如果您想在淺色屏幕中獲取一個由深色主題背景構成的區域,那這個功能會非常有用。更多內容請參見本系列下一篇文章,我們會在之後更新。

請注意,這種功能僅在初始化佈局的時候生效。在初始化佈局之前需要調用 Context 提供的 setTheme 方法或者是主題背景提供的 applyStyle 方法。佈局初始化完畢之後再調用 setTheme 或者 applyStyle 方法,此時對已有的 View 不會造成任何改變。

  • setTheme

    https://developer.android.google.cn/reference/android/content/Context#setTheme(int)

  • applyStyle

    https://developer.android.google.cn/reference/android/content/res/Resources.Theme?hl=en#applyStyle(int,%20boolean)

不同的關注點

瞭解主題背景與樣式的不同目的與使用方法,會讓您更方便地管理樣式資源。

舉個例子,假設您的應用有一個藍色主題背景,但某些 Pro 界面需要有花俏的紫色,而且您還想要提供一個調色過的深色主題。如果您只使用樣式來實現這個效果,需要分別爲 Pro/non-Pro 和 light/dark 創建四個不同的樣式。由於樣式是特定於一個視圖類型 (按鈕、開關等),因此您需要爲應用中的每一種 View 類型創建這四個樣式。

△ 不含主題的 widgets 或樣式的擴展組合

如果改爲使用樣式和主題背景,則可以將因主題背景變化而發生改變的部分封裝爲主題背景屬性,因此我們僅需要爲每種 View 類型定義一個樣式。對於上面的示例,我們可以定義 4 個主題背景,爲其中的 colorPrimary 主題背景屬性提供不同的值,之後當樣式引用這些主題的屬性時會自動得到正確的值。

混合使用主題背景與樣式的方法可能看起來相比之前更復雜了,但是它的好處是把每個主題變化的部分封裝了起來。

因此,當您需要把程序的界面從藍色改爲橙色時,只需要修改一個地方就夠了,而不需要修改多個樣式。它還有助於您避免發生樣式氾濫。

理想情況下,針對一個視圖類型,您應該只有少數幾種樣式。如果不使用主題背景,您爲幾個長得類似的樣式創建不同的擴展版本時,就會使得 styles.xml 文件很大,維護起來會非常頭疼。

 

下一篇文章,我們將會跟大家共同探索主題背景的公共屬性以及如何創建您自己的主題背景,敬請關注。


推薦閱讀




 點擊屏末  | 查看 Android 官方中文文檔《樣式和主題背景》


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章