如何专业地组织按钮的样式? (SDK 26+,最低 SDK 21)

How to professionally organise the styling of buttons? (SDK 26+, min SDK 21)

组织专业 android 应用程序按钮样式的最佳做法是什么?假设一个更大的当代应用程序(SDK 26+,最低 SDK 21)。

这个问题是可以回答的,因为 Material Design 的来源和 Android Studio 的设置都提供了足够的线索和预期专业用法模式的示例。用户当然不限于此模式,但遵循这些模式,可以使应用程序与 Material Design 的源一起很好地发挥作用,并提供最佳的可维护性。

我发现了几种与按钮样式相关的成分。

可能还有更多。

你可以去查资料。然而,即使了解所有细节也无法全面了解预期用途。本题要求画图

这取决于您并取决于您的应用程序..您也可以使用 xml 文件设置背景 例如:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true" >
        <shape xmlns:android="http://schemas.android.com/apk/res/android">
            <corners android:radius="20dp" />
            <solid android:color="#8c0000" />
        </shape>
    </item>
    <item >
        <shape xmlns:android="http://schemas.android.com/apk/res/android">
            <corners android:radius="20dp" />
            <solid android:color="#c62f2c" />
        </shape>
    </item>
</selector>

(最小 SDK 21)

一般方法

粒度

我认为这是一种足够细粒度的方法来将文本外观与背景分开。这提供了将不同背景与不同文本外观组合的选项。它还匹配Button提供的两种样式设置和Material设计的组织。因此它解决了这个问题,它打算如何使用。

价格是,每个 Button 需要两个设置:

  • 按钮文字:android:textAppearance
  • 按钮背景:style

要进一步降低这个价格 styles_material.xml 实际上需要一个更高级的方法。每个按钮 style 已包含默认文本外观。所以在正常情况下,我只需要应用按钮 style

<Button
 style="?defaultButtonStyle"

我在自己的按钮样式中遵循此模式,因为问题是针对预期用途的。如果我想修改默认值,我通过将其设置为 android:textAppearance.

添加替代文本外观
<Button
 style="?defaultButtonStyle"
 android:textAppearance="?smallButtonTextAppearance"

对于非常特殊的按钮,我仍然可以在布局文件级别调整样式。这是最低级别的粒度。

Hint: Be aware that android:textAppearance has a very low precedence. If you set a text attribute somewhere in the theme (or style), you will overwrite the same attribute in all of android:textAppearance. It works with a similar force like the "!important" annotation in CSS, which can be a pretty pitfall.

灵活的主题

没有

如果我不打算使用不同的主题,我可以直接在布局中设置样式。

<Button
 style="@style/My.DefaultButtonStyle"
 android:textAppearance="@style/My.SmallButtonTextAppearance"
 ...

如果想要能够交换主题,我首先将所有类型的样式映射到属性。然后我使用属性间接设置样式。这使我可以选择为其他主题连接其他样式,而无需重复布局。

<Button
 style="?defaultButtonStyle"
 android:textAppearance="?smallButtonTextAppearance"
 ...

我个人不喜欢使用或混合给定的属性,而是完全定义我自己的一组属性来解决我的设计。所以洋葱的层次保持干净分离。

<?xml version="1.0" encoding="utf-8" ?>
<resources>

    <!-- button text appearance -->
    <attr name="defaultButtonTextAppearance" format="reference" />
    <attr name="smallButtonTextAppearance" format="reference" />

    <!-- button backgrounds -->
    <attr name="defaultButtonStyle" format="reference" />
    <attr name="alarmButtonStyle" format="reference" />

在主题中,属性映射到主题特定样式。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <style name="OtherTheme" parent="Theme.AppCompat">

        <!-- button text appearance -->
        <item name="defaultButtonTextAppearance">@style/OtherTheme.DefaultButtonTextAppearance</item>
        ...

        <!-- button backgrounds -->
        <item name="defaultButtonStyle">@style/OtherTheme.DefaultButtonStyle</item>
        ...

按钮文本

如果我追踪样式的来源,我会找到一个文件 data/res/values/styles_material.xml,它定义了所有按钮文本外观的根。 TextAppearance.Material.Button 继承自 TextAppearance.Material,但按钮的四个相关属性被覆盖。

