原文:Android Styling: Themes Overlay
作者:Nick Butcher
译者:Fly_with24
题图来自 Virginia Poltrack
在 Android styling 系列文章中,我们研究了 style 与 theme 的区别,讨论了 使用主题和主题属性的优势,并且介绍了 常用的属性
今天,我们将集中讨论主题的实际使用,如何将其应用到您的应用程序中
在 第一篇文章 中我们提到
Theme
可以作为Context
的属性被获取,并且它可以从任何 Context 或 Context 的子类获得,例如Activity
,View
,或者ViewGroup
。这些对象存在于一个「树」中,其中 Activity 包含 ViewGroup,ViewGroup 包含 View。在此树的任何级别上指定主题都会影响到其后代节点,例如在 ViewGroup 上设置 Theme 会作用域其所有子 View(这与只作用于单一 View 的 Style 相反)
在此「树」中的任何一层设置主题都不会 「替换 」当前有效的主题,而是将其 「覆盖」。下面的例子中有一个按钮,该按钮可以选择一个主题,但它的 parent 也可以指定一个主题:
<!-- 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> 复制代码
如果在两个主题中都指定了属性,则最本地的 「获胜」,即 主题Bar
将应用于按钮。 在主题 Foo
中指定但 未 在主题 Bar
中指定的任何属性也将应用于按钮
这可能看起来像是脱离实际的示例,但是有些场景特别有用。例如在浅色屏幕上的深色 Toolbar,或者这个界面(来自 Owl sample app),它大部分是粉色主题,但是底部是一个蓝色主题
这可以通过在蓝色部分的 root 设置主题,它的子 view 都会受此影响
由于主题会覆盖其树中 parent 的主题,确保它不会意外地替换您想要保留的属性十分重要。例如,您可能想要改变 view 的背景色(通常由 colorSurface
控制)不做其他更改,即您想保留当前主题的剩余部分。对于这种场景,我们可以使用一种叫做 theme overlays
的技术
这些主题的作用是与其他主题合并。它们的范围可能很狭窄,即它们仅定义(或继承)尽可能少的属性。theme overlays
经常(并非总是)没有 parent
<!-- Copyright 2019 Google LLC. SPDX-License-Identifier: Apache-2.0 --> <style name="ThemeOverlay.MyApp.DarkSurface" parent=""> <item name="colorSurface">#121212</item> </style> 复制代码
theme overlays
的作用范围十分小,它十分克制地定义了尽可能少的属性,目的是为了与其它主题合并共同作用
按照惯例,我们以 ThemeOverlay 作为命名前缀。MDC 和 AppCompat 提供了很多方便的 theme overlays
,您可以使用它们为程序的特定部分的颜色做从浅色到深色的转换
根据定义,theme overlays
不会指定很多内容,因此它不应被单独使用。例如,将其用作 activity 的主题。事实上,您可以在 app 中使用两种类型的主题:
Theme.MaterialComponents
。它们应该在 activity 中使用总会有一个生效的主题,即使您未在应用中的任何地方指定一个主题,也会继承默认主题。 因此,上面的示例只是一种简化,因此您绝对不应在 View 中使用 full theme,而是应该使用 theme overlays
使用主题会在运行期产生一定的开销。每当您声明一个 android:theme
,您都在创建一个新的 ContextThemeWrapper 来分配新的 Theme
和 Resource
实例。它还间接引入了更多层的 styling。请谨慎使用主题,尤其在 RecyclerView 这种重复使用的场景下
我们在前文提到主题与 Context 关联——这意味着如果您正在使用 context 来检索代码中的资源,那么请注意使用正确的 context 。例如您在某个位置获取 Drawable
如果 drawable 引用了主题属性(所有的 Drawable 可以在 API 21+ 使用,VectorDrawable 可以通过 Jetpack 在 API 14+ 中使用),则应确保 Drawable 使用了正确的 context。如果您用错了,则可能发现在子节点设置的主题在 Drawable 中不符合预期。例如如果您使用了 Fragment 或者 Activity 的 Context 加载 Drawable,它不会使用当前树的子节点的主题,而是使用最接近传入 Context 的资源
我们已经讨论的「树」中的主题和上下文:Activity > ViewGroup > View。我们很自然的会将这个模式套用在 Application
类中,毕竟您可以在 <application> 标签中指定主题。但别被忽悠了!
Application Context
不包含任何主题信息,您在 manifest 文件中的 <application> 标签设置主题仅作用于那些没指定主题的 Activity。因此不要使用 Application Context 加载那些因主题而异的 资源(drawable 或 color)或者解析主题属性
切勿使用 Application Context 来加载可以的资源
这也是为什么我们为 Activity 指定 full theme,而不会覆盖在 <application> 标签设置的主题
本文解释了 themes overlay 的原理和使用。在你的布局中使用 android:theme
标签配置主题,并使用 theme overlays 来调整您需要的属性。请注意使用正确的主题和 context 来加载资源并小心 application context!
感谢 Florina Muntenescu 和 Chris Banes
译文完
本系列完
【译】Android Styling 1: Themes vs Styles
【译】Android Styling 2: 常用主题属性
【译】Android Styling 3: 使用主题和主题属性的优势
【译】Android Styling 4: 主题实战
我是 Flywith24
掘金
简书
Github