Compose for Wear OS 程式碼研究室

1. 簡介

2c9dd335c9d65f10.png

Compose for Wear OS 的用法與 Jetpack Compose 相同,您可以根據先前的經驗建構適用於穿戴式裝置的應用程式。

Compose for Wear OS 內建 Material Design 支援機制,可簡化及加速 UI 開發作業,協助您用較少的程式碼打造精美的應用程式。

在這個程式碼研究室中,我們希望您能對 Compose 有些基本瞭解,但不需要達到專家的程度。

我們將使用 Horologist,這是以 Jetpack Compose 為基礎建構的開放原始碼專案,可協助開發人員加速開發應用程式。

您將建立多種 Wear 專屬可組合函式 (包括簡易與複雜),最後,您可開始自行編寫適用於 Wear OS 的應用程式。立即開始!

學習目標

  • 與舊版 Compose 的異同
  • 簡易可組合函式,以及在 Wear OS 上的運作方式
  • Wear OS 專屬的可組合函式
  • Wear OS 的 LazyColumn (ScalingLazyColumn)
  • Wear OS 的 Scaffold 版本

建構目標

您會建構一個簡單的應用程式,顯示可組合函式捲動式清單,這些可組合函式已針對 Wear OS 進行最佳化。

由於您使用 Scaffold,系統也會在頂端顯示弧形文字時間、暈影,以及沿著裝置側邊顯示的捲動指標。

完成本程式碼研究室後,您建立的應用程式會如下所示:

31cb08c0fa035400.gif

必要條件

2. 開始設定

在這個步驟中,您會設定環境並下載範例專案。

軟硬體需求

  • 最新的 Android Studio 穩定版
  • Wear OS 裝置或模擬器 (剛開始使用嗎?這裡有設定方式的說明)

下載程式碼

如果您已安裝 Git,只要執行下列指令即可複製這個存放區的程式碼。如要檢查 Git 是否已安裝完成,請在終端機或指令列中輸入 git --version,並確認該指令可正確執行。

git clone https://github.com/android/codelab-compose-for-wear-os.git
cd compose-for-wear-os

如果您沒有 Git,可以點選下方按鈕,下載這個程式碼研究室的所有程式碼:

您隨時可以變更工具列中的執行設定,在 Android Studio 中執行任一模組。

b059413b0cf9113a.png

在 Android Studio 中開啟專案

  1. 在「Welcome to Android Studio」視窗中,選取「c01826594f360d94.png Open an Existing Project」
  2. 選取資料夾 [Download Location]
  3. Android Studio 匯入專案後,請測試是否能在 Wear OS 模擬器或實體裝置上執行 startfinished 模組。
  4. start 模組應如以下螢幕截圖所示。您會在該處執行所有工作。

d6d4b92ac53d9b3e.png

探索範例程式碼

  • build.gradle 包含基本的應用程式設定。這包括建立 Composable Wear OS 應用程式所需的依附元件。我們將探討 Jetpack Compose 與 Wear OS 版本有何相似與相異之處。
  • main > AndroidManifest.xml 包含建立 Wear OS 應用程式所需的元素。這與非 Compose 應用程式相同,且與行動應用程式類似,因此我們不會進行審查。
  • main > theme/ 資料夾包含 Compose 用於主題的 ColorTypeTheme 檔案。
  • main > MainActivity.kt 包含用於透過 Compose 建立應用程式的樣板。其中也包含我們應用程式的頂層可組合內容 (例如 ScaffoldScalingLazyList)。
  • main > ReusableComponents.kt 包含我們將要建立的大部分 Wear 特定可組合內容函式。我們將會在這個檔案中執行眾多作業。

3. 查看依附元件

您對 Wear 相關依附元件所做的變更,多半會位於架構層頂端 (下圖中以紅框標示處)。

d92519e0b932f964.png

這表示許多搭配 Jetpack Compose 使用的依附元件,在 Wear OS 版本還是可以繼續沿用。舉例來說,您可以使用相同的使用者介面、執行階段、編譯器和動畫依附元件。

不過,先前使用的程式庫就不一定適用了,您必須採用適當的 Wear OS MaterialFoundationNavigation 程式庫。

您可透過下方的比較協助釐清差異:

Wear OS 依附元件(androidx.wear.*)

比較

