【译】Android Styling 1: Themes vs Styles

2,702

原文:Android Styling: Themes vs Styles
作者:Nick Butcher
译者:Fly_with24


题图来自 Virginia Poltrack

B站官方视频


Android styling system 提供了一种强大的方式来指定应用程序的视觉设计,但很容易被滥用。正确地使用它可以使 theme 和 style 更易于维护,使品牌更好地更新并且直接支持暗黑模式。这是我和 Chris Banes 揭开 Android styling system 神秘面纱系列文章的第一篇,这样您就可以更轻松地打造一款时尚的 app

在第一篇文章中,我们来聊一聊 Android styling system 的组成部分:Theme 和 Style

Theme != Style

Theme 和 Style 都使用 <style> 标签,但它们的用途截然不同。您可以将它们视为一个 key-value 模型,其中 key 是属性,而 value 代表资源。

Style 是什么?

Style 是 view 属性的集合。您可以将 style 视为 Map<view attribute, resource>。这里的 key 是 view 的所有属性,例如控件声明并且开发者可以在布局文件中配置的属性。Style 支持特定类型的控件,因为不同的控件有着不同的属性集:

Style 是 view 属性的集合;特定于单一类型的控件

<!-- 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>

Style 中的每一个 key 都是可以在 layout 文件中配置的

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

将它们抽取为 style 可以更方便地在多个 view 中复用和维护

Style 的使用

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

View 只能使用一种 Style,这与其他的 styling systems 不同(例如 Web 上的 CSS,在该系统中,组件可以设置多个 CSS 样式)

Style 的作用范围

一个 Style 只作用于其应用的 view,不包含它的任何子 view。

例如,存在一个 ViewGroup,其内部有三个 button。为 ViewGroup 配置 style 不会作用于这些 button。Style 提供的值会与在布局中直接设置的值组合(使用 styling precedence order

Theme 是什么?

Theme 是资源的集合,它可以被 style ,layout 或者其它引用。它为 Android 资源提供了语义明确的命名,例如 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>

这些被命名的资源被称为 theme 属性。Theme 是 Map<theme attribute, resource>。theme 属性不同于 view 属性,因为它们不是特定于单个 view 类型的属性,而是指向值的指针,这些指针在应用中的适用范围更广。theme 为这些已命名的资源提供了正确的值。在上面的示例中,colorPrimary 属性指定此主题的原色为蓝绿色。通过将这些资源抽取到一个 theme 中,我们可以提供不同的具体的值(例如 colorPrimary=orange 是不同的主题)

theme 是命名资源的集合,广泛应用于整个应用程序

theme 就像接口,面向接口编程能够让开发者将公共协议与具体实现解耦,从而允许开发者提供出不同的实现。theme 扮演类似的角色, 通过针对 theme 属性编写 layout 和 style,我们可以在不同的主题下使用它们,从而提供不同的具体资源

大致等效的伪代码:

/* 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)

Theme 的使用

您可以在具有 Context 的组件中使用 theme,例如 Activity ,View ,ViewGroup

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

<!-- AndroidManifest.xml -->
<applicationandroid:theme="@style/Theme.Plaid">
<activityandroid:theme="@style/Theme.Plaid.About"/>

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

您还可以通过用 ContextThemeWrapper 包装现有的 Context 来在代码中设置 theme,然后将其用于 inflate 布局等

Theme 的作用范围

Theme 可以作为 Context 的属性被获取,并且它可以从任何 Context 或 Context 的子类获得,例如 ActivityView,或者 ViewGroup。这些对象存在于一个「树」中,其中 Activity 包含 ViewGroup,ViewGroup 包含 View。在此树的任何级别上指定主题都会影响到其后代节点,例如在 ViewGroup 上设置 Theme 会作用域其所有子 View(这与只作用于单一 View 的 Style 相反)

<!-- Copyright 2019 Google LLC.	
   SPDX-License-Identifier: Apache-2.0 -->
<ViewGroupandroid:theme="@style/Theme.App.SomeTheme">
  <! - 该主题还会作用于所有子 View -->
</ViewGroup>

例如,如果您想让原本暗色的屏幕变暗,则这可能非常有用。 在下一篇文章(即将发布!)中了解有关此行为的更多信息。

请注意,它的行为仅适用于 layout inflation 的时刻。 尽管 Context 提供了 setTheme 方法,Theme 提供了 applyStyle 方法,但都需要在 inflation 之前调用。 在 inflation 之后设置新 theme 或应用 style 不会更新已存在的 view。

总结

理解 Style 和 Theme 的职责和作用有助于我们更好地管理资源

例如我们有一个蓝色主题的 app ,但是在 pro 版本我们想要一个紫色并且花哨的外观,并且需要提供暗黑主题。如果仅使用 Style 实现此需求,则必须为 pro/non-Pro,light/dark 创建四个 Style。由于 Style 只能作用于特定的 View(Button,Switch 等等),您需要为应用中的每种视图类型创建这些组合

不使用 theme

如果改为使用 Style 和 Theme,则可以将因 Theme 而变化的抽取为 Theme 属性,因此我们仅需要为每个视图类型定义一个 Style。 对于上面的示例,我们可以定义4个主题,每个主题为colorPrimary 主题属性提供不同的值,然后这些主题并自动反映出正确值

当您需要考虑 Style 和 Theme 的交互时,此方法可能看起来更复杂,但是它的好处是可以隔离每个主题中差异的部分。 因此,如果您的应用程序的主题色从蓝色改为橙色,则只需要在一个地方进行更改,而无需分散在整个 Style 中。 它还有助于避免 Style 的泛滥。 理想情况下,每种视图类型只具有少量样式。 如果您不利用主题,则您的 styles.xml 文件很容易失控并以相似样式的不同变体「爆炸」,这使维护工作变得头疼


感谢 Florina Muntenescu 和 Chris Banes

译文完


系列译文

关于我

我是 Flywith24,我的博客内容已经分类整理 在这里,点击右上角的 Watch 可以及时获取我的文章更新哦 😉