<style name="TextAppearance.Material">
    <item name="textColor">?attr/textColorPrimary</item>
    <item name="textColorHint">?attr/textColorHint</item>
    <item name="textColorHighlight">?attr/textColorHighlight</item>
    <item name="textColorLink">?attr/textColorLink</item>
    <item name="textSize">@dimen/text_size_body_1_material</item>
    <item name="fontFamily">@string/font_family_body_1_material</item>
    <item name="lineSpacingMultiplier">@dimen/text_line_spacing_multiplier_material</item>
</style>

<style name="TextAppearance.Material.Button">
    <item name="textSize">@dimen/text_size_button_material</item>
    <item name="fontFamily">@string/font_family_button_material</item>
    <item name="textAllCaps">true</item>
    <item name="textColor">?attr/textColorPrimary</item>
</style>

可以用自己继承的样式覆盖。它还表明,在根本不使用继承的情况下编写我自己的文本外观样式会很容易。

颜色

了解Android的文本颜色管理系统是他们最困惑的部分,因为该系统非常强大。来点启示。

在上面的 TextAppearance.Material.Button 中,我发现文本颜色是由属性 ?textColorPrimary 指定的。此属性再次基于属性 ?android:colorForeground.

属性?android:colorForeground是设置文本颜色的中央开关。默认情况下,所有文本颜色都是根据此设置计算的,包括按钮的颜色。例如,为禁用的按钮、正文等计算不同等级的灰色或不透明变体。

与其接触几十个不同的地方,不如在这里设置通用的默认文本颜色,并尽可能依赖默认的 Android 颜色计算系统。详细调整。

<item name="android:colorForeground">@color/orange_700</item>

此设置默认为 @color/foreground_material_dark

Hint 1: If you edit the setting by use of the Android Studio Theme Editor, it will possibly change the value of@color/foreground_material_dark. To me it does not feel like a good idea to change a value of material dark because it is not my realm. Better use a reference like shown before.

Hint 2: The Theme Editor is an appropriate tool to discover the relations of the color attributes system. This relations reveal, when you experimentally try to edit the different attributes with the editor.

如果我想要一个不同于整体文本颜色的按钮文本颜色,我将其设置在文本外观样式级别。

Hint 3: Using ?android:colorForeground does not work out of the
box below API 26. For a workaround see here.

文字大小

文本大小是影响文本外观的因素,我通常希望在自定义文本外观样式中直接调整到我自己的设计。

<style name="My.SmallButtonTextAppearance" parent="My.DefaultButtonTextAppearance">
    <item name="android:textSize">16sp</item>
</style>

TextAppearance.Material.Button 采用资源 @dimen/text_size_button_material 中的默认文本大小。没有一个系统的中央文本大小设置可以与文本颜色设置系统相媲美。

全部大写

根样式 TextAppearance.Material.Button 将全部大写设置为 true。连一个资源都没有,值是从哪里拿来的。它只是硬编码。

<item name="textAllCaps">true</item>

很有可能,我想在我的自定义按钮样式中设置为false

<item name="android:textAllCaps">false</item>

字体家族

与文本颜色一样,字体系列通常是一个具有共同中心性质的系统。按钮是如何管理的?根样式 TextAppearance.Material.Button 使用字符串资源 @string/font_family_button_material.

<item name="fontFamily">@string/font_family_button_material</item>

在文件 data/res/values/donttranslate_material.xml 中设置为 sans-serif-medium,而在文件 data/res/values-watch/donttranslate_material.xml 中设置为 sans-serif-condensed

<string name="font_family_button_material">sans-serif-medium</string>
<string name="font_family_button_material">sans-serif-condensed</string>

sans-serif 设置映射到我在字体设置中选择的字体系列。通常 sans-serif 适用于按钮文本。为了进一步自定义字体,我指向 this question.

按钮样式

除了使用背景颜色外,还可以使用xml资源文件来指定带有花边、颜色渐变或其他图形效果的背景,还支持different backgrounds for different states of the button

这部分受我的设计影响很大。我通常会使用自己的背景。

另一方面,material 设计中有丰富的预定义按钮背景资源文件系统。我想在这里做一个简短的概述,但这超出了我的技能范围,而且似乎太大了,值得单独讨论一个话题。

