乐趣区

关于android:Android-样式系统-主题背景覆盖

在 Android 款式零碎系列的前几篇文章中,咱们探讨了 款式和主题背景之间的区别,探讨了 应用主题背景和主题背景属性的益处,并重点介绍了一些 罕用的主题背景属性。

明天,咱们聚焦于主题背景的理论应用,如何将它们利用到咱们的利用中,以及如何构建主题背景。

范畴

在 上一篇文章 中,咱们提到:

任何一个领有或者本人自身就是 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。

笼罩了各自的主题背景

这或者是一个不太失当的例子,但款式化利用中不同外观的子区域时,这项技术的价值则被凸显进去。例如,浅色内容上有深色的工具栏,或者该界面 (比方,Owl 示例利用) 中显示了大面积的粉色主题背景但显示相干内容的底部具备蓝色主题背景:

粉色主题背景屏幕中的蓝色子区域

通过在蓝色分区的根部 (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),您能够应用它们来把应用程序子区域的色彩从浅色转换到深色:

  • ThemeOverlay.MaterialComponents.Dark
  • ThemeOverlay.MaterialComponents.Light

依据定义,主题背景笼罩不会指定很多内容,同时也不应独自应用。例如,作为您 Activity 的主题背景。实际上,您能够认为在利用中能够应用两种 “ 类型 ” 主题:

  1. “ 残缺 ” 主题背景。 它们定义了一个屏幕所需的所有。它们继承了另一个 “ 残缺 ” 主题背景 (如,Theme.MaterialComponents),因而能够将其设置为 Activity 主题背景。
  2. 主题背景笼罩。 仅利用于 “ 残缺 ” 的主题背景。因为其不会指定重要且必要的信息,因而不应该独自应用。

永远存在

总会有一个无效的主题背景,即便您未在利用中的任何中央指定一个主题背景,您也会继承 默认主题。因而,下面的示例只是一种简化,因而您相对不应该在 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 时,您都在创立一个新的 ContextThemeWrapper,它会调配新的主题背景 (Theme) 和资源 (Resources) 实例。它还须要解决多层级款式化的间接援用问题。

留神不要适度应用主题,您应该监控它们的影响,特地是在重复使用的状况下,例如: 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 是否正确的话,您可能会遇到在尝试利用背景主题到子层级时不失效的状况,届时您可能会陷入困惑并且搞不清楚到底产生了什么。例如,如果您应用 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!

退出移动版