標準依附元件 (androidx.*)

androidx.wear.compose:compose-material

而非

androidx.compose.material:material

androidx.wear.compose:compose-navigation

而非

androidx.navigation:navigation-compose

androidx.wear.compose:compose-foundation

除了

androidx.compose.foundation:foundation

androidx.wear.compose:compose-ui-tooling

加上

androidx.compose.ui:ui-tooling-preview

1. 開發人員可繼續使用如漣漪材質等其他材質相關程式庫,以及透過 Wear Compose Material 程式庫延伸的質感設計圖示。

開啟 build.gradle,在 start 模組中搜尋「TODO: Review Dependencies」。(這個步驟只是要查看依附元件,您將不會新增任何程式碼。)

start/build.gradle:

   def composeBom = platform(libs.androidx.compose.bom)

    // General compose dependencies
    implementation composeBom
    implementation libs.androidx.activity.compose
    implementation libs.compose.ui.tooling.preview

    implementation(libs.androidx.material.icons.extended)

    // Compose for Wear OS Dependencies
    implementation libs.wear.compose.material

    // Foundation is additive, so you can use the mobile version in your Wear OS app.
    implementation libs.wear.compose.foundation

    // Compose preview annotations for Wear OS.
    implementation(libs.androidx.compose.ui.tooling)

    debugImplementation libs.compose.ui.tooling
    debugImplementation libs.androidx.ui.test.manifest
    debugImplementation composeBom

您應會辨識眾多一般 Compose 依附元件,因此其不在我們的涵蓋範圍之列。

接著來看看 Wear OS 依附元件。

如前所述,系統只會納入 Wear OS 特定版本的 material (androidx.wear.compose:compose-material)。也就是說,您不會在專案中看到或包含 androidx.compose.material:material

請務必留意,您可以將其他材質程式庫與 Wear Material 搭配使用。我們實際上已在這個程式碼研究室中納入 androidx.compose.material:material-icons-extended,以實踐上述做法。

最後,我們納入適用於 Compose 的 Wear foundation 程式庫 (androidx.wear.compose:compose-foundation)。這是附加內容,方便您與先前所用的標準 foundation 搭配使用。事實上,您可能已經注意到我們在一般 Compose 依附元件中納入上述內容!

瞭解依附元件之後,我們來看看主要應用程式。

4. 查看 MainActivity

我們會在

start

模組中執行所有工作,因此請確認在模組中開啟每個檔案。

首先,請在 start 模組中開啟 MainActivity

這個相當簡單的類別延伸了 ComponentActivity,並使用 setContent { WearApp() } 來建立使用者介面。

根據您先前對 Compose 的瞭解,這對您來說應並不陌生。我們只是在設定使用者介面。

向下捲動至 WearApp() 可組合函式。在我們討論程式碼本身之前,您應該會看到許多 TODO 四散在程式碼中,每個都代表本程式碼研究室中的步驟。您可以暫時忽略這些資訊。

如下所示:

趣味 WearApp() 中的程式碼:

WearAppTheme {
     /* *************************** Part 4: Wear OS Scaffold *************************** */
    // TODO (Start): Create a AppScaffold (Wear Version)

    // TODO: Swap to ScalingLazyColumnState
    val listState = rememberLazyListState()

    /* *************************** Part 4: Wear OS Scaffold *************************** */
    // TODO (Start): Create a ScreenScaffold (Wear Version)

    // Modifiers used by our Wear composables.
    val contentModifier = Modifier.fillMaxWidth().padding(bottom = 8.dp)
    val iconModifier = Modifier.size(24.dp).wrapContentSize(align = Alignment.Center)

    /* *************************** Part 3: ScalingLazyColumn *************************** */
    // TODO: Swap a ScalingLazyColumn (Wear's version of LazyColumn)
    LazyColumn(
        modifier = Modifier.fillMaxSize(),
        contentPadding = PaddingValues(
            top = 32.dp,
            start = 8.dp,
            end = 8.dp,
            bottom = 32.dp,
        ),
        verticalArrangement = Arrangement.Center,
        state = listState,
    ) {
        // TODO: Remove item; for beginning only.
        item { StartOnlyTextComposables() }

        /* ******************* Part 1: Simple composables ******************* */
        item { ButtonExample(contentModifier, iconModifier) }
        item { TextExample(contentModifier) }
        item { CardExample(contentModifier, iconModifier) }

        /* ********************* Part 2: Wear unique composables ********************* */
        item { ChipExample(contentModifier, iconModifier) }
        item { ToggleChipExample(contentModifier) }
        }

    // TODO (End): Create a ScreenScaffold (Wear Version)
    // TODO (End): Create a AppScaffold (Wear Version)
}

