乐趣区

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

Android 提供了功能强大的款式零碎 (Android styling system) 来实现利用的视觉设计,但它也容易被误用。正确地应用款式零碎会让您在开发利用的时候更容易保护主题与款式,在开发新性能的时候少一些抓狂,而且还能够反对深色模式。

本系列文章将由 Android 开发者关系团队的工程师 Nick Butcher 和 Chris Banes 独特撰写,与各位开发者们独特揭开 Android 款式零碎的神秘面纱,帮忙您高效编写时尚的利用界面。

在本系列的第一篇文章中,我会介绍款式零碎的根底部件: 主题背景与款式。

主题背景 != 款式

主题背景与款式都应用雷同的 <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 无效,而对它的三个按钮来说是有效的。款式中定义的值与布局文件中设置的值会交融在一起 (解决办法见这篇文章: 应用款式优先级程序)。

什么是主题背景?

主题背景是一组命名的资源的汇合,这些资源能够被款式或者布局文件等援用。它们提供了一种对 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) 办法创立布局。

主题背景的应用成果取决于您的应用形式,您能够通过援用主题背景属性来创立灵便的 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 提供的 setTheme) 办法或者是主题背景提供的 applyStyle
) 办法。布局初始化结束之后再调用 setTheme 或者 applyStyle 办法,此时对已有的 View 不会造成任何扭转。

不同的关注点

理解主题背景与款式的不同目标与应用办法,会让您更不便地治理款式资源。

举个例子,假如您的利用有一个蓝色主题背景,但某些 Pro 界面须要有花俏的紫色,而且您还想要提供一个调色过的深色主题。如果您只应用款式来实现这个成果,须要别离为 Pro/non-Pro 和 light/dark 创立四个不同的款式。因为款式是特定于一个视图类型 (按钮、开关等),因而您须要为利用中的每一种 View 类型创立这四个款式。

△ 不含主题的 widgets 或款式的扩大组合

如果改为应用款式和主题背景,则能够将因主题背景变动而产生扭转的局部封装为主题背景属性,因而咱们仅须要为每种 View 类型定义一个款式。对于下面的示例,咱们能够定义 4 个主题背景,为其中的 colorPrimary 主题背景属性提供不同的值,之后当款式援用这些主题的属性时会主动失去正确的值。

混合应用主题背景与款式的办法可能看起来相比之前更简单了,然而它的益处是把每个主题变动的局部封装了起来。

因而,当您须要把程序的界面从蓝色改为橙色时,只须要批改一个中央就够了,而不须要批改多个款式。它还有助于您防止产生款式泛滥。

现实状况下,针对一个视图类型,您应该只有少数几种款式。如果不应用主题背景,您为几个长得相似的款式创立不同的扩大版本时,就会使得 styles.xml 文件很大,保护起来会十分头疼。

下一篇文章,咱们将会跟大家独特摸索主题背景的公共属性以及如何创立您本人的主题背景,敬请关注。

退出移动版