Button 的 "进化之旅" | 我们是如何设计 Compose API 的 (下篇)

Button 的 "进化之旅" | 我们是如何设计 Compose API 的 (下篇)
www.zeeklog.com  - Button 的 "进化之旅" | 我们是如何设计 Compose API 的 (下篇)

本文由 Jetpack Compose 团队的 Louis Pullen-Freilich (软件工程师)、Matvei Malkov (软件工程师) 和 Preethi Srinivas (UX 研究员) 共同撰写。

近期 Jetpack Compose 发布了 ,带来了一系列用于构建 UI 的稳定 API。今年早些时候,我们发布了 API 指南,介绍了编写 Jetpack Compose API 的最佳实践和 API 设计模式。经过多次迭代公共 API 接口 (API surface) 之后形成的指南,其实没有展示出这些设计模式的形成过程和我们在迭代过程中决策背后的故事。

API 指南

https://github.com/androidx/androidx/blob/androidx-main/compose/docs/compose-api-guidelines.md

本文将继续带您了解一个 "简单" 的 Button 的 "进化之旅",来深入了解我们是如何迭代设计 API,使其简单易用又不失灵活性。这个过程需要基于开发者的反馈,对 API 的可用性进行多次的适配和改进。

在中,我们通过开发者反馈、API 一致性和可发现性三个方面,和大家探讨了 Button API 的迭代过程。本文将进一步为大家展示 Button API 是如何在原有设计上逐步优化演变成为今天的设计。

映射开发者的工作框架

接下来是更多的反馈 —— 我们在一系列更进一步的编程活动中,重新评估了 Button API 的可用性。在这些活动中,我们使用 Material Design 中对于按钮的定义来进行命名: Button 变为 ContainedButton 以符合它在 Material Design 中的特性。然后,我们测试新的命名,以及当时已有的整个 Button API,并且评估了两个主要的开发者目标:

  • 创建 Button 并且处理点击事件
  • 使用预定义的 Material 主题为 Button 添加样式
www.zeeklog.com  - Button 的 "进化之旅" | 我们是如何设计 Compose API 的 (下篇)

△ material.io 中的 Material Button

我们从开发者活动中得到了一个关键启示 —— 大多数开发者不太熟悉 Material Button 中的命名习惯。比如,很多开发者无法区分 ContainedButton 和 OutlinedButton:

ContainedButton 是什么意思呢?

我们发现当输入 Button,并且看到自动补全建议的三个 Button 组件时,开发者花费了相当的精力来猜测哪个才是自己需要的。大多数开发者希望默认的按钮就是 ContainedButton,因为这是最常用的一个,并且也是最像 "按钮" 的一个。所以就明确了我们需要一个默认设置,使开发者可以直接使用而无需阅读 Material Design 的指南。此外,基于视图的 MDC-Android Button 默认就是填充式按钮,这也是将其作为默认按钮的先例。

MDC-Android

https://github.com/material-components/material-components-android

更清楚地描述角色

研究发现,另外一个令人困惑的点是两个已存在的 Button 的版本: 一个 Button 可接受一个 String 类型的参数作为文本,而一个 Button 可接受一个可修改的 lambda 参数,表示通用内容。这么设计的本意是从两个不同的层次来提供 API:

  • 带有文本的 Button 更简单一些,更加易于实现
  • 更高级的 Button,它其中的内容更具开放性

我们发现开发者在两者之间进行选择时,会有一定困难: 但是当从 String 重载转移到 lambda 重载时,自定义 "悬崖" 的存在,使得增量自定义 Button 变得具有挑战性。我们常常听到开发者要求在 String 重载中为 Button 增加 TextStyle 参数。

它允许自定义内部的 TextStyle 而无需使用 lambda 重载的版本。

我们提供 String 的本意是希望能够简化那些最简单用例的实现,但是这样却阻碍了开发者使用带有可组合的 lambda 的重载,转而要求 String 重载增加额外功能。这两个单独 API 的存在,不仅造成了开发者的困惑,也表明了带有原始类型的重载的确存在一些根本的问题: 他们接受了原始类型,比如 String,而不是可组合的 lambda 类型。