首先請設定主題 WearAppTheme { }。設定方式與您先前撰寫的方式完全相同,亦即以顏色、字體排版和形狀來設定 MaterialTheme

不過,以 Wear OS 來說,我們通常建議使用預設的 Material Wear 形狀,這已針對圓形裝置完成最佳化處理,因此若您深入瞭解 theme/Theme.kt,會發現我們並未覆寫形狀。

如有需要,您可以開啟 theme/Theme.kt 進一步探索,但也與手機上呈現的內容相同。

接下來,我們將為要建構的 Wear 可組合函式建立一些修飾符,這樣就無需每次都指定。其主要是以內容為中心,並增加一些邊框間距。

與先前的做法相同,我們後續會建立 LazyColumn,用於產生內含許多項目的垂直捲動清單。

程式碼:

item { StartOnlyTextComposables() }

/* ******************* Part 1: Simple composables ******************* */
item { ButtonExample(contentModifier, iconModifier) }
item { TextExample(contentModifier) }
item { CardExample(contentModifier, iconModifier) }

/* ********************* Part 2: Wear unique composables ********************* */
item { ChipExample(contentModifier, iconModifier) }
item { ToggleChipExample(contentModifier) }

針對項目本身,僅有 StartOnlyTextComposables() 會產生任一使用者介面。(我們將會在整個程式碼研究室填入其餘部分)。

這些函式實際上是位於 ReusableComponents.kt 檔案,我們會於下節說明。

準備開始使用 Compose for Wear OS 吧!

5. 新增簡易的可組合函式

讓我們先來探討您可能已熟悉的三種可組合內容 (ButtonTextCard)。

首先,我們要移除 Hello World 可組合內容。

搜尋「TODO: Remove item」,然後清除留言和下一行:

步驟 1

// TODO: Remove item; for beginning only.
item { StartOnlyTextComposables() }

接著,讓我們來新增第一個可組合函式。

建立「按鈕」可組合函式

start 模組中開啟 ReusableComponents.kt 並搜尋「TODO: Create a Button Composable」,將目前的可組合內容方法替換為此程式碼。

步驟 2

// TODO: Create a Button Composable (with a Row to center)
@Composable
fun ButtonExample(
    modifier: Modifier = Modifier,
    iconModifier: Modifier = Modifier
) {
    Row(
        modifier = modifier,
        horizontalArrangement = Arrangement.Center
    ) {
        // Button
        Button(
            modifier = Modifier.size(ButtonDefaults.LargeButtonSize),
            onClick = { /* ... */ },
        ) {
            Icon(
                imageVector = Icons.Rounded.Phone,
                contentDescription = "triggers phone action",
                modifier = iconModifier
            )
        }
    }
}

ButtonExample() 可組合內容 (此程式碼所在函式) 現會產生置中按鈕。

讓我們來逐步瞭解程式碼。

Row 在此僅用於將 Button 可組合內容置中於圓形螢幕。實際做法是套用我們在 MainActivity 中建立的修飾符,並將其傳遞至此函式。之後在圓形螢幕上捲動時,我們會希望確保內容不會遭到截斷,這就是其會置中的原因所在。

接下來,我們要建立 Button 本身。程式碼與先前用於 Button 的程式碼相同,但本例中使用的是 ButtonDefault.LargeButtonSize。這些是針對 Wear OS 裝置最佳化的預設大小,因此請務必善加利用!

之後,我們會將點擊事件設為空白的 lambda。這些可組合內容在本範例中僅供示範用途,因此將無需派上用場。不過在實際應用程式中,我們會透過 ViewModel 等方式進行通訊,以便執行商業邏輯。

接著會在按鈕當中設定「圖示」。此程式碼與先前看過的 Icon 相同。我們也會從 androidx.compose.material:material-icons-extended 程式庫下載圖示,

最後,我們會設定先前針對「圖示」所設定的修飾符。

