抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

课本

书籍资源进入官网下载,PC端进入

第十二章-最佳的UI体验,MaterialDesign3实战

M3首页
其实长久以来,大多数人可能会认为Android系统的UI并不算美观,至少没有iOS系统的美观。以至于很多IT公司在进行应用界面设计的时候,为了保证双平台的统一性,强制要求Android端的界面风格必须和iOS端一致。这种情况在现实工作当中实在是太常见了,虽然我认为这是非常不合理的。因为对于一般用户来说,他们不太可能会在两个操作系统上分别使用同一个应用,但是必定会在同一个操作系统上使用不同的应用。因此,同一个操作系统中各个应用之间界面统一性要远一个应用在双平台界面统一重要得多。
但是Android标准的界面设计风格并不是特别被大众所接受,很多公司觉得自己可以设计出更加好看的界面,从而导致Android平台的界面风格长期难以得到统一。为了解决这个问题,Google也是使出了杀手锏,在2014年Google I/O大会上重磅推出了一套全新的界面设计语言——Material Design。

本章仅演示怎么使用button按钮和部分使用方法,例如怎么正确在程序中使用等还是需要查看官方文档

什么是Material Design

Material Design是由Google的设计工程师们基于传统优秀的设计原则,结合丰富的创意和科学技术所开发的一套全新的界面设计语言,包含了视觉、运动、互动效果等特性。那么Google凭什么认为Material Design就能解决Android平台界面风格不统一的问题呢?一言以蔽之,好看!

不过,在重磅推出之后,Material Design的普及程度却不是特别理想。因为这只是一个推荐的设计规范,主要是面向UI设计人员的,而不是面向开发者的。很多开发者可能根本就搞不清楚什么样的界面和效果才叫Material Design,就算搞清楚了,实现起来也会很费劲,因为不少Material Design的效果是很难实现的,而Android中几乎没有提供相应的API支持,基本需要靠开发者自己从零写起。

Google当然意识到了这个问题,于是在2015年的Google I/O大会上推出了一个DesignSupport库,这个库将Material Design中最具代表性的一些控件和效果进行了封装,使得开发者即使在不了解Material Design的情况下,也能非常轻松地将自己的应用Material化。后来Design Support库又改名成了Material库,用于给Google全平台类的产品提供MaterialDesign的支持。本章我们就将对Material库进行深入的学习,并且配合AndroidX库中的一些控件来完成一个优秀的Material Design应用。

什么是Material Design3

