安卓风格:偏爱主题属性
主题归属的所有事情
在本系列关于Android风格设计的前几篇文章中,我们探讨了主题和风格之间的区别,以及为什么要把你希望通过主题和常用主题属性来改变的东西考虑进去。
Android造型。主题与风格 安卓风格系统提供了一种强大的方式来指定你的应用程序’的视觉设计,但它可能很容易被滥用…。
Android造型。常见的主题属性 在这一系列关于Android风格的文章中,我们看了主题和风格的区别,以及…。
这使我们能够创建更少的布局或样式,在一个主题内隔离变化。在实践中,你很大程度上想通过主题来改变颜色,因此你应该总是*通过主题属性来引用颜色。
Always* refer to colors via theme attributes
这意味着你可以认为像这样的代码是一种气味。
<!-- Copyright 2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
<View …
android:background="@color/white"/>
相反,你应该引用一个主题属性,允许你根据主题来改变颜色,例如,在黑暗主题中提供一个不同的值。
<!-- Copyright 2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
<View …
android:background="?attr/colorSurface"/>
即使你目前不支持备用主题(什么--不支持黑暗主题?),我也会建议你遵循这种方法,因为它将使采用主题化变得更加容易。
合格的颜色吗?
你可以通过在不同的配置中提供替代值来改变颜色(例如@color/foo
在res/values/colors.xml
中都定义了,而在res/values-night/colors.xml
中设置了一个替代值),但我’建议使用主题属性来代替。
在颜色层变化迫使你为颜色提供语义名称,即你可能不会将一种颜色命名为@color/white
,并在-night
配置中提供一个深色变体&mdash;这将是非常混乱的。相反,你可能会倾向于使用一个语义的名字,比如@color/background
。这样做的问题是,它结合了颜色的声明和提供值。因此,它没有说明这可以或将会因主题而变化。
变化的@colors
也可以鼓励你创造更多的颜色。如果不同的情况需要一个新的、具有相同价值的语义命名的颜色(即不是背景,但应该是相同的颜色),那么你仍然需要在你的颜色文件中创建一个新条目。
通过使用主题属性,我们将语义颜色的声明与提供它们的值分开,并使调用站点更清楚地知道颜色将因主题而异(因为它们使用?attr/
语法)。将你的颜色声明保持在字面上的命名值,可以鼓励你定义你的应用所使用的颜色调色板,并在主题级别上改变它们,保持你的颜色文件小而可维护。
Define a palette of colors used by your app and vary them at the theme level
这种方法的额外好处是,引用这些颜色的布局/样式变得更加可重复使用。因为主题可以被叠加或改变,这种间接性意味着你不需要为了改变一些颜色而创建备用的布局或样式--你可以用不同的主题来使用相同的布局。
总是这样吗?
我在“总是*通过主题属性参考颜色”上加了一个星号,因为在某些场合,你可能明确地不想通过主题来改变一个颜色。例如,材料设计指南指出了一些场合,你可能希望在浅色和深色主题中使用同一品牌的颜色。
在这些罕见的情况下,直接引用一个颜色资源是完全有效的。
<!-- Copyright 2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
<FloatingActionButton …
app:backgroundTint="@color/owl_pink_500"/>
艺术的现状
在另一种情况下,你可能不会在你的布局/样式中直接引用一个主题属性,那就是在使用ColorStateList
时。
<!-- Copyright 2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
<View …
android:background="@color/primary_20"/>
如果primary_20
是一个ColorStateList
,这可能是有效的,因为ColorStateList
本身是指颜色值的主题属性(见下文)。虽然通常用于在不同的状态下提供不同的颜色(按下、禁用等),但ColorStateList
有另一种能力,对主题化很有用。它们可以让你指定一个应用于颜色的alpha值。
<!-- Copyright 2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
<selector …
<item android:alpha="0.20" android:color="?attr/colorPrimary" />
</selector>
这种单项ColorStateList
(即只提供单一的、默认的颜色,而不是每个状态的不同颜色)有助于减少你需要维护的颜色资源的数量。也就是说,与其定义一个新的颜色资源,在你的主色上手动设置一个阿尔法值(每个配置!),不如改变当前主题中的任何colorPrimary
。如果你的主色调发生了变化,你只需要在一个地方更新它,而不是在所有的实例中寻找它被调整过的地方。
虽然很有用,但这种技术也有一些需要注意的地方。
1.如果指定的颜色也有一个阿尔法值,那么阿尔法值就合并,例如,将50%的阿尔法值应用于50%的不透明的白色,将产生25%的白色。
<!-- Copyright 2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
<selector …
<item android:alpha="0.50" android:color="#80ffffff" />
</selector>
出于这个原因,最好将主题颜色指定为完全不透明的,并使用ColorStateList
s来修改它们的字母。
2.阿尔法组件只是在API 23中添加的,所以如果你的min sdk比这更低,一定要使用AppCompatResources.getColorStateList
,它备份了这个行为(并且总是使用android:alpha
命名空间,而不是app:alpha
命名空间)。
3.通常我们用一个速记法来设置一个颜色作为可画性,例如。
<!-- Copyright 2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
<View …
android:background="@color/foo"/>
一个View
’的背景是一个可画的,这个速记法将给定的颜色强制转换成一个ColorDrawable
。然而,没有办法将ColorStateList
转换为Drawable
(在API 29之前,ColorStateListDrawable
被引入以解决这个问题)。然而,我们可以绕过这个限制。
<!-- Copyright 2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
<View …
android:background="@drawable/a_solid_white_rectangle_shape_drawable"
app:backgroundTint="@color/some_color_state_list"/>
要确保你的背景色调支持你的视图所需要的状态,例如,如果它需要在禁用时改变的话。
执法工作
所以你确信你应该使用主题属性和ColorStateList
,但你如何在你的代码库或团队中强制执行?你可以尝试在代码审查期间保持警惕,但这并不能很好地扩展。一个更好的方法是依靠工具来捕获这种情况。这篇文章概述了如何添加lint检查来寻找字面颜色的使用,并且可以扩展到本文的所有建议。
间接的方式
使用主题属性和ColorStateList
s来为你的主题添加颜色,使你的布局和风格更加灵活,促进重用并保持你的代码库的精简和可维护。
请在下一篇文章中加入我们,我们将更多地探讨使用主题以及它们之间的互动。
使用主题attr是为了表明该值来自为应用程序或活动指定的主题风格。我们可以直接检查当前主题中定义的相应值。为了让人信服,IDE可能提供了你所描述的一些功能,但我们可以在知道主题attrs的效果后手动引用它。
首先,感谢你的这一系列文章!你说:"我不知道。其次,当你说的时候,我有点疑惑。并且总是使用android:alpha命名空间,从不使用app:alpha命名空间--而在前一篇文章中--在这些情况下[同时定义在平台和库中的元素],更喜欢非平台版本。但后者不是意味着我们应该使用app:alpha吗?谢谢你🙏
Single-item-ColorStateList非常有用,但必须使用@color/color_primary_20作为例子。有没有可能在应用程序主题中用一个自定义属性来引用这个选择器?
像
哦,肯定不是。它坏了,在安卓4上崩溃,在安卓5上解决为红色(或至少它曾经是这样)。在res/colors
中做app:alpha
,在res/colors-v23
中做android:alpha
。 有重复的地方,但它在任何地方都有效。
这是使用AppCompatResources.getColorStateList
时的建议,在API 23以上的平台上,它服从于仅知道android:alpha
的平台,在较早的平台上,它使用它自己的膨胀器,它知道android:alpha
和app:alpha
。
因此,使用android:alpha
在任何地方都适用—只要你’使用AppCompatResources
"。
感谢Nick Butcher的这一系列好文章。有一个问题。
如果我的colors.xml遵循这样的命名模式。
colorBackground
colorSurface
colorOnSurface
colorPrimary
colorOnPrimary
colorSecondary
colorOnSecondary
colorPrimaryButton
colorOnPrimaryButton
colorSecondaryButton
colorOnSecondaryButton
colorOnSecondaryButtonVariant
textColorPrimary
textColorSecondary
textColorSecondaryVariant
colorError
colorOnError
rippleOnSecondaryColor
separatorColor
...
然后像这样使用。
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/colorPrimaryButton" android:state_enabled="true"/>
<item android:alpha="0.38" android:color="@color/colorPrimaryButton"/>
</selector>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/colorOnPrimaryButton" android:state_enabled="true"/>
<item android:alpha="0.38" android:color="@color/colorOnPrimaryButton"/>
</selector>
样式.xml
<style name="App.Widget.Button.Primary">
<item name="backgroundTint">@color/selector_for_btn_primary_background</item>
<item name="android:textColor">@color/selector_for_btn_primary_text</item>
</style>
我是否能从自定义属性中获益?
谢谢你。
你不需要自定义属性&hellip;MDC为你定义了它们。是的,我仍然建议使用主题属性,原因在这篇文章中已经解释过了,即你为字面上的名称分配了语义,并在调用现场提供了较少的意义。
关注这些文章有助于我更好地组织和理解风格和主题。
不过需要注意的是,有一个非微不足道的开发成本。属性和样式之间额外的间接性使得从XML布局中的调用站点导航到样式声明变得非常痛苦。
例如,如果你用CTRL+点击XML中的一个样式名称,Android Studio会直接把你带到样式定义。但是如果你用CTRL+点击XML中的attr,Android Studio会带你到属性定义(在attrs.xml中)而不是样式。
跳到样式的唯一方法是让Android Studio找到对该属性的所有引用,这将显示该attr在你的THEME中实现的位置,但这仍然不能让你找到样式!你必须进入主题,然后CTRL+点击attr实现,以获得样式。然后你必须进入主题,CTRL+点击attr的实现来获得样式。
那是相当严重的痛苦,而且真的拖累了我的发展流程。
因此,我现在很纠结于是否要选择attr而不是style。这是双重的,因为我不打算在我的应用程序中提供多个主题。