若您執行此應用程式,看起來應會像這樣:

881cfe2dcdef5687.png

這是您之前已撰寫的程式碼 (非常好)。差別在於,現在取得的按鈕會針對 Wear OS 進行最佳化調整。

一切皆相當直覺,讓我們來看看另一個主題吧。

建立「文字」可組合函式

ReusableComponents.kt 中搜尋「TODO: Create a Text Composable」,並將目前的可組合方法替換為本程式碼。

步驟 3

// TODO: Create a Text Composable
@Composable
fun TextExample(modifier: Modifier = Modifier) {
    Text(
        modifier = modifier,
        textAlign = TextAlign.Center,
        color = MaterialTheme.colors.primary,
        text = stringResource(R.string.device_shape)
    )
}

建立 Text 可組合函式、為其設定修飾符、對齊文字、設定顏色,最後再透過字串資源設定文字本身。

Compose 開發人員應該會認為文字可組合函式相當眼熟,相關程式碼實際上與您先前使用的程式碼完全相同。

其外觀如下:

b4f0e65e666cf3eb.png

TextExample() 可組合函式 (程式碼的所在位置) 現在會產生用於設定主要材質顏色的 Text 可組合函式。字串提取自 res/values/strings.xml 檔案。

到目前為止都很順利。我們來看看最後一個類似的可組合內容:Card

建立「卡片」可組合內容

ReusableComponents.kt 中搜尋「TODO: Create a Card」,並將目前的可組合方法替換為本程式碼。

步驟 4

// TODO: Create a Card (specifically, an AppCard) Composable
@Composable
fun CardExample(
    modifier: Modifier = Modifier,
    iconModifier: Modifier = Modifier
) {
    AppCard(
        modifier = modifier,
        appImage = {
            Icon(
                imageVector = Icons.Rounded.Message,
                contentDescription = "triggers open message action",
                modifier = iconModifier
            )
        },
        appName = { Text("Messages") },
        time = { Text("12m") },
        title = { Text("Kim Green") },
        onClick = { /* ... */ }
    ) {
        Text("On my way!")
    }
}

Wear 略有差異,其具有以下兩種主要的卡片:AppCardTitleCard

針對本範例,我們希望在資訊卡中使用 Icon,因此請使用 AppCard (TitleCard 的運算單元數量較少,詳情請參閱「資訊卡」指南)。

我們會建立 AppCard 可組合函式、設定其修飾符、新增 Icon、新增數個 Text 可組合參數 (分別用於資訊卡的不同處),最後在結尾處設定主要內容文字。

其外觀如下:

430eaf85d8ee5883.png

您現在可能已經發現,這些可組合函式的 Compose 程式碼其實與之前使用的相同。這樣一來,您可以重複運用現有的所有知識!

讓我們看看一些新的可組合內容。

6. 新增 Wear 專屬可組合內容

我們將在本節中探索 ChipToggleChip 可組合內容。

建立「方塊」可組合函式

在材質指南中實際上已指定方塊,但在標準材質資料庫中並無實際的可組合函式。

方塊是一種快速、輕觸一下的動作,尤其適合螢幕空間有限的 Wear 裝置。

以下是 Chip 可組合函式的幾個不同版本,讓您瞭解可以建立的項目:

現在,我們就開始動手寫程式吧。

ReusableComponents.kt 中搜尋「TODO: Create a Chip」,並將目前的可組合方法替換為本程式碼。

步驟 5

// TODO: Create a Chip Composable
@Composable
fun ChipExample(
    modifier: Modifier = Modifier,
    iconModifier: Modifier = Modifier
) {
    Chip(
        modifier = modifier,
        onClick = { /* ... */ },
        label = {
            Text(
                text = "5 minute Meditation",
                maxLines = 1,
                overflow = TextOverflow.Ellipsis
            )
        },
        icon = {
            Icon(
                imageVector = Icons.Rounded.SelfImprovement,
                contentDescription = "triggers meditation action",
                modifier = iconModifier
            )
        },
    )
}

Chip 可組合函式所用的參數中,有許多與您在其他可組合函式 (修飾符和 onClick) 內慣用的相同,因此我們無須查看這些參數。

其還使用標籤 (我們為其建立了一個 Text 可組合內容) 和圖示。