单步代码

原始类型的 Button 重载直接将文本作为参数,减少了开发者在创建文本式 Button 时所需要写的代码。我们最初使用简单的 String 类型作为文本参数,但是后来发现 String 类型很难对其中的部分文本添加样式。

对于这样的需求,Compose 提供了 AnnotatedString API,来对文本的不同部分添加自定义样式。然而,它对于简单的应用场景增加了一定成本,因为开发者首先需要将 String 转换为 AnnotatedString。这也使我们在考虑是否应该提供新的 Button 重载,既可以接受 String 作为参数,也可以接受 AnnotatedString 作为参数,来支持简单和更加进阶的需求。

我们的 API 设计讨论在图片和图标方面更加的复杂,比如当 FloatingActionButton 需要用到图片或者图标的时候。icon 参数的类型应该是 Vector 还是 Bitmap?如何支持带有动画的图标?即使我们竭尽了全力,最终发现我们也只能支持 Compose 中可用的类型 —— 任何第三方图片类型都需要开发者实现他们自己的重载以提供支持。

紧耦合的副作用

Compose 最大的优势之一是可组合性。创建可组合的函数以较小成本分离关注点,构建可复用的和相对独立的组件。通过可组合的 lambda 重载,可以直观地看到这样的思路: Button 是可点击内容的容器,但是它无需关心其中的内容是什么。

但是对于原始类型的重载,情况就变复杂了: 直接接受文本参数的 Button,现在既需要负责作为可点击的容器,又需要将 Text 组件传递到内部。这意味着它现在需要管理两者的公共 API 接口,这也引发了另一个重要的问题: Button 该对外暴露什么样的文本相关参数呢?这也将 Button 和 Text 的公共 API 接口绑定到了一起: 如果未来 Text 增加了新的参数和功能,那是不是意味着 Button 也需要增加对这些新增内容的支持?紧耦合是 Compose 试图避免的问题之一,而且很难以统一的方式在所有组件上回答该问题,这也导致了公共 API 接口的不一致性。

支持工作框架

原始类型的重载使开发者可以避免使用可组合的 lambda 重载,而以较少的自定义空间作为代价。但是当开发者需要在原始类型的重载上,实现原本无法实现的自定义呢?唯一的选择,就是使用可组合的 lambda 重载,然后,将内部的实现代码从原始类型重载中复制过来,并做相应的修改。我们在研究中发现,自定义操作的 "悬崖" 阻碍了开发者使用更加灵活、可组合的 API,因为在层级之间的操作显得比之前更具挑战。

使用 "slot API" 解决问题

列举上述问题后,我们决定去掉 Button 的原始类型重载,为每种 Button 仅留下包含针对内容的可组合 lambda 参数的 API。我们开始将这个通用的 API 形式叫做 "slot API",现已经广泛应用于各个组件。

www.zeeklog.com  - Button 的 "进化之旅" | 我们是如何设计 Compose API 的 (下篇)
Button(backgroundColor = Color.Purple) {
    // 任何可组合内容都可以写在这里
}

△ 带有空白 "slot" 的 Button

www.zeeklog.com  - Button 的 "进化之旅" | 我们是如何设计 Compose API 的 (下篇)
Button(backgroundColor = Color.Purple) {
    Row {
        MyImage()
        Spacer(4.dp)
        Text("Button")
    }
}

△ 带有横向排列的图片和文本的 Button

一个 "slot" 代表一个可组合的 lambda 参数,它代表组件中的任意内容,比如 Text 或者 Icon。Slot API 增加了可组合性,使组件更加简单,减少了组件之间的独立概念数量,使开发者可以快速上手创建一个新的组件,或者在不同的组件之间切换。

www.zeeklog.com  - Button 的 "进化之旅" | 我们是如何设计 Compose API 的 (下篇)

△ 移除原始类型重载的 CL

展望未来

