close
本文由 Jetpack Compose 團隊的 Louis Pullen-Freilich (軟件工程師)、Matvei Malkov (軟件工程師) 和 Preethi Srinivas (UX 研究員) 共同撰寫。

近期 Jetpack Compose 發布了 1.0 版本,帶來了一系列用於構建 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 添加樣式

△ 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",現已經廣泛應用於各個組件。

Button(backgroundColor=Color.Purple){ // 任何可組合內容都可以寫在這裡}
△ 帶有空白 "slot"的 Button

Button(backgroundColor = Color.Purple) { Row { MyImage() Spacer(4.dp) Text("Button") }}

△ 帶有橫向排列的圖片和文本的 Button

一個 "slot"代表一個可組合的 lambda 參數,它代表組件中的任意內容,比如 Text 或者 Icon。Slot API 增加了可組合性,使組件更加簡單,減少了組件之間的獨立概念數量,使開發者可以快速上手創建一個新的組件,或者在不同的組件之間切換。
△ 移除原始類型重載的 CL

展望未來




我們對 Button API 所做的修改數量之多,在討論Button 的會議中所付出的時間之多,以及收集開發者的反饋所投入的精力之巨大,足以驚人。話雖如此,我們對 API 整體的效果非常滿意。事後看來,我們看到在 Compose 中 Button 變得更具可發現性、可定製性,最重要的是它促進了組合式思維。

@Composablefun 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 設計過程中的兩個要點:

API 的設計是一個迭代的過程。在 API 最初的迭代中就達到完美的狀態是幾乎不可能的。有一些需求容易被忽視。作為一個 API 的作者,您需要做出一些假設。這其中包括開發者背景的不同,所帶來的不同思維方式¹,最終影響了開發者探索和使用 API 的方式。適配調整是無法避免的,這是好事,不斷迭代可以得到可用性更高並且更加直觀的 API。

在迭代一個 API 設計時,您最有價值的工具之一是開發者使用 API 體驗的反饋循環。對我們的團隊來說,最關鍵的是去理解開發者所說的 "這個 API 太複雜了" 意味着什麼。當錯誤調用 API 時,通常會降低開發者的成功率和效率,從中所獲得感悟,會幫助我們更深入理解 "複雜 API"的意思。我們不斷迭代的關鍵驅動力是我們要設計易用且出色的 API。為此,創建開發者反饋循環,我們使用了多種研究路徑 —— 現場編程活動²,和需要開發者提供體驗日記³的遠程途徑。我們已經可以理解開發者是如何處理 API,以及他們為打算實現的功能,找到正確方法所採取的路徑。諸如工程師思維方式 (Programmer Thinking Styles) 和認知緯度 (Cognitive Dimensions) 這類框架中的支柱,有助於我們跨職能團隊保持語言思維上的一致,不僅表現在審核、溝通開發者反饋中,也涉及到 API 設計討論。尤其是,當評估用戶體驗和功能性之間的關係時,這個框架幫助我們塑造了為選擇和權衡所做的討論。

來自 Android Developer UX 團隊的 Meital Tagor Sbero 受到角色模型和思維方式 (personas & Thinking Styles) 的設計和認知維度框架 (Cognitive Dimensions Framework) 的啟發,開發了工程師思維方式框架 (Programmer Thinking Styles Framework)。該框架使用開發者在限定時間內所需 "解決方案的類型"的動機和態度,幫助開發者確定 API 可用性的設計思路。它兼顧了普通工程師的工作方式,並且針對高強度開發任務優化了可用性。

我們通常使用這種方式評估 API 特定方面的可用性。比如,每個活動會邀請一組開發者使用 Button API 來完成一系列開發任務,這些任務會特意暴露一些 API 的特徵,而這些特徵是我們希望收集反饋的目標。我們通過放聲思考法,來獲得更多關於開發者所追求的和開發者所設想的信息。這些活動中還包含研究者通過一些隨訪的問題,來進一步了解開發者的需求。我們會回顧這些活動,從而確定開發者在編程任務中促成成功或者導致失敗的行為模式。

我們通常使用這種方式來評估 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

也歡迎您通過下方二維碼向我們提交反饋,或分享您喜歡的內容、發現的問題。您的反饋對我們非常重要,感謝您的支持!



推薦閱讀



如頁面未加載,請刷新重試


點擊屏末|閱讀原文|即刻了解使用 Jetpack Compose 打造更出色的應用


arrow
arrow
    全站熱搜
    創作者介紹
    創作者 鑽石舞台 的頭像
    鑽石舞台

    鑽石舞台

    鑽石舞台 發表在 痞客邦 留言(0) 人氣()