Icon 程式碼看起來應與您在其他可組合函式中看見的程式碼完全相同,但對於本程式碼,我們會從 androidx.compose.material:material-icons-extended 程式庫中提取 Self Improvement 圖示。

其外觀如下 (請記得向下捲動):

bd178a52438cfcbc.png

讓我們看看 Toggle 上的變化版本,亦即 ToggleChip 可組合內容。

建立 ToggleChip 可組合函式

ToggleChipChip 類似,但前者可讓使用者與圓形按鈕、切換按鈕或核取方塊互動。

ReusableComponents.kt 中搜尋「TODO: Create a ToggleChip」,並將目前的可組合方法替換為本程式碼。

步驟 6

// TODO: Create a ToggleChip Composable
@Composable
fun ToggleChipExample(modifier: Modifier = Modifier) {
    var checked by remember { mutableStateOf(true) }
    ToggleChip(
        modifier = modifier,
        checked = checked,
        toggleControl = {
            Switch(
                checked = checked,
                modifier = Modifier.semantics {
                    this.contentDescription = if (checked) "On" else "Off"
                }
            )
        },
        onCheckedChange = {
            checked = it
        },
        label = {
            Text(
                text = "Sound",
                maxLines = 1,
                overflow = TextOverflow.Ellipsis
            )
        }
    )
}

現在 ToggleChipExample() 可組合函式 (此程式碼的所在位置) 會產生採用切換按鈕 (而非核取方塊或圓形按鈕) 的 ToggleChip

首先,我們來建立 MutableState。由於主要目的是提供 UI 示範,讓您查看 Wear 提供哪些功能,因此我們未在其他函式中執行此操作。

在一般應用程式中,您可能會想傳入已勾選狀態和 lambda 來處理輕觸動作,因此可組合函式可能會是無狀態的 (詳情請參閱這裡)。

但在本例中,我們只想簡單呈現 ToggleChip 搭配可用切換按鈕的實際效果,因此即使未對狀態執行任何操作也無妨。

接下來,我們會設定修飾符、已勾選狀態和切換按鈕控制選項,以便產生所需的切換鈕。

接著我們會建立 lambda 來變更狀態,最後再使用 Text 可組合函式和一些基本參數來設定標籤。

其外觀如下:

76a0b8d96fd36438.png

您現在已看過許多 Wear OS 專用可組合函式。如前文所述,程式碼多半都與您先前編寫的程式碼幾乎相同。

讓我們來看看一些更進階的資訊。

7. 遷移至 ScalingLazyColumn

您可能曾在行動應用程式中使用 LazyColumn 來產生垂直捲動清單。

由於圓形裝置的頂端和底部較小,因此可顯示項目的空間較少。基於以上原因,Wear OS 具有專屬的 LazyColumn 版本,以針對這些圓形裝置提供更妥善的支援。

ScalingLazyColumn 延伸 LazyColumn 以在螢幕頂端和底部支援縮放和透明度,讓使用者更容易讀取內容。

示範如下:

198ee8e8fa799f08.gif

請注意,當項目靠近中心時會放大至完整大小,然後在向外移動時會隨之縮小 (且會變得更為透明)。

以下是更具體的應用程式範例:

a5a83ab2e5d5230f.gif

我們發現這能提升可讀性。

您已瞭解到 ScalingLazyColumn 的實際運作情形,接下來讓我們開始轉換 LazyColumn

我們會使用 Horologist ScalinglazyColumn,確保清單中的項目邊框間距正確,而且不會在不同尺寸的裝置螢幕上遭到裁剪。

轉換為 Horologist ScalingLazyColumnState

MainActivity.kt 中搜尋「TODO: Swap to ScalingLazyColumnState」,然後將該註解和下方的程式碼行替換為以下程式碼。請留意其中第一個和最後一個元件的指定方式,以便產生最佳邊框間距值來避免內容遭到裁剪。

步驟 7

// TODO: Swap to ScalingLazyColumnState
val listState = rememberResponsiveColumnState(
    contentPadding = ScalingLazyColumnDefaults.padding(
        first = ItemType.SingleButton,
        last = ItemType.Chip,
    ),
)

這些名稱幾乎相同。就像 LazyListState 處理 LazyColumn 的狀態一樣,ScalingLazyColumnState 也會處理 ScalingLazyColumn 的狀態。

轉換為 Horologist ScalingLazyColumn