我们对 Button API 所做的修改数量之多,在讨论 Button 的会议中所付出的时间之多,以及收集开发者的反馈所投入的精力之巨大,足以惊人。话虽如此,我们对 API 整体的效果非常满意。事后看来,我们看到在 Compose 中 Button 变得更具可发现性、可定制性,最重要的是它促进了组合式思维。

@Composable
fun Button(
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
    enabled: Boolean = true,
    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
    elevation: ButtonElevation? = ButtonDefaults.elevation(),
    shape: Shape = MaterialTheme.shapes.small,
    border: BorderStroke? = null,
    colors: ButtonColors = ButtonDefaults.buttonColors(),
    contentPadding: PaddingValues = ButtonDefaults.ContentPadding,
    content: @Composable RowScope.() -> Unit
) {
    // 实现体代码
}

△ 1.0 Button AP

1.0 Button AP

https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/material/material/src/commonMain/kotlin/androidx/compose/material/Button.kt;l=96?q=Button.kt

重要的是认识到,我们的设计决策都基于下面这句口号:

让简单的开发变得简单,让困难的开发变得可能。*

*这里出自著名的技术类书籍: 英文版:《Learning Perl: Making Easy Things Easy and Hard Things Possible》(Randal L. Schwartz、Brian D Foy 和 Tom Phoenix 著),中文版:《Perl 语言入门》(盛春译)

我们尝试通过减少重载,并将 "样式" 扁平化处理,使开发变得更加简单。与此同时,我们改进了 Android Studio 的自动补全功能,来帮助开发者提高效率。

这里我们希望特别提出在整个 API 设计过程中的两个要点:

  1. API 的设计是一个迭代的过程。在 API 最初的迭代中就达到完美的状态是几乎不可能的。有一些需求容易被忽视。作为一个 API 的作者,您需要做出一些假设。这其中包括开发者背景的不同,所带来的不同思维方式¹,最终影响了开发者探索和使用 API 的方式。适配调整是无法避免的,这是好事,不断迭代可以得到可用性更高并且更加直观的 API。
  2. 在迭代一个 API 设计时,您最有价值的工具之一是开发者使用 API 体验的反馈循环。对我们的团队来说,最关键的是去理解开发者所说的 "这个 API 太复杂了" 意味着什么。当错误调用 API 时,通常会降低开发者的成功率和效率,从中所获得感悟,会帮助我们更深入理解 "复杂 API" 的意思。我们不断迭代的关键驱动力是我们要设计易用且出色的 API。为此,创建开发者反馈循环,我们使用了多种研究路径 —— 现场编程活动²,和需要开发者提供体验日记³ 的远程途径。我们已经可以理解开发者是如何处理 API,以及他们为打算实现的功能,找到正确方法所采取的路径。诸如工程师思维方式 (Programmer Thinking Styles) 和认知纬度 (Cognitive Dimensions) 这类框架中的支柱,有助于我们跨职能团队保持语言思维上的一致,不仅表现在审核、沟通开发者反馈中,也涉及到 API 设计讨论。尤其是,当评估用户体验和功能性之间的关系时,这个框架帮助我们塑造了为选择和权衡所做的讨论。
  3. 来自 Android Developer UX 团队的 Meital Tagor Sbero 受到角色模型和思维方式 (personas & Thinking Styles) 的设计和认知维度框架 (Cognitive Dimensions Framework) 的启发,开发了工程师思维方式框架 (Programmer Thinking Styles Framework)。该框架使用开发者在限定时间内所需 "解决方案的类型"的动机和态度,帮助开发者确定 API 可用性的设计思路。它兼顾了普通工程师的工作方式,并且针对高强度开发任务优化了可用性。
  4. 我们通常使用这种方式评估 API 特定方面的可用性。比如,每个活动会邀请一组开发者使用 Button API 来完成一系列开发任务,这些任务会特意暴露一些 API 的特征,而这些特征是我们希望收集反馈的目标。我们通过放声思考法,来获得更多关于开发者所追求的和开发者所设想的信息。这些活动中还包含研究者通过一些随访的问题,来进一步了解开发者的需求。我们会回顾这些活动,从而确定开发者在编程任务中促成成功或者导致失败的行为模式。
  5. 我们通常使用这种方式来评估 API 在一段时间内的可用性和易学习性。这种方式可以通过倾听开发者在常规工作中的反馈,来捕捉遇到困难的瞬间和受到启发的瞬间。在这个过程中,我们会有一组开发者开发由他们自选的特定项目,同时也确保他们会使用我们希望评估的 API。我们会结合开发者通过自行提交的日记,和由研究人员基于认知维度框架 (Cognitive Dimensions Framework) (示例) 所组织的深度调查,以及专访活动来帮助我们确定 API 的可用性。

