軟體工程師
在本課程中,我們將深入探討技術,瞭解全螢幕資訊方塊如何演變成動態、部分高度的 Wear 小工具。
我們將探討統一 Jetpack Glance API 和新 Remote Compose 引擎的技術優勢,並詳細說明這些技術如何與 Modern Android Development 完美搭配。
最後,我們會分享最新的使用者體驗指南,並逐步說明實用的遷移策略,協助您打造新一代的資訊一覽 UI。
轉錄稿
大家好!
我是 Frank,負責 Wear OS 的一目瞭然體驗。
手錶是使用者全天候不可或缺的夥伴,今天我們將逐步說明如何運用全新的 Wear 小工具體驗。
我們將探討如何使用最新 API,直接在使用者手腕上提供最重要的功能。這些 API 旨在讓 Android 開發人員 (例如您) 熟悉且直覺地建構 Wear OS。
今天和我一起上台的是我的優秀同事兼鄰座夥伴 Yuri,他將深入探討我們如何做到這一點。
大家好,我是 Yuri Schimke。
我是 Wear OS 團隊的工程師,主要負責 Remote Compose API。
今天我會簡單介紹 Remote Compose,並說明從資訊方塊遷移至 Wear 小工具的程序。
接著,我會將時間交回給 Frank。
為了盡可能順暢地從資訊方塊轉換為小工具,我們推出了兩種標準容器大小:小型和大型。
這些格式與您在行動裝置上建構小工具時,所習慣的 2x1 和 2x2 格狀小工具格式非常相似。
在部分硬體製造商的 Wear 7 裝置上,Wear 小工具會與其他應用程式的小工具一起顯示在垂直捲動匣中。
舉例來說,Samsung Galaxy Watch 上的 Wear 小工具會像這樣。
平台架構支援垂直捲動匣,方便硬體製造商日後採用這種模式。
您也不必擔心會遺漏任何使用者或現有使用者。
Jetpack Glance Wear API 旨在支援回溯相容性,最低可支援 Wear 4。
如果舊款裝置支援水平小工具匣或資訊方塊匣,系統會自動處理。
您只需在程式碼中聲明要支援全螢幕模式,系統就會在這些裝置上,將大型小工具顯示為全螢幕傳統資訊方塊。
現在,Yuri 將逐步說明程式碼,示範如何將現有資訊方塊遷移至 Wear 小工具。
謝謝 Frank。
從頭開始建構新的 Wear 小工具非常簡單。
您只需定義 GlanceWearWidgetService,並使用 Remote Compose 建構 UI 即可。
不過,如果應用程式已有正式版資訊方塊,則必須遷移服務、UI 和資料。
為盡可能順利完成轉換,我們提供多種方式,確保現有使用者享有流暢體驗。
服務方面,您可以選擇雙重服務或單一服務策略。
我們建議採用雙重服務,也就是保留現有的 TileService,用於舊版 Wear OS 裝置,並新增 GlanceWearWidgetService,用於 Wear OS 7 以上版本。
相較之下,如果使用單一服務,您會建立新的單一 GlanceWearWidgetService,用於處理這兩種通訊協定,並在相容模式下執行,將小工具轉譯為舊版裝置上的資訊方塊。
無論採用哪種方式,都能繼續觸及使用者。
只會影響日後要維護的程式碼。
我們剛在 Android 開發人員網站上發布了完整說明文件,詳細說明這兩種模式的使用方式和時機。
歡迎參閱,瞭解哪種策略最適合您的應用程式。
現在來談談 UI 層。
遷移時,您的思考框架會改變。
先前,資訊方塊使用的是 ProtoLayout,這是專為 Wear 資訊方塊建構的程式庫。
Wear 小工具則是以 Remote Compose 為基礎。
Remote Compose 與 Jetpack Compose UI 採用相同的基礎,使用相同的核心編譯器和執行階段。
此外,這項工具的設計與 Compose UI 程式庫 (例如 Foundation 和 Material 3) 非常相似,因此您能輕鬆上手。
但您只需將方塊、欄或文字換成 RemoteBox、RemoteColumn 或 RemoteText 即可。
在這種情況下,您會經常寫到「remote」。
此外,為了加快工作流程,我們還提供一整套專為小工具設計、可立即使用的 Remote Compose 元件。
也就是說,現有的 Wear Material 3 設計 (包括 Figma 中的設計) 都能輕鬆轉換成小工具,工程師和設計師都能輕鬆上手。
為了瞭解這些架構的一致性,我們來看一個具體範例。
我們將在 Material 3 切換按鈕上建構自己的應用程式元件。
左側是您在應用程式中編寫的標準 Wear Compose 元件,右側則是 Remote Compose 版本。
// Wear Compose
@Composable
fun HistoryEraserButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
checked: Boolean = true,
onCheckedChange: (Boolean) -> Unit = {},
)// Wear RemoteCompose
@RemoteComposable
@Composable
fun RemoteHistoryEraserButton(
onClick: Action,
modifier: RemoteModifier = RemoteModifier,
checked: RemoteBoolean = true.rb,
onCheckedChange: Action = Action.Empty,
)請注意兩者的模式有多相似。
不過,編寫遠端 Compose 程式碼時,您會使用 RemoteComposable 註解。
如果您使用修飾符和布林值等熟悉的參數,這些參數會有對應的遠端 Compose 類型,例如 RemoteModifier 和 RemoteBoolean。
並使用動作,而非 lambda。
您不必定義要在 lambda 內執行的程式碼,而是需要宣告動作,例如啟動待處理意圖、呼叫服務或更新值。
接著來看看元件的主體。
您會發現連主題都一致。
您可以將 Material 3 色彩配置套用至遠端 Compose 元件,就像在標準 Compose 中一樣,讓小工具與應用程式的品牌宣傳完全一致。
// Wear Compose
val colors = MaterialTheme.colorScheme
SplitSwitchButton(
checked = checked,
onCheckedChange = onCheckedChange,
toggleContentDescription = "Arm Eraser",
onContainerClick = onClick,
modifier = modifier.fillMaxWidth(),
colors =
SwitchButtonDefaults.splitSwitchButtonColors(
checkedContainerColor = colors.errorContainer, …
),
label = { Text("History Eraser") },
secondaryLabel = { Text("Don't touch it!") },
)// Wear RemoteCompose
val colors = RemoteMaterialTheme.colorScheme
RemoteSplitSwitchButton(
checked = checked,
onCheckedChange = onCheckedChange,
toggleContentDescription = "Arm Eraser".rs,
onContainerClick = onClick,
modifier = modifier.fillMaxWidth(),
colors =
RemoteSplitSwitchButtonDefaults.splitSwitchButtonColors(
checkedContainerColor = colors.errorContainer, …
),
label = { RemoteText("History Eraser".rs) },
secondaryLabel = { RemoteText("Don't touch it!".rs) },
)最後,我們來看看結果。產生的 UI 正如預期,是簡潔、回應式、動畫切換鈕。
很難分辨兩者差異。
為了協助您設計、疊代及驗證小工具版面配置,而不必一律在裝置或模擬器上執行,我們新增了 Wear 小工具的 Compose 預覽支援。
由於小工具取決於原始設備製造商 (OEM) 特定的容器限制 (例如大小、形狀、邊框間距、圓角半徑),因此我們以標準預覽註解為基礎,但使用額外的參數提供者來插入這些設定。
@Preview(name = "Squircle Preview", device = "spec:width=1000dp,height=1000dp,dpi=320")
@Composable
fun WeatherWidgetSquirclePreview(
@PreviewParameter(SquircleAllWidgetPreviewParams::class) params: WearWidgetParams
) = WearWidgetPreview(MockWeatherWidget(65, WeatherCondition.CLOUDY), params)
@Preview(name = "Round Preview", device = "spec:width=1000dp,height=1000dp,dpi=320")
@Composable
fun WeatherWidgetRoundPreview(
@PreviewParameter(RoundAllWidgetPreviewParams::class) params: WearWidgetParams
) = WearWidgetPreview(MockWeatherWidget(65, WeatherCondition.CLOUDY), params)程式庫提供預先設定的 PreviewParameter 供應器實作,與實際的 Wear OS 裝置相符。
這些預覽註解可讓您輕鬆驗證各種硬體形狀的版面配置。
除了測試版面配置外,您也可以使用這些預覽畫面,產生裝置端小工具挑選器所需的靜態圖片。
@Preview(name = "Widget Picker Preview", device = "spec:width=1000dp,height=1000dp,dpi=320")
@Composable
fun WeatherWidgetRectangularPreview(
@PreviewParameter(RectangularAllWidgetPreviewParams::class) params: WearWidgetParams
) = WearWidgetPreview(MockWeatherWidget(65, WeatherCondition.CLOUDY), params)程式庫提供矩形預覽形狀的參數,可模擬完整未裁剪的矩形容器。
如果想瞭解實際運作方式,可以查看 Horologist 媒體範例應用程式,我們已更新這個應用程式,加入展示這個 API 的新小工具。
我們會說明如何新增小型和大型媒體小工具。
請前往 GitHub 查看,並排比較舊版 ProtoLayout 程式碼和新的 Remote Compose 實作。
相信你會喜歡。
最後,這場演講當然少不了 AI。
為加快遷移速度,我們在 GitHub 上發布了新的「將資訊方塊遷移至小工具」技能。
您可以搭配偏好的代理使用,也可以透過 Android CLI 直接新增至 Android Studio。
android skills add wear-widgets
android skills add --all現在,我將交棒給 François,由他為大家總結。
// Wear Compose
@Composable
fun HistoryEraserButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
checked: Boolean = true,
onCheckedChange: (Boolean) -> Unit = {},
)// Wear RemoteCompose
@RemoteComposable
@Composable
fun RemoteHistoryEraserButton(
onClick: Action,
modifier: RemoteModifier = RemoteModifier,
checked: RemoteBoolean = true.rb,
onCheckedChange: Action = Action.Empty,
)// Wear Compose
val colors = MaterialTheme.colorScheme
SplitSwitchButton(
checked = checked,
onCheckedChange = onCheckedChange,
toggleContentDescription = "Arm Eraser",
onContainerClick = onClick,
modifier = modifier.fillMaxWidth(),
colors =
SwitchButtonDefaults.splitSwitchButtonColors(
checkedContainerColor = colors.errorContainer, …
),
label = { Text("History Eraser") },
secondaryLabel = { Text("Don't touch it!") },
)// Wear RemoteCompose
val colors = RemoteMaterialTheme.colorScheme
RemoteSplitSwitchButton(
checked = checked,
onCheckedChange = onCheckedChange,
toggleContentDescription = "Arm Eraser".rs,
onContainerClick = onClick,
modifier = modifier.fillMaxWidth(),
colors =
RemoteSplitSwitchButtonDefaults.splitSwitchButtonColors(
checkedContainerColor = colors.errorContainer, …
),
label = { RemoteText("History Eraser".rs) },
secondaryLabel = { RemoteText("Don't touch it!".rs) },
)@Preview(name = "Squircle Preview", device = "spec:width=1000dp,height=1000dp,dpi=320")
@Composable
fun WeatherWidgetSquirclePreview(
@PreviewParameter(SquircleAllWidgetPreviewParams::class) params: WearWidgetParams
) = WearWidgetPreview(MockWeatherWidget(65, WeatherCondition.CLOUDY), params)
@Preview(name = "Round Preview", device = "spec:width=1000dp,height=1000dp,dpi=320")
@Composable
fun WeatherWidgetRoundPreview(
@PreviewParameter(RoundAllWidgetPreviewParams::class) params: WearWidgetParams
) = WearWidgetPreview(MockWeatherWidget(65, WeatherCondition.CLOUDY), params)@Preview(name = "Widget Picker Preview", device = "spec:width=1000dp,height=1000dp,dpi=320")
@Composable
fun WeatherWidgetRectangularPreview(
@PreviewParameter(RectangularAllWidgetPreviewParams::class) params: WearWidgetParams
) = WearWidgetPreview(MockWeatherWidget(65, WeatherCondition.CLOUDY), params)android skills add wear-widgets
android skills add --all