背景的style不应包含widthheightmargins的设置,因为这属于周围布局。另一方面 padding 属于背景 style.

Material 设计的按钮样式

在文件 data/res/values/styles_material.xml 中,我找到了九种我可以继承的按钮样式。如果我自己写,不应该忘记设置默认文本外观。

根元素是 Widget.Material.Button。它将默认文本外观设置为 ?textAppearanceButton。因此,设置此属性是一个选项,可以直接使用 material 设计按钮样式而无需继承,但仍具有自定义的默认文本外观。

<!-- Bordered ink button -->
<style name="Widget.Material.Button">
    <item name="background">@drawable/btn_default_material</item>
    <item name="textAppearance">?attr/textAppearanceButton</item>
    <item name="minHeight">48dip</item>
    <item name="minWidth">88dip</item>
    <item name="stateListAnimator">@anim/button_state_list_anim_material</item>
    <item name="focusable">true</item>
    <item name="clickable">true</item>
    <item name="gravity">center_vertical|center_horizontal</item>
</style>

属性?colorAccent用于设置Widget.AppCompat.Button.Colored的颜色。参见Android Studio Theme Editor。参见 @drawable/btn_colored_material

请注意,Widget.AppCompat.Button.Colored 的默认文本外观各不相同,并非由可自定义的属性设置。

<!-- Colored bordered ink button -->
<style name="Widget.Material.Button.Colored">
    <item name="background">@drawable/btn_colored_material</item>
    <item name="textAppearance">@style/TextAppearance.Material.Widget.Button.Colored</item>
</style>

<!-- Small bordered ink button -->
<style name="Widget.Material.Button.Small">
    <item name="minHeight">48dip</item>
    <item name="minWidth">48dip</item>
</style>

<!-- Borderless ink button -->
<style name="Widget.Material.Button.Borderless">
    <item name="background">@drawable/btn_borderless_material</item>
    <item name="stateListAnimator">@null</item>
</style>

请注意,Widget.Material.Button.Borderless.Colored 的默认文本外观各不相同,并非由可自定义的属性设置。

<!-- Colored borderless ink button -->
<style name="Widget.Material.Button.Borderless.Colored">
    <item name="textAppearance">@style/TextAppearance.Material.Widget.Button.Borderless.Colored</item>
</style>

请注意 Widget.Material.Button.ButtonBar.AlertDialog 继承自 Widget.Material.Button.Borderless.Colored。默认文本外观的相同限制适用。

<!-- Alert dialog button bar button -->
<style name="Widget.Material.Button.ButtonBar.AlertDialog" parent="Widget.Material.Button.Borderless.Colored">
    <item name="minWidth">64dp</item>
    <item name="minHeight">@dimen/alert_dialog_button_bar_height</item>
</style>

<!-- Small borderless ink button -->
<style name="Widget.Material.Button.Borderless.Small">
    <item name="minHeight">48dip</item>
    <item name="minWidth">48dip</item>
</style>

<style name="Widget.Material.Button.Inset">
    <item name="background">@drawable/button_inset</item>
</style>

<style name="Widget.Material.Button.Toggle">
    <item name="background">@drawable/btn_toggle_material</item>
    <item name="textOn">@string/capital_on</item>
    <item name="textOff">@string/capital_off</item>
</style>

就我个人而言,我要么使用其中一种预定义按钮样式,要么从 Widget.Material.Button 继承我自己的样式。这使得继承层次较低,代码易于阅读。如果我从另一个样式继承,它最多为我节省三行代码,同时代码变得更难维护。

这条经验法则也有例外。例如 @drawable/btn_borderless_material 是私有的。所以我要么必须从 Widget.Material.Button.Colored 继承,要么创建文件的副本。

附录

相关问题

属性

  • Defining custom attrs
  • Android "?colorPrimary" vs "?attr/colorPrimary"?

颜色

  • Setting a color based on theme
  • Attribute android:colorForeground not working in API 23 android themes - defining colours in custom themes

字体

  • How to set default font family for entire Android app
  • How to change fontFamily of TextView in Android
  • What is the difference between fontFamily and typeFace in android?