接著我們要切換至 ScalingLazyColumn

MainActivity.kt 中搜尋「TODO: Swap a ScalingLazyColumn」。首先,請將 LazyColumn 替換為 Horologist ScalingLazyColumn

接下來,一併移除 contentPadding, verticalArrangement, modifierautocentering。Horologist ScalingLazyColumn 已提供預設設定,保證能顯示更佳的預設視覺效果,因為大多數可視區域都會填滿清單項目。在多數情況下,預設參數就足以應付;如果畫面頂端有標題,我們建議將其放進 ResponsiveListHeader 並列為第一個項目。

步驟 8

// TODO: Swap a ScalingLazyColumn (Wear's version of LazyColumn)
ScalingLazyColumn(
    columnState = listState

大功告成!其外觀如下:

442700f212089fd0.png

當您捲動時會於螢幕頂端與底部縮放內容和調整透明度,輕輕鬆鬆即可完成遷移!

當您上下移動冥想可組合內容時,就會確實留意到這點。

接著讓我們探討最後一個主題:Wear OS 的 Scaffold

8. 新增 Scaffold

Scaffold 提供版面配置結構,可協助您在一般模式下編排畫面,就像在行動裝置上顯示一樣。不過,有別於應用程式列、懸浮動作按鈕 (FAB)、導覽匣或其他行動裝置專用元素,Scaffold 支援四種包含以下頂層元件的 Wear 專用版面配置:時間、捲動/位置指標,以及頁面指標。

這些元件會顯示如下:

TimeText

PositionIndicator

PageIndicator

我們會詳細探討前三個元件,但先讓我們把 Scaffold 設定好。

我們將使用 Horologist AppScaffoldScreenScaffold,在畫面中預設加入 TimeText

並確保在瀏覽不同畫面時能正確播放動畫。

此外,ScreenScaffold 會為可捲動的內容新增 PositionIndicator

新增 Scaffold

現在讓我們來新增 AppScaffoldScreenScaffold 的樣板。

找出「TODO (Start): Create a AppScaffold (Wear Version)」並在下方新增以下程式碼。

步驟 9

WearAppTheme {
// TODO (Start): Create a Horologist AppScaffold (Wear Version)
AppScaffold {

找出 "TODO (Start): Create a ScreenScaffold (Wear Version)" 並在下方新增以下程式碼。

// TODO (Start): Create a Horologist ScreenScaffold (Wear Version)
ScreenScaffold(
    scrollState = listState,
){

接著,請務必在正確位置加上右括號。

找出 "TODO (End): Create a ScreenScaffold (Wear Version)" 並在該處加上右括號:

步驟 10

// TODO (End): Create a ScreenScaffold (Wear Version)
}

找出 "TODO (End): Create a AppScaffold (Wear Version)" 並在該處加上右括號:

步驟 10

// TODO (End): Create a AppScaffold (Wear Version)
}

請先執行。如下所示:

c2cb637a495bc977.png

請注意,這段程式碼新增了下列元素:

  • TimeText 會在背景中使用弧形文字,讓開發人員能夠輕鬆顯示時間,不僅不用放置可組合函式,也不用處理任何與時間相關的類別。此外,根據《Material 指南》建議,您應在應用程式所有畫面的頂端顯示時間,並在捲動時淡出。
  • PositionIndicator (亦稱「捲動指標」) 是畫面右側的指標,可根據傳入的狀態物件類型,顯示目前的指標位置。在本範例中即是 ScalingLazyColumnState

現在外觀看起來會像這樣:

1b6fa8091b920587.png

請試著上下捲動。僅在捲動時才會看見捲動指標。

太棒了,您已完成大部分 Wear OS 可組合函式的 UI 演示程序!

9. 恭喜

恭喜!您已瞭解關於在 Wear OS 使用 Compose 的基本知識!

您現在可以重新應用所有 Compose 知識,打造精美的 Wear OS 應用程式!

後續步驟

查看其他 Wear OS 程式碼研究室:

其他資訊

意見回饋:

歡迎分享您使用 Compose for Wear OS 的經驗談,告訴我們您學會建構哪些內容!也期待您參與 Kotlin Slack #compose-wear 頻道的討論,並持續透過 Issue Tracker 提供意見回饋。

祝您編寫程式一切順利!