14 年 Google 发布了 Material Design(简称MD),成为了 Google 系产品统一的 UI 设计语言。时至今日 MD 已经有了两次大升级,2018年发布的 Material Theming (Material Design2,简称 M2),以及 2021年新发布的 Material You (Material Design3,简称 M3

创建一个Material Design3项目

在最新版Android studio中支持直接创建Material Design3项目,但是创建的项目带有大量示例代码.

这里为了方便简化学习,我们来手动创建一个空的项目.

  1. 新建项目还是选择Empty Activity,和以前一样的流程这里不做讲解,文件名随意.
  2. 创建完成后打开项目中的settings.gradle文件,添加google的maven仓库:
    allprojects {
    repositories {
     google()
     mavenCentral()
    }
    }
    
    博客第一行代码3添加google的maven仓库
  3. 添加material依赖
    访问Google的Maven存储库或MVN存储库以查找最新版本的库。比我当前我的最新稳定版本是1.7.0
    博客第一行代码3material依赖google最新依赖
    博客第一行代码3material依赖MVN最新依赖
    build.gradle中添加
    dependencies {
     // ...
     implementation 'com.google.android.material:material:<最新版本号>'
     // ...
    }
    
    博客第一行代码3material依赖添加
  4. 更改主题
    编辑项目res/values/themes.xml文件:
    <resources xmlns:tools="http://schemas.android.com/tools">
     <!-- Base application theme. -->
    <!--    更改父主题-->
     <style name="Theme.Md3Test" parent="Theme.Material3.Light">
         <item name="colorPrimary">@color/purple_500</item>
         <item name="colorPrimaryVariant">@color/purple_700</item>
         <item name="colorOnPrimary">@color/white</item>
         <item name="colorSecondary">@color/teal_200</item>
         <item name="colorSecondaryVariant">@color/teal_700</item>
         <item name="colorOnSecondary">@color/black</item>
         <item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
     </style>
    </resources>
    
  5. 为了方便学习,也可以下载我配置好的基础项目

图标

在使用组件前我们应该学会使用图标

自带的图标库

  1. 右键res/drawable->New->Vector Asset
  2. 选择Clip Art,搜索自己想要的图标(假设需要加号),接着修改自己需要的参数就行.
    博客第一行代码3material依赖添加图标

学会查看官方文档

Md3官方文档,加入网页后会看到如下页面:

  • 开始: 介绍MD3– 从UX指南和工具到可重用组件和开源代码
  • 开发: MDC-Android、Flutter、Jetpack Compose 和 Web 的代码和开发人员文档资源。
  • 设计基础: 提供了许多优秀用户界面的基础,从可访问性标准到布局和交互的基本模式。
  • 样式: 样式是UI的视觉方面,它赋予UI独特的外观和感觉。它们可以通过更改Material主题进行定制。
  • 组件: 组件是用于创建用户界面的交互构建块。它们可以根据用途分为五类: Action, containment, navigation, selection, and text input.
  • 博客: 官方最新的特性新闻

博客第一行代码3material官方文档首页

组件

加入组件页面后可以看见根据用途分的5类Action, containment, navigation, selection, and text input.可以根据自己的需要进行选择
博客第一行代码3material5大组件分类
若是需要查看全部组件,那么将鼠标悬浮到左边即可
博客第一行代码3material所有组件分类
这里以按钮为示例进行讲解
这里一般是一个组件分类的总体介绍,一般是设计规范
博客第一行代码3material按钮首页
进入具体的组件页面后看到如下界面:
博客第一行代码3material控件首页1

  • 概述: 一般是只用看开发文档即可

博客第一行代码3material概述
在这里可以找到各个软件的文档,一般只要看MDC- Android的原生控件开发文档即可.

  • 控件规格: 控件的设计大小或者属性

博客第一行代码3material控件规格
在这个分类里面可以看见控件的设计思想,和默认属性等,还有可选属性.但这不是必要的,因为在MDC- Android里面也有.

  • 控件设计指导: 正确的在应用程序上排列控件示例,了解即可
  • 无障碍: 字面意思,业务需要可以看一下

组件

Button-按钮

常见的按钮有9种类型:1. 阴影按钮2. 填充按钮3.填充色调按钮4. 轮廓按钮5. 文本按钮6. 图标按钮7. 切换(分段)按钮8. 浮动动作按钮 (FAB)9. 拓展浮动动作按钮
博客第一行代码3material9中按钮示意图

阴影按钮

阴影按钮是基本上带有阴影的轮廓按钮。为防止阴影蠕变,请仅在绝对必要时使用它们,例如当按钮需要视觉时与图案背景分离

简单使用

在布局中:

<Button
    style="@style/Widget.Material3.Button.ElevatedButton"
    android:id="@+id/elevatedButton"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Elevated button"
/>

效果:
博客第一行代码3material按钮阴影效果
在代码中仍然调用setOnClickListener方法:

elevatedButton.setOnClickListener {
    // Respond to button press
添加图标

更改style样式添加icon图标即可

<Button
style="@style/Widget.Material3.Button.ElevatedButton.Icon"
android:id="@+id/elevatedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Elevated button"
app:icon="@drawable/ic_baseline_add_24"
/>
分析

阴影按钮具有文本标签、描边容器和可选图标。
博客第一行代码3materia按钮分析

属性
  • 文本标签属性
元素 属性 相关方法 默认值
文本 android:text setText/getText null
颜色 android:textColor setTextColor/getTextColor ?attr/colorOnSurface(查看声明)
字体外观 android:textAppearance setTextAppearance ?attr/textAppearanceLabelLarge
  • 容器属性
元素 属性 相关方法 默认值
颜色 app:backgroundTint setBackgroundColor
setBackgroundTintList
getBackgroundTintList
?attr/colorSurface(查看声明)
描边颜色 app:strokeColor setStrokeColor
setStrokeColorResource
getStrokeColor
null
描边宽度 app:strokeWidth setStrokeWidth
setStrokeWidthResource
getStrokeWidth
0dp
形状 app:shapeAppearance setShapeAppearanceModel
getShapeAppearanceModel
?attr/shapeAppearanceSmallComponent
阴影 app:elevation setElevation
getElevation
1dp
点击按钮产生的波纹色 app:rippleColor setRippleColor
setRippleColorResource
getRippleColor
?attr/colorOnSurface不透明度为 16%(查看声明)
  • 图标属性
元素 属性 相关方法 默认值
图标 app:icon etIcon
setIconResource
getIcon
null
颜色 app:iconTint setIconTint
setIconTintResource
getIconTint
?attr/colorOnSurface(查看所有州)
大小 app:iconSize setIconSize
getIconSize
wrap_content
图标位置(相对于文本标签的位置) app:iconGravity setIconGravity
getIconGravity
start
图标外边距(图标和文本标签之间的空格) app:iconPadding setIconPadding
getIconPadding
8dp
  • style
元素 style
默认样式 Widget.Material3.Button.ElevatedButton
图标样式 Widget.Material3.Button.ElevatedButton.Icon

查看全部style列表
查看全部属性列表

填充按钮

填充按钮的对比表面颜色使其成为仅次于FAB的最突出的按钮。它用于流中的最终操作或取消阻止操作。

注意如果未设置样式,则填充按钮是默认样式。

简单使用

在布局中:

<Button
    android:id="@+id/filledButton"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Filled button"
/>

博客第一行代码3material按钮填充效果

注意:由于这是默认类型,因此无需指定style标记 只要您使用的是material组件主题。如果没有,请将样式设置为 @style/Widget.Material3.Button

在代码中这样调用:

filledButton.setOnClickListener {
    // Respond to button press
}
添加图标和控件分析与阴影按钮类似
属性与阴影按钮一致,这里补充一部分
  • style
元素 style
默认样式 Widget.Material3.Button
图标样式 Widget.Material3.Button.Icon
非高架风格 Widget.Material3.Button.UnelevatedButton
未提升的图标样式 Widget.Material3.Button.UnelevatedButton.Icon

查看全部style列表
查看全部属性列表

填充色调按钮

填充色调按钮具有较浅的背景色和较深的标签颜色,使它们视觉上比常规填充按钮突出。它们仍然用于完成或取消阻止流中的操作,但这些不需要强调最好。

简单使用

在布局中:

<Button
    style="@style/Widget.Material3.Button.TonalButton"
    android:id="@+id/filledTonalButton"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Filled tonal button"
/>

博客第一行代码3material填充色调按钮效果
在代码中:

filledTonalButton.setOnClickListener {
    // Respond to button press
}
添加图标和控件分析与阴影按钮类似
属性与阴影按钮一致,这里补充一部分
  • style
元素 style
默认样式 Widget.Material3.Button.TonalButton
图标样式 Widget.Material3.Button.TonalButton.Icon

查看全部style列表
查看全部属性列表

轮廓按钮

带轮廓的按钮适用于需要注意但不是主要操作的操作,例如“查看全部” 或“添加到购物车”。这也是用来给别人机会的按钮改变主意或退出。

简单使用

在布局中:

<Button
    style="?attr/materialButtonOutlinedStyle"
    android:id="@+id/outlinedButton"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Outlined button"
/>

博客第一行代码3material轮廓效果
在代码中:

outlinedButton.setOnClickListener {
    // Respond to button press
}
添加图标和控件分析与阴影按钮类似
属性与阴影按钮一致,这里补充一部分
  • style
元素 style
默认样式 Widget.Material3.Button.OutlinedButton
图标样式 Widget.Material3.Button.OutlinedButton.Icon

查看全部style列表
查看全部属性列表

文本按钮

文本按钮使用较少,视觉突出,因此应该用于低强调的动作,例如:当提供多个选项。

简单使用

在布局中:

<Button
    style="@style/Widget.Material3.Button.TextButton"
    android:id="@+id/textButton"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Text button"
/>

博客第一行代码3material文本效果
在代码中:

textButton.setOnClickListener {
    // Respond to button press
}
添加图标与阴影按钮类似
控件分析

博客第一行代码3material文本按钮分析

属性与阴影按钮一致,这里补充一部分
  • style
元素 style
默认样式 Widget.Material3.Button.TextButton
图标样式 Widget.Material3.Button.TextButton.Icon
全宽按钮 Widget.Material3.Button.TextButton.Dialog.FullWidth

查看全部style列表
查看全部属性列表

图标按钮

图标按钮帮助用户只需轻点一下即可执行补充操作。有两种类型的图标按钮:标准按钮和包含按钮。

  • 标准图标按钮:默认情况下,图标按钮没有容器。
  • 包含的图标按钮:有一个容器在图标周围。
使用要求
  • 当需要紧凑按钮时,应该使用图标按钮.例如工具栏
  • 应该确保图标的意义明确
  • 应该由工具提示描述图标操作,而不是图标
简单使用
  1. 在布局中:
    <Button
         style="?attr/materialIconButtonStyle"
         android:id="@+id/iconButton"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         app:icon="@drawable/ic_baseline_add_24"
         />
    
  2. 在代码中:
    iconButton.addOnButtonCheckedListener { iconButton, checkedId, isChecked ->
     // Respond to button selection
    }
    
  3. 效果图
    博客第一行代码3material图标效果123
style
元素 style 主题属性
默认样式 Widget.Material3.Button.IconButton ?attr/materialIconButtonStyle
填充图标按钮 Widget.Material3.Button.IconButton.Filled ?attr/materialIconButtonFilledStyle
填充色调图标按钮 Widget.Material3.Button.IconButton.Filled.Tonal ?attr/materialIconButtonFilledTonalStyle
“带轮廓的图标”按钮 Widget.Material3.Button.IconButton.Outlined ?attr/materialIconButtonOutlinedStyle

查看全部style列表
查看全部属性列表

分段(切换)按钮

切换按钮可以是用于从一组按钮选项中进行选择。有两种类型的分段按钮:单选和多选。

  • 分段按钮以前称为切换按钮
  • 单选分段按钮用于选择单个选项、在视图之间切换或对元素进行排序
  • 多选分段按钮用于从一组选项或过滤器元素中选择多个选项
简单使用

在布局中:

<com.google.android.material.button.MaterialButtonToggleGroup
    android:id="@+id/toggleButton"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">
    <Button
        style="?attr/materialButtonOutlinedStyle"
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button 1"
    />
    <Button
        style="?attr/materialButtonOutlinedStyle"
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button 2"
    />
    <Button
        style="?attr/materialButtonOutlinedStyle"
        android:id="@+id/button3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button 3"
    />
</com.google.android.material.button.MaterialButtonToggleGroup>

博客第一行代码3material切换按钮效果
在代码中:

toggleButton.addOnButtonCheckedListener { toggleButton, checkedId, isChecked ->
    // Respond to button selection
}
图片按钮组
  1. 修改res/values/styles.xml
    <?xml version="1.0" encoding="utf-8"?>
    <resources>
     <style name="Widget.App.Button.OutlinedButton.IconOnly" parent="Widget.Material3.Button.OutlinedButton">
         <item name="iconPadding">0dp</item>
         <item name="android:insetTop">0dp</item>
         <item name="android:insetBottom">0dp</item>
         <item name="android:paddingLeft">12dp</item>
         <item name="android:paddingRight">12dp</item>
         <item name="android:minWidth">48dp</item>
         <item name="android:minHeight">48dp</item>
     </style>
    </resources>
    
  2. 在布局中:
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     tools:context=".MainActivity">
     <com.google.android.material.button.MaterialButtonToggleGroup
         android:id="@+id/toggleButton"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content">
         <Button
             style="@style/Widget.App.Button.OutlinedButton.IconOnly"
             android:id="@+id/button1"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             app:icon="@drawable/ic_baseline_add_24"
             />
         <Button
             style="@style/Widget.App.Button.OutlinedButton.IconOnly"
             android:id="@+id/button2"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             app:icon="@drawable/ic_baseline_add_24"
             />
         <Button
             style="@style/Widget.App.Button.OutlinedButton.IconOnly"
             android:id="@+id/button3"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             app:icon="@drawable/ic_baseline_add_24"
             />
     </com.google.android.material.button.MaterialButtonToggleGroup>
    </LinearLayout>
    
  3. 效果图
    博客第一行代码3material图片切换按钮效果
控件分析

博客第一行代码3material图片切换按钮分析2

属性
元素 属性 相关方法 默认值
单选 app:singleSelection setSingleSelection
isSingleSelection
false
需要选择 app:selectionRequired setSelectionRequired
isSelectionRequired
false
**启用组和所有子项 android:enabled setEnabled
isEnabled
true
  • style
元素 风格
默认样式 Widget.Material3.MaterialButtonToggleGroup

查看全部style列表
查看全部属性列表

浮动动作按钮(FAB)

FAB 在屏幕上执行主要或最常见的操作。它出现在所有屏幕内容的前面,通常为带有图标的方形。

在使用FAB之前需要添加依赖项。有关更多信息,请转到入门页面。

无障碍: 您应该通过属性或方法在FA上设置内容描述,以便屏幕阅读器能够正确识别android:contentDescription``setContentDescription

有四种类型的FAB:1.FAB,2.小型FAB, 3.大型FAB,4下一小节的浮动FAB
博客第一行代码3material浮动按钮3种类型

简单使用
  1. 在布局中:
    <com.google.android.material.floatingactionbutton.FloatingActionButton
    android:id="@+id/floating_action_button"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom|end"
    android:layout_margin="16dp"
    android:contentDescription="确认"
    app:srcCompat="@drawable/ic_baseline_done_24"/>
    
  2. 在代码中:
    fab.setOnClickListener {
     // Respond to FAB click
    }
    
  3. 效果图
    博客第一行代码3material浮动按钮效果
分析

博客第一行代码3material浮动按钮分析

常规、小型和大型 FAB 关键属性
  • 容器属性
元素 属性 相关方法 默认值
颜色 app:backgroundTint setBackgroundTintList
getBackgroundTintList
?attr/colorPrimaryContainer (see all states)
边框宽度 app:borderWidth N/A 0dp
大小 app:fabSize
app:fabCustomSize
setSize
setCustomSize
clearCustomSize
getSize
getCustomSize
auto
形状外观 shapeAppearance
shapeAppearanceOverlay
setShapeAppearanceModel
getShapeAppearanceModel
ShapeAppearanceOverlay.Material3.FloatingActionButton
阴影 app:elevation setElevation
getCompatElevation
6dp
悬浮和聚焦阴影 app:hoveredFocusedTranslationZ setCompatHoveredFocusedTranslationZ
getCompatHoveredFocusedTranslationZ
2dp
点击阴影 app:pressedTranslationZ setCompatPressedTranslationZ
getCompatPressedTranslationZ
6dp
波纹颜色 app:rippleColor setRippleColor
getRippleColor
getRippleColorStateList
variations of ?attr/colorOnPrimaryContainer, see all states
动画 app:showMotionSpec
app:hideMotionSpec
setMotionSpec
setMotionSpecResource
getMotionSpec
@null
  • 图标属性
元素 属性 相关方法 默认值
图标 app:srcCompat setImageDrawable
setImageResource
getDrawable
null
颜色 app:tint setImageTintList
getImageTintList
?attr/colorOnPrimaryContainer (see all states)
  • style属性
元素 风格
默认样式 Widget.Material3.FloatingActionButton.Primary(?attr/floatingActionButtonStyle)

查看全部style列表
查看全部属性列表

一些方法
  1. 显示/隐藏
    使用 theand 方法对可见性进行动画处理。显示控件并将其淡入,而隐藏控件并淡出它.
    // To show:
    fab.show()
    // To hide:
    fab.hide()
    
  2. 扩展和收缩
    使用 theand 方法对显示和隐藏的文本进行动画处理 扩展动画扩展 FAB 以显示 文本和图标。收缩动画将 FAB 缩小以仅显示 图标。
    // To extend:
    extendedFab.extend()
    // To shrink:
    extendedFab.shrink()
    
    设置FAB的尺寸
    可以通过使用或调整大小 对于大/小 FAB(仅限 M3),通过应用所需的样式。discrete大小模式在 Material3 样式中已弃用。
    如果要使用小型 FAB,请应用以下样式属性之一:
  • ?attr/floatingActionButtonSmallStyle
  • ?attr/floatingActionButtonSmallPrimaryStyle
  • ?attr/floatingActionButtonSmallSecondaryStyle
  • ?attr/floatingActionButtonSmallTertiaryStyle
  • ?attr/floatingActionButtonSmallSurfaceStyle`

如果要使用大型 FAB,请应用以下样式属性之一:

  • ttr/floatingActionButtonLargeStyle
  • ttr/floatingActionButtonLargePrimaryStyle
  • ttr/floatingActionButtonLargeSecondaryStyle
  • ttr/floatingActionButtonLargeTertiaryStyle
  • ttr/floatingActionButtonLargeSurfaceStyle

拓展浮动动作按钮

扩展的 FAB 更宽,并且包含文本标签。

简单使用
  1. 在布局中
    <com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
    android:id="@+id/extended_fab"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_margin="16dp"
    android:layout_gravity="bottom|end"
    android:contentDescription="拓展浮动按钮"
    android:text="拓展浮动按钮"
    app:icon="@drawable/ic_baseline_done_24"/>
    
  2. 在代码中:
    extendedFab.setOnClickListener {
     // Respond to Extended FAB click
    }
    
  3. 效果图
    博客第一行代码3material浮动拓展效果
    分析
    博客第一行代码3material拓展浮动按钮示意图
关系属性和浮动按钮一致,这里仅补充
  • 容器属性
元素 属性 相关方法 默认值
扩展策略 app:extendStrategy 不适用 wrap_content
压高 app:pressedTranslationZ 不适用 6dp
动画 app:showMotionSpec
app:hideMotionSpec
extendMotionSpec
shrinkMotionSpec
setMotionSpec
set
MotionSpecResource
get*MotionSpec
查看动画

查看全部style列表
查看全部属性列表

顺带一说

  • 根据操作的重要性选择按钮的类型。动作越重要,其按钮应越强调。
  • 所有按钮都有完全圆角
  • 按钮有四种常见的颜色映射,每种映射都有浅色和深色主题

新增功能

  • 颜色:新的颜色映射与动态颜色的兼容性。图标和标签现在共享相同的颜色。
  • 图标:头部和尾部图标的标准大小现在为 18dp
  • 形状:全圆角半径、更高的高度和新的最小宽度
  • 排版:按钮文本为句子大小写,没有全部大写
  • 类型:三种新的按钮类型 - 阴影按钮,填充按钮和填充色调按钮 - 取代以前称为包含按钮的内容。
  • M2:按钮的高度为 36dp,角半径略微圆润。按钮标签使用全部大写。
  • M3:按钮较高,为 40dp,并具有完全圆角。按钮标签使用句子大小写。

按钮选择的标准

重点级别 元件 理由 示例操作
高强调 – 用于屏幕上的主要、最重要或最常见的操作 拓展FAB 扩展后的 FAB 更宽的格式和文本标签使其比 FAB 更具视觉效果。它通常用于较大的屏幕上,而FAB似乎太小了。 创建文件
FAB FAB 仍然是屏幕主要操作的默认组件。它有三种尺寸:小型 FAB、FAB 和大型 FAB。 创建\撰写
填充按钮 填充按钮的对比表面颜色使其成为仅次于 FAB 的最突出的按钮。它用于流中的最终操作或取消阻止操作。 保存\确认\完成
中等强调 – 用于不会分散其他屏幕元素注意力的重要操作 填充色按钮 填充色调按钮具有较浅的背景色和较深的标签颜色,使其视觉效果不如常规填充按钮突出。它们仍用于流中的最终操作或取消阻止操作,但强调较少。 保存\确认\完成
阴影按钮 阴影的按钮本质上是具有较浅背景颜色和阴影的填充按钮。为了防止阴影蠕变,请仅在绝对必要时使用它们,例如当按钮需要与图案背景进行视觉分离时。 回复\查看全部\加入购物车\删除
分级显示按钮 对需要注意但不是主要操作的操作使用分级显示的按钮,例如查看全部或添加到购物车。这也是用于让某人有机会改变主意或退出流程的按钮。 回复\查看全部\加入购物车\删除
低强调 – 用于突出程度最低的可选或补充操作 文本按钮 文本按钮的视觉突出度较低,因此应用于低强调操作,例如替代选项。 了解更多 \查看全部 \更改帐号 \开启
分段按钮 分段按钮比单个图标按钮具有更多的视觉突出度。 左对齐/中间对齐/右对齐
图标按钮 图标按钮是最紧凑和最微妙的按钮类型,用于可选的补充操作,例如“书签”或“星号”。 添加到收藏夹 \打印

Kotlin课堂:编写好用的工具方法

到目前为止,我们已经将Kotlin大部分系统性的知识点学习完了。掌握了如此多的Kotlin特性,你知道该如何对它们进行灵活运用吗?
事实上,Kotlin提供的丰富语法特性给我们提供了无限扩展的可能,各种复杂的API经过特殊的封装处理之后都能变得简单易用。比如我们之前在第7章体验过的KTX库,就是Google为了简化许多API的用法而专门设计的。不过KTX库所能覆盖到的功能毕竟有限,因此最重要的还是我们要能养成对Kotlin的各种特性进行灵活运用的意识。那么在本节的Kotlin课堂中,我将带你对几个常用API的用法进行简化,从而编写出一些好用的工具方法。

求N个数的最大最小值

两个数比大小这个功能,相信每一位开发者都遇到过。如果我想要获取两个数中较大的那个数,除了使用最基本的if语句之外,还可以借助Kotlin内置的max()函数,如下所示:

val a = 10
val b = 15
val larger = max(a, b)

这种代码看上去简单直观,也很容易理解,因此好像并没有什么优化的必要。

可是现在如果我们想要在3个数中获取最大的那个数,应该怎么写呢?由于max()函数只能接收两个参数,因此需要先比较前两个数的大小,然后再拿较大的那个数和剩余的数进行比较,写法如下:

val a = 10
val b = 15
val c = 5
val largest = max(max(a, b), c)

有没有觉得代码开始变得复杂了呢?3个数中获取最大值就需要使用这种嵌套max()函数的写法了,那如果是4个数、5个数呢?没错,这个时候你就应该意识到,我们是可以对max()函数的用法进行简化的。

回顾一下,我们之前在第7章的Kotlin课堂中学过vararg关键字,它允许方法接收任意多个同等类型的参数,正好满足我们这里的需求。那么我们就可以新建一个Max.kt文件,并在其中自定义一个max()函数,如下所示:

fun max(vararg nums: Int): Int {
 var maxNum = Int.MIN_VALUE
 for (num in nums) {
 maxNum = kotlin.math.max(maxNum, num)
 }
 return maxNum
}

可以看到,这里max()函数的参数声明中使用了vararg关键字,也就是说现在它可以接收任意多个整型参数。接着我们使用了一个maxNum变量来记录所有数的最大值,并在一开始将它赋值成了整型范围的最小值。然后使用for-in循环遍历nums参数列表,如果发现当前遍历的数字比maxNum更大,就将maxNum的值更新成这个数,最终将maxNum返回即可。

仅仅经过这样的一层封装之后,我们在使用max()函数时就会有翻天覆地的变化,比如刚才同样的功能,现在就可以使用如下的写法来实现:

val a = 10
val b = 15
val c = 5
val largest = max(a, b, c

这样我们就彻底摆脱了嵌套函数调用的写法,现在不管是求3个数的最大值还是求N个数的最大值,只需要不断地给max()函数传入参数就可以了。

不过,目前我们自定义的max()函数还有一个缺点,就是它只能求N个整型数字的最大值,如果我还想求N个浮点型或长整型数字的最大值,该怎么办呢?当然你可以定义很多个max()函数的重载,来接收不同类型的参数,因为Kotlin中内置的max()函数也是这么做的。但是这种方案实现起来过于烦琐,而且还会产生大量的重复代码,因此这里我准备使用一种更加巧妙的做法。
Java中规定,所有类型的数字都是可比较的,因此必须实现Comparable接口,这个规则在Kotlin中也同样成立。那么我们就可以借助泛型,将max()函数修改成接收任意多个实现Comparable接口的参数,代码如下所示:

fun <T : Comparable<T>> max(vararg nums: T): T {
 if (nums.isEmpty()) throw RuntimeException("Params can not be empty.")
 var maxNum = nums[0]
 for (num in nums) {
 if (num > maxNum) {
 maxNum = num
 }
 }
 return maxNum
}

可以看到,这里将泛型T的上界指定成了Comparable<T>,那么参数T就必然是Comparable<T>的子类型了。接下来,我们判断nums参数列表是否为空,如果为空的话就主动抛出一个异常,提醒调用者max()函数必须传入参数。紧接着将maxNum的值赋值成nums参数列表中第一个参数的值,然后同样是遍历参数列表,如果发现了更大的值就对maxNum进行更新。
经过这样的修改之后,我们就可以更加灵活地使用max()函数了,比如说求3个浮点型数字的最大值,同样也变得轻而易举:

val a = 3.5
val b = 3.8
val c = 4.1
val largest = max(a, b, c)

而且现在不管是双精度浮点型、单精度浮点型,还是短整型、整型、长整型,只要是实现Comparable接口的子类型,max()函数全部支持获取它们的最大值,是一种一劳永逸的做法。

而如果你想获取N个数的最小值,实现的方式也是类似的,只需要定义一个min()函数就可以了,这个功能就当作课后习题留给你来完成吧。

简化Toast的用法

新建一个Toast.kt文件,并在其中编写如下代码:

fun String.showToast(context: Context) {
 Toast.makeText(context, this, Toast.LENGTH_SHORT).show()
}
fun Int.showToast(context: Context) {
 Toast.makeText(context, this, Toast.LENGTH_SHORT).show()
}

这里分别给String类和Int类新增了一个showToast()函数,并让它们都接收一个Context参数。然后在函数的内部,我们仍然使用了Toast原生API用法,只是将弹出的内容改成了this,另外将Toast的显示时长固定设置成Toast.LENGTH_SHORT。

那么经过这样的扩展之后,我们以后在使用Toast时可以变得多么简单呢?体验一下就知道了,比如同样弹出一段文字提醒就可以这么写:

"This is Toast".showToast(context)

怎么样,比起原生Toast的用法,有没有觉得这种写法畅快多了呢?另外,这只是直接弹出一段字符串文本的写法,如果你想弹出一个定义在strings.xml中的字符串资源,也非常简单,写法如下:

R.string.app_name.showToast(context)

当然,这种写法其实还存在一个问题,就是Toast的显示时长被固定了,如果我现在想要使用Toast.LENGTH_LONG类型的显示时长该怎么办呢?要解决这个问题,其实最简单的做法就是在showToast()函数中再声明一个显示时长参数,但是这样每次调用showToast()函数时都要额外多传入一个参数,无疑增加了使用复杂度。
不知道你现在有没有受到什么启发呢?回顾一下,我们在第2章学习Kotlin基础语法的时候,曾经学过给函数设定参数默认值的功能。只要借助这个功能,我们就可以在不增加showToast()函数使用复杂度的情况下,又让它可以支持动态指定显示时长了。修改Toast.kt中的代码,如下所示:

fun String.showToast(context: Context, duration: Int = Toast.LENGTH_SHORT) {
 Toast.makeText(context, this, duration).show()
}
fun Int.showToast(context: Context, duration: Int = Toast.LENGTH_SHORT) {
 Toast.makeText(context, this, duration).show()
}

可以看到,我们给showToast()函数增加了一个显示时长参数,但同时也给它指定了一个参数
默认值。这样我们之前所使用的showToast()函数的写法将完全不受影响,默认会使用
Toast.LENGTH_SHORT类型的显示时长。而如果你想要使用Toast.LENGTH_LONG的显示时
长,只需要这样写就可以了:

"This is Toast".showToast(context, Toast.LENGTH_LONG)

简化Snackbar的用法

Snackbar的用法和Toast类似但是又比Toast复杂一些,在学习本小节之前应该先了解它的用法

  1. 先来回顾一下Snackbar的常规用法吧,如下所示:
    Snackbar.make(view, "This is Snackbar", SnackbarLENGTH_SHORT)
    .setAction("Action") {
    // 处理具体的逻辑
    }
    .show()
    
    那么对于这种结构的API,我们该如何进行简化呢?其实简化的方式并不固定,接下来我即将演示的写法也只是我个人认为比较不错的一种。
  2. 由于make()方法接收一个View参数,Snackbar会使用这个View自动查找最外层的布局,用于展示Snackbar。因此,我们就可以给View类添加一个扩展函数,并在里面封装显示Snackbar的具体逻辑。新建一个Snackbar.kt文件,并编写如下代码:
    fun View.showSnackbar(text: String, duration: Int = Snackbar.LENGTH_SHORT) {
     Snackbar.make(this, text, duration).show()
    }
    fun View.showSnackbar(resId: Int, duration: Int = Snackbar.LENGTH_SHORT) {
     Snackbar.make(this, resId, duration).show()
    }
    
    这段代码应该还是很好理解的,和刚才的showToast()函数比较相似。只是我们将扩展函数添加到了View类当中,并在参数列表上声明了Snackbar要显示的内容以及显示的时长。另外,Snackbar和Toast类似,显示的内容也是支持传入字符串和字符串资源id两种类型的,因此这里我们给showSnackbar()函数进行了两种参数类型的函数重载。
  3. 现在想要使用Snackbar显示一段文本提示,只需要这样写就可以了:
    view.showSnackbar("This is Snackbar")
    
    假如Snackbar没有setAction()方法,那么我们的简化工作到这里就可以结束了。但是setAction()方法作为Snackbar最大的特色之一,如果不能支持的话,我们编写的showSnackbar()函数也就变得毫无意义了。
  4. 这个时候,神通广大的高阶函数又能派上用场了,我们可以让showSnackbar()函数再额外接收一个函数类型参数,以此来实现Snackbar的完整功能支持。修改Snackbar.kt中的代码,如下所示:
    fun View.showSnackbar(text: String, actionText: String? = null,
                       duration: Int = Snackbar.LENGTH_SHORT, block: (() -> Unit)? = null) {
     val snackbar = Snackbar.make(this, text, duration)
     if (actionText != null && block != null) {
         snackbar.setAction(actionText) {
             block()
         }
     }
     snackbar.show()
    }
    fun View.showSnackbar(resId: Int, actionResId: Int? = null,
                       duration: Int = Snackbar.LENGTH_SHORT, block: (() -> Unit)? = null) {
     val snackbar = Snackbar.make(this, resId, duration)
     if (actionResId != null && block != null) {
         snackbar.setAction(actionResId) {
             block()
         }
     }
     snackbar.show()
    }
    
    可以看到,这里我们给两个showSnackbar()函数都增加了一个函数类型参数,并且还增加了一个用于传递给setAction()方法的字符串或字符串资源id。这里我们需要将新增的两个参数都设置成可为空的类型,并将默认值都设置成空,然后只有当两个参数都不为空的时候,我们才去调用Snackbar的setAction()方法来设置额外的点击事件。如果触发了点击事件,只需要调用函数类型参数将事件传递给外部的Lambda表达式即可。
  5. 这样showSnackbar()函数就拥有比较完整的Snackbar功能了,比如本小节最开始的那段示例代码,现在就可以使用如下写法进行实现:
    view.showSnackbar("This is Snackbar", "Action") {
    // 处理具体的逻辑
    }
    
    怎么样,和Snackbar原生API的用法相比,我们编写的showSnackbar()函数是不是要明显简单好用得多?

评论