在 Android 樣式系統(tǒng)系列的前幾篇文章中,我們探討了 樣式和主題背景之間的區(qū)別,討論了 使用主題背景和主題背景屬性的好處胚鸯,并重點介紹了一些 常用的主題背景屬性钻弄。
今天乖坠,我們聚焦于主題背景的實際使用矮台,如何將它們應用到我們的應用中漓概,以及如何構建主題背景漾月。
范圍
在 上一篇文章 中,我們提到:
任何一個擁有或者自己本身就是 Context (如 Activity胃珍,View or ViewGroup) 的對象都可以通過訪問 Context 的屬性來獲取 主題背景梁肿。這些對象以樹的形式組織而成,比如 Activity 包含 ViewGroup觅彰,而 ViewGroup 又包含 View吩蔑。把主題背景設置到一個樹狀結構的任意一層,此層及下一層都會受到影響填抬。比如在 ViewGroup 上設置一個主題背景烛芬,此 ViewGroup 包含的所有子 View 都會受到這個主題背景的影響。(只適用于單個 View 的樣式則恰恰相反)
在樹結構中的任何層級上設置主題背景痴奏,都不會替換當前生效的主題背景蛀骇,但會將其覆蓋 (Overlay)。一起看看下面這個 Button读拆,該 Button 設置了一個主題背景擅憔,但是它父結構也指定了一個主題背景:
<!-- Copyright 2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
<ViewGroup …
android:theme="@style/Theme.App.Foo">
<Button …
android:theme="@style/Theme.App.Bar"/>
</ViewGroup>
如果在兩個主題背景中都指定了同一屬性,則最鄰近的 (local) 設置會生效檐晕,即 Bar 中的設置被應用于該 Button暑诸。任何在主題背景 Foo 中有指定,但是在主題背景 Bar 中未指定的屬性也被應用于此 Button辟灰。
這或許是一個不太恰當的例子个榕,但樣式化應用中不同外觀的子區(qū)域時,這項技術的價值則被凸顯出來芥喇。例如西采,淺色內容上有深色的工具欄,或者該界面 (比如继控,Owl 示例應用) 中顯示了大面積的粉色主題背景但顯示相關內容的底部具有藍色主題背景:
通過在藍色分區(qū)的根部 (Root) 設置主題背景的方式械馆,可級聯到它所有的子視圖。
過度重疊
由于主題背景會覆蓋樹結構中更高一級的主題背景武通,因此請務必留意主題背景所指定的內容霹崎,以此避免它意外替換您本想要保留的屬性。例如冶忱,您可能只是想改變視圖 (View) 的背景顏色 (通常由 colorSurface 控制)尾菇,即,您不打算更新該主題背景的其他部分∨晌埽基于此劳淆,您可以試試主題背景覆蓋 (Theme Overlay) 的技術。
設計這些主題背景的目的是用于覆蓋其他主題背景千埃。它們的作用范圍需要盡可能的狹小憔儿,也就是說,它們僅定義 (或繼承) 最小化的屬性放可。實際上谒臼,主題背景覆蓋通常 (但并不總是) 是沒有父級的,例如:
<!-- Copyright 2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
<style name="ThemeOverlay.MyApp.DarkSurface" parent="">
<item name="colorSurface">#121212</item>
</style>
主題背景覆蓋是限定范圍的主題背景耀里,定義的屬性要越少越好蜈缤,它的作用只是為了覆蓋另外一個主題背景
按照慣例,我們以 "ThemeOverlay" 為前綴給這些主題背景覆蓋起名字冯挎。MDC (和 AppCompat) 提供了許多有用的主題背景覆蓋 (Theme Overlay)底哥,您可以使用它們來把應用程序子區(qū)域的顏色從淺色轉換到深色:
根據定義,主題背景覆蓋不會指定很多內容房官,同時也不應單獨使用趾徽。例如,作為您 Activity 的主題背景翰守。實際上孵奶,您可以認為在應用中可以使用兩種 "類型" 主題:
- "完整" 主題背景。 它們定義了一個屏幕所需的一切蜡峰。它們繼承了另一個 "完整" 主題背景 (如了袁,Theme.MaterialComponents),因此可以將其設置為 Activity 主題背景湿颅。
- 主題背景覆蓋载绿。 僅應用于 "完整" 的主題背景。由于其不會指定重要且必要的信息油航,因此不應該單獨使用崭庸。
永遠存在
總會有一個有效的主題背景,即使您未在應用中的任何地方指定一個主題背景谊囚,您也會繼承 默認主題怕享。因此,上面的示例只是一種簡化秒啦,因此您絕對不應該在 View 中使用一個 "完整" 的主題背景熬粗,而應使用主題背景覆蓋:
<!-- Copyright 2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
<ViewGroup …
- android:theme="@style/Theme.App.Foo">
+ android:theme="@style/ThemeOverlay.App.Foo">
<Button …
- android:theme="@style/Theme.App.Bar"/>
+ android:theme="@style/ThemeOverlay.App.Bar"/>
</ViewGroup>
這些主題背景覆蓋不會孤立地存在搀玖,因為它們本身會被外圍的 Activity 的主題背景所覆蓋余境。
成本效益
使用主題背景需要一些運行時的代價。每次您聲明 android:theme 時,您都在創(chuàng)建一個新的 ContextThemeWrapper芳来,它會分配新的主題背景 (Theme) 和資源 (Resources) 實例含末。它還需要解決多層級樣式化的間接引用問題。
注意不要過度使用主題即舌,您應該監(jiān)控它們的影響佣盒,特別是在重復使用的情況下,例如: RecyclerView 項的布局或者配置文件顽聂。
在上下文中使用
我們曾說過主題背景與 Context 相關聯肥惭,這意味著,如果您在代碼中使用 Context 來獲取資源 (Resource)紊搪,請確保您使用的是正確的 Context蜜葱。例如,您可以在代碼中的某個位置獲取 Drawable:
someView.background = AppCompatResources.getDrawable(requireContext(), R.drawable.foo)
如果 Drawable 引用了主題背景屬性 (所有的 Drawable 從 API 21+ 開始生效耀石,VectorDrawables 可以通過 Jetpack 從 API 14+ 開始生效)牵囤,則應確保使用正確的 Context 來加載 Drawable。如果不清楚 Context 是否正確的話滞伟,您可能會遇到在嘗試應用背景主題到子層級時不生效的情況揭鳞,屆時您可能會陷入困惑并且搞不清楚究竟發(fā)生了什么。例如梆奈,如果您使用 Fragment 或 Activity 的 Context 來加載 Drawable野崇,應用在樹結構底層的主題背景就會失效。最佳做法是鉴裹,應使用離資源 (Resource) 最近的 Context:
someView.background = AppCompatResources.getDrawable(someView.context, R.drawable.foo)
誤用
我們已經討論了樹結構中存在的主題背景和 Context: Activity > ViewGroup > View 等舞骆。將這種思維模型擴展到 Application 級,聽起來很吸引人——畢竟您可以在 manifest 中通過 <application> 標簽指定一個主題背景径荔。千萬不要被愚弄督禽!
Application Context 不保留任何主題背景相關信息,您在 manifest 中設置的主題背景僅用作未明確設置主題背景的 Activity 的默認選擇总处。因此狈惫,您絕不要在 Application Context 中 加載資源 (如 Drawable 或者顏色,因為它們可能因主題背景不同而不同) 或者用來解析主題背景屬性鹦马。
切勿使用 Application Context 加載可使用的資源
這也是為什么我們把 "完整" 主題背景應用到 Activity 胧谈,并從 Application 主題背景維度對這種組織結構進行了擴展。<activity> 級別的主題背景不會覆蓋 <application> 級別的主題背景荸频。
強調
希望這篇文章已經解釋清楚了主題背景覆蓋在樹結構中的功能菱肖,以及在樣式化我們 App 的時候如何使用這個功能。使用 android:theme 標簽為布局中的分段設置主題背景旭从,并僅在您需要調整屬性的地方使用主題背景覆蓋稳强。請注意使用正確的主題背景和 Context 來加載資源场仲,并謹慎使用 Application Context!