Meital Tagor Sbero

https://www.linkedin.com/in/meitaltagor/

角色模型和思维方式 (personas & Thinking Styles)

https://medium.com/inclusive-software/tagged/thinking-styles

认知维度框架 (Cognitive Dimensions Framework)

https://www.researchgate.net/profile/Marian-Petre-4/publication/200085937_Usability_Analysis_of_Visual_Programming_Environments_A_%27Cognitive_Dimensions%27_Framework/links/02bfe50fbf23476730000000/Usability-Analysis-of-Visual-Programming-Environments-A-Cognitive-Dimensions-Framework.pdf

示例

https://arxiv.org/pdf/1703.09846.pdf

我们承认虽然我们对现有版本的 Button API 很满意,但是我们也知道它并不是完美的。开发者的思维方式有很多,加上不同的应用场景,以及层出不穷的需求,要求我们要不断迎接新的挑战。这都不是问题!Button 的整个进化过程,对于我们和开发者社区的意义都很大。所有这些都是为 Compose 设计和塑造了一个可用的 Button API —— 一个可以在屏幕上点击的简单矩形。

希望这篇文章能够帮助大家清楚了解到您的反馈如何帮助我们改进 Compose 中 Button API。如果您在使用 Compose 时遇到任何问题,或者对新 API 的体验提升有任何建议和想法,请告诉我们。欢迎广大开发者参与到我们接下来的用户调研活动中,期待您的注册报名。

问题提出和建议反馈

https://issuetracker.google.com/issues/new?component=612128&template=1253476

Google 用户体验调研

https://google.qualtrics.com/jfe/form/SV_3NMIMtX0F2zkakR?reserved=1&utm_source=Survey&Q_Language=en&utm_medium=own_evt&utm_campaign=Q1&productTag=adstu&campaignDate=February2021&referral_code=UXSf298725

也欢迎您通过下方二维码向我们提交反馈,或分享您喜欢的内容、发现的问题。您的反馈对我们非常重要,感谢您的支持!

www.zeeklog.com  - Button 的 "进化之旅" | 我们是如何设计 Compose API 的 (下篇)

推荐阅读

如页面未加载,请刷新重试

www.zeeklog.com  - Button 的 "进化之旅" | 我们是如何设计 Compose API 的 (下篇)

点击屏末 | 阅读原文 | 即刻了解使用 Jetpack Compose 打造更出色的应用


www.zeeklog.com  - Button 的 "进化之旅" | 我们是如何设计 Compose API 的 (下篇)
www.zeeklog.com  - Button 的 "进化之旅" | 我们是如何设计 Compose API 的 (下篇)
www.zeeklog.com  - Button 的 "进化之旅" | 我们是如何设计 Compose API 的 (下篇)

Read more

2018年阿里巴巴关于Java重要开源项目汇总

2018年阿里巴巴关于Java重要开源项目汇总

1.分布式应用服务开发的一站式解决方案 Spring Cloud Alibaba Spring Cloud Alibaba 致力于提供分布式应用服务开发的一站式解决方案。此项目包含开发分布式应用服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。 依托 Spring Cloud Alibaba,您只需要添加一些注解和少量配置,就可以将 Spring Cloud 应用接入阿里分布式应用解决方案,通过阿里中间件来迅速搭建分布式应用系统。 地址:https://github.com/spring-cloud-incubator/spring-cloud-alibaba 2. JDBC 连接池、监控组件 Druid Druid是一个 JDBC 组件。 1.监控数据库访问性能。 2.提供了一个高效、功能强大、可扩展性好的数据库连接池。 3.数据库密码加密。 4.SQL执行日志。 地址:https:

By Ne0inhk
Mysql学习总结(70)——MySQL 优化实施方案

Mysql学习总结(70)——MySQL 优化实施方案

1.1 前言 在进行MySQL的优化之前必须要了解的就是MySQL的查询过程,很多的查询优化工作实际上就是遵循一些原则让MySQL的优化器能够按照预想的合理方式运行而已。 图 - MySQL查询过程 1.2 优化的哲学 优化有风险,涉足需谨慎 1.2.1 优化可能带来的问题 优化不总是对一个单纯的环境进行,还很可能是一个复杂的已投产的系统。 优化手段本来就有很大的风险,只不过你没能力意识到和预见到! 任何的技术可以解决一个问题,但必然存在带来一个问题的风险! 对于优化来说解决问题而带来的问题,控制在可接受的范围内才是有成果。 保持现状或出现更差的情况都是失败! 1.2.2 优化的需求 稳定性和业务可持续性,通常比性能更重要! 优化不可避免涉及到变更,变更就有风险! 优化使性能变好,维持和变差是等概率事件! 切记优化,应该是各部门协同,共同参与的工作,任何单一部门都不能对数据库进行优化! 所以优化工作,是由业务需要驱使的!!! 1.2.3 优化由谁参与 在进行数据库优化时,应由数据库管理员、业务部门代表、应用程序架构师、

By Ne0inhk
Mysql学习总结(71)——数据库介绍(MySQL安装 体系结构、基本管理)再回顾

Mysql学习总结(71)——数据库介绍(MySQL安装 体系结构、基本管理)再回顾

1.1 数据库简介 数据库,简而言之可视为电子化的文件柜——存储电子文件的处所,用户可以对文件中的数据运行新增、截取、更新、删除等操作。所谓“数据库”系以一定方式储存在一起、能予多个用户共享、具有尽可能小的冗余度、与应用程序彼此独立的数据集合。 1.1.1 什么是数据 数据是指对客观事件进行记录并可以鉴别的符号,是对客观 事物的性质、状态以及相互关系等进行记载的物理符号或这些物 理符号的组合。它是可识别的、抽象的符号。 1.1.2 数据库管理系统 非关系型数据库 NoSQL:非关系型数据库(Not only SQL) 不是否定关系型数据库,做关系型数据库的的补充。 想做老大,先学会做老二。 关系型数据库 关系型数据库的特点 二维表 典型产品 Oracle传统企业,MySQL是互联网企业 数据存取是通过SQL(结构化查询语句) 最大特点,

By Ne0inhk
互联网金融学习总结(1)——互联网金融(ITFIN)概念相关学习

互联网金融学习总结(1)——互联网金融(ITFIN)概念相关学习

前言 互联网金融(ITFIN)就是互联网技术和金融功能的有机结合,依托大数据和云计算在开放的互联网平台上形成的功能化金融业态及其服务体系,包括基于网络平台的金融市场体系、金融服务体系、金融组织体系、金融产品体系以及互联网金融监管体系等,并具有普惠金融、平台金融、信息金融和碎片金融等相异于传统金融的金融模式。 简介 互联网金融是传统金融机构与互联网企业(以下统称从业机构)利用互联网技术和信息通信技术实现资金融通、支付、投资和信息中介服务的新型金融业务模式。互联网与金融深度融合是大势所趋,将对金融产品、业务、组织和服务等方面产生更加深刻的影响。互联网金融对促进小微企业发展和扩大就业发挥了现有金融机构难以替代的积极作用,为大众创业、万众创新打开了大门。促进互联网金融健康发展,有利于提升金融服务质量和效率,深化金融改革,促进金融创新发展,扩大金融业对内对外开放,构建多层次金融体系。作为新生事物,互联网金融既需要市场驱动,鼓励创新,也需要政策助力,促进发展。 发展历程 中国互联网金融发展历程要远短于美欧等发达经济体。截至目前,中国互联网金融大致可以分为三个发展阶段:第一个阶段是1990

By Ne0inhk