在 Wear OS 中建立第一個資訊方塊

1. 簡介

三個資訊方塊:健身、訊息和日曆。

Wear OS 資訊方塊可讓使用者輕鬆存取必要資訊和動作,流暢地處理大小事務。只要滑動錶面,就能查詢最新天氣預報或啟動計時器。

資訊方塊是系統 UI 的一部分,並不是在專屬應用程式容器中執行。我們會使用服務來描述資訊方塊的版面配置和內容,系統 UI 則會視需要顯示資訊方塊。

執行步驟

656045bed9c45083.png

您會建構訊息應用程式的資訊方塊,用於顯示最近的對話。使用者可以從這個畫面直接跳到下列三項常見操作:

  • 開啟對話
  • 撰寫新訊息

課程內容

在這個程式碼研究室中,您將瞭解如何編寫自己的 Wear OS 資訊方塊,包括如何:

  • 建立 TileService
  • 在裝置上測試資訊方塊
  • 在 Android Studio 中預覽資訊方塊 UI
  • 開發資訊方塊 UI
  • 新增圖片
  • 處理互動

必要條件

2. 開始設定

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

軟硬體需求

如果您不熟悉 Wear OS 的使用方式,建議先參考這篇簡要說明,再開始進行。文章內容包括如何設定及操作 Wear OS 模擬器。

下載程式碼

如果您已安裝 Git,只要執行下列指令即可複製這個存放區的程式碼。

git clone https://github.com/android/codelab-wear-tiles.git
cd codelab-wear-tiles

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

在 Android Studio 中開啟專案

在「Welcome to Android Studio」視窗中,選取 c01826594f360d94.png「Open an Existing Project」或依序點選「File」>「Open」,然後選取「Download Location」資料夾。

3. 建立基本資訊方塊

資訊方塊的進入點是資訊方塊服務。在這個步驟中,您必須註冊資訊方塊服務,並定義方塊的版面配置。

HelloWorldTileService

實作 TileService 的類別需要指定兩個方法:

  • onTileResourcesRequest(requestParams: ResourcesRequest): ListenableFuture<Resources>
  • onTileRequest(requestParams: TileRequest): ListenableFuture<Tile>

第一個方法會傳回 Resources 物件,在當中將字串 ID 對應至要在資訊方塊中使用的圖片資源。

第二個則會傳回資訊方塊的說明,包括方塊版面配置。請在這裡定義資訊方塊的版面配置,以及資料的繫結方式。

start 模組開啟 HelloWorldTileService.kt。您所做的任何變更都會收錄在本模組中。如果您想查看這個程式碼研究室的結果,也可以使用 finished 模組。

HelloWorldTileService 可擴充 SuspendingTileService,後者來自 Horologist Tiles 程式庫,是適合用於 Kotlin 協同程式的包裝函式。Horologist 是 Google 提供的一組程式庫,旨在補充 Wear OS 開發人員常用,但 Jetpack 目前尚未提供的功能。

SuspendingTileService 提供兩個暫停函式,是 TileService 中函式的協同程式對等項目:

  • suspend resourcesRequest(requestParams: ResourcesRequest): Resources
  • suspend tileRequest(requestParams: TileRequest): Tile

如要進一步瞭解協同程式,請參閱 Android 上的 Kotlin 協同程式說明文件。

HelloWorldTileService 尚未完成。我們需要在資訊清單中註冊服務,且需要為 tileLayout 提供實作方式。

註冊資訊方塊服務

資訊方塊服務在資訊清單中完成註冊後,就會顯示在可供使用者新增的資訊方塊清單中。

<application> 元素中加入 <service>

start/src/main/AndroidManifest.xml

<service
    android:name="com.example.wear.tiles.hello.HelloWorldTileService"
    android:icon="@drawable/ic_waving_hand_24"
    android:label="@string/hello_tile_label"
    android:description="@string/hello_tile_description"
    android:exported="true"
    android:permission="com.google.android.wearable.permission.BIND_TILE_PROVIDER">
    
    <intent-filter>
        <action android:name="androidx.wear.tiles.action.BIND_TILE_PROVIDER" />
    </intent-filter>

    <!-- The tile preview shown when configuring tiles on your phone -->
    <meta-data
        android:name="androidx.wear.tiles.PREVIEW"
        android:resource="@drawable/tile_hello" />
</service>

使用者首次載入資訊方塊或發生載入錯誤時,系統會把圖示和標籤放在預留位置。結尾的中繼資料會定義使用者新增資訊方塊時,在輪轉介面中顯示的預覽圖片。

定義資訊方塊的版面配置

HelloWorldTileService 具有名為 tileLayout 的函式,主體為 TODO()。現在,我們要實際替換程式碼,定義資訊方塊的版面配置:

start/src/main/java/com/example/wear/tiles/hello/HelloWorldTileService.kt

fun tileLayout(
    context: Context,
    deviceConfiguration: DeviceParametersBuilders.DeviceParameters,
    message: String,
) =
    materialScope(
        context = context,
        deviceConfiguration = deviceConfiguration,
        allowDynamicTheme = false,
    ) {
        primaryLayout(mainSlot = { text(message.layoutString) })
    }

您已成功建立第一個 Wear OS 資訊方塊!立即安裝,看看實際呈現的樣子。

4. 在裝置上測試資訊方塊

只要在執行設定下拉式選單中選取 start 模組,即可在裝置或模擬器上安裝應用程式 (start 模組),然後像使用者一樣手動安裝資訊方塊。

不過,Android Studio 有捷徑可用來執行這項操作:輕觸空白邊中的「run service」圖示 (▷),然後選取「Run ‘HelloWorldTileService'」,即可在已連結的裝置上安裝及啟動資訊方塊。

ded9f9355abd02f3.png

選取「Run ‘HelloWorldTileService'」,即可在已連結的裝置上建構及執行資訊方塊。您看到的畫面應如下方螢幕截圖所示。

693c130912097be6.png

畫面頂端顯示的「揮手」圖示是由系統提供。如要變更,請在資訊清單中修改資訊方塊 <service> 元素的 android:icon 屬性。

為方便起見,這項程序也會建立「HelloWorldTileService」的「run configuration」,供日後使用。

b3335148771abbeb.png

5. 新增預覽函式

我們可以在 Android Studio 中預覽資訊方塊 UI,這可縮短開發 UI 時的回饋循環,提升開發速度。

HelloWorldTileService.kt 檔案結尾為 HelloWorldTileService 新增資訊方塊預覽。

start/src/main/java/com/example/wear/tiles/hello/HelloWorldTileService.kt

@Preview(device = WearDevices.SMALL_ROUND, name = "Small Round")
@Preview(device = WearDevices.LARGE_ROUND, name = "Large Round")
internal fun helloLayoutPreview(context: Context): TilePreviewData {
    return TilePreviewData {
        TilePreviewHelper.singleTimelineEntryTileBuilder(
            helloLayout(context, it.deviceConfiguration, "Hello, preview tile!")
        )
            .build()
    }
}

您可以使用「分割」編輯器模式預覽資訊方塊:

Android Studio 的分割畫面檢視畫面,左側是預覽程式碼,右側是資訊方塊的圖片。

請注意,這裡「沒有」使用 @Composable 註解;雖然 Tiles 使用與可組合函式相同的預覽 UI,但 Tiles 不會使用 Compose,也不具可組合性質。

6. 建構訊息資訊方塊

cf18db0f604b1999.png

接下來要建構的訊息資訊方塊比較貼近日常使用情境。這個例子與 HelloWorld 不同,會展示 Material 3 Expressive 元件、顯示圖片,並處理互動以開啟應用程式。

MessagingTileService

MessagingTileService 擴充了我們先前看到的 SuspendingTileService 類別。

7. 新增 UI 元件

ProtoLayout 程式庫提供預先建立的元件和版面配置,可讓您建立採用 Wear OS 最新 Material 3 Expressive 設計的資訊方塊。

將 Tiles Material 依附元件新增至 build.gradle 檔案:

start/build.gradle

implementation "androidx.wear.protolayout:protolayout-material3:$protoLayoutVersion"

將版面配置程式碼新增至 tileLayout() 函式,做為 materialScope() 函式的主體。這會建立由兩個資料列 (每列兩個按鈕) 和一個邊緣按鈕組成的版面配置。

找出「TODO() // Add primaryLayout()」這一行,並替換為以下程式碼。

start/src/main/java/com/example/wear/tiles/messaging/tile/Layout.kt

primaryLayout(
    mainSlot = {
        // This layout code assumes "contacts" contains at least 4 elements, for sample code
        // that can handle an arbitrary number of contacts, and also shows different numbers
        // of contacts based on the physical screen size, see
        // <https://github.com/android/wear-os-samples/tree/main/WearTilesKotlin>.
        Column.Builder()
            .apply {
                setWidth(expand())
                setHeight(expand())
                addContent(
                    buttonGroup {
                        buttonGroupItem { contactButton(contacts[0]) }
                        buttonGroupItem { contactButton(contacts[1]) }
                    }
                )
                addContent(DEFAULT_SPACER_BETWEEN_BUTTON_GROUPS)
                addContent(
                    buttonGroup {
                        buttonGroupItem { contactButton(contacts[2]) }
                        buttonGroupItem { contactButton(contacts[3]) }
                    }
                )
            }
            .build()
    },
    bottomSlot = {
        textEdgeButton(
            onClick = clickable(), // TODO: Launch new conversation activity
            labelContent = { text("New".layoutString) },
        )
    },
)

同一個檔案中的 contactButton() 函式會建立個別聯絡人按鈕。如果聯絡人有關聯圖片,就會顯示在按鈕上;否則會使用聯絡人名稱的縮寫。

此時您可能會發現,雖然整體版面配置正確,但缺少圖片:

809bdb9d1213c376.png

如果將資訊方塊部署至裝置,也會看到相同的畫面:

4671bb2eafdcc528.png

在下一步中,我們會修正缺少圖片的問題。

8. 新增圖片

大致來說,Tiles 包含兩項要素:版面配置 (依字串 ID 參照資源),以及資源本身 (可以是圖片)。

目前程式碼已提供版面配置,但沒有提供資源本身。如要修正預覽畫面,就需要提供圖片「資源」。如要這麼做,請找出「TODO: Add onTileResourceRequest」,然後將以下程式碼新增為 TilePreviewData() 的額外具名引數:

start/src/main/java/com/example/wear/tiles/messaging/tile/Layout.kt

// Additional named argument to TilePreviewData
onTileResourceRequest = { resourcesRequest ->
    Resources.Builder()
        .setVersion(resourcesRequest.version)
        .apply {
            contacts.forEach {
                if (it.avatarSource is AvatarSource.Resource) {
                    addIdToImageMapping(
                        it.imageResourceId(),
                        it.avatarSource.resourceId
                    )
                }
            }
        }
        .build()
}

現在預覽畫面應會顯示圖片:

e77d746268f293f2.png

不過,如果資訊方塊已部署至裝置,就會遺失圖片。如要修正這個問題,請將 Service.kt 中的 resourcesRequest() 函式替換為以下內容:

start/src/main/java/com/example/wear/tiles/messaging/tile/Service.kt

override suspend fun resourcesRequest(
    requestParams: ResourcesRequest
): Resources {
    // resourceIds is a list of the ids we need to provide images for. If we're passed an empty
    // list, set resourceIds to all resources.
    val resourceIds =
        requestParams.resourceIds.ifEmpty {
            contacts.map { it.imageResourceId() }
        }

    // resourceMap maps (tile) resource ids to (Android) resource ids.
    val resourceMap =
        contacts
            .mapNotNull {
                when (it.avatarSource) {
                    is AvatarSource.Resource ->
                        it.imageResourceId() to
                            it.avatarSource.resourceId
                    else -> null
                }
            }
            .toMap()
            .filterKeys {
                it in resourceIds
            } // filter to only the resources we need

    // Add images in the resourceMap to the Resources object, and return the result.
    return Resources.Builder()
        .setVersion(requestParams.version)
        .apply {
            resourceMap.forEach { (id, imageResource) ->
                addIdToImageMapping(id, imageResource)
            }
        }
        .build()
}

資訊方塊部署至裝置後,也會顯示圖片了:

cf18db0f604b1999.png

在下一步中,我們會處理各個元素的點擊動作。

9. 處理互動

資訊方塊最實用的功能之一,就是提供關鍵使用者旅程的捷徑。這與僅開啟應用程式的應用程式啟動器不同:此處有空間為應用程式的特定畫面提供內容捷徑。

到目前為止,我們一直在使用無引數 clickable() 為方塊和每個按鈕提供的虛擬動作。這對於沒有互動的預覽而言沒有問題,但接下來將說明如何為元素新增動作。

LaunchAction

LaunchAction 可用來啟動活動。我們可以修改 Layout,讓使用者輕觸「New」按鈕時,能啟動「new conversation」使用者歷程。

找出「TODO: Launch new conversation activity」這一行,並將 clickable() 替換為:

start/src/main/java/com/example/wear/tiles/messaging/tile/Layout.kt

clickable(
    id = "new_button",
    action =
        launchAction(
            ComponentName(
                "com.example.wear.tiles",
                "com.example.wear.tiles.messaging.MainActivity",
            ),
            mapOf(
                MainActivity.EXTRA_JOURNEY to
                    ActionBuilders.stringExtra(
                        MainActivity.EXTRA_JOURNEY_NEW
                    )
            ),
        ),
)

重新部署資訊方塊。現在,輕觸「New」就會啟動 MainActivity 並開始「new conversation」使用者歷程,而不會沒有反應:

a08c28b4a142fb8f.png

同樣地,只要修改 Layout,就能在輕觸聯絡人按鈕時,與特定使用者開始對話。

找出「Launch open conversation activity」這一行,並將 clickable() 替換為:

start/src/main/java/com/example/wear/tiles/messaging/tile/Layout.kt

clickable(
    id = contact.id.toString(),
    action =
        launchAction(
            ComponentName(
                "com.example.wear.tiles",
                "com.example.wear.tiles.messaging.MainActivity",
            ),
            mapOf(
                MainActivity.EXTRA_JOURNEY to
                    ActionBuilders.stringExtra(
                        MainActivity
                            .EXTRA_JOURNEY_CONVERSATION
                    ),
                MainActivity.EXTRA_CONVERSATION_CONTACT to
                    ActionBuilders.stringExtra(
                        contact.name
                    ),
            ),
        ),
)

重新部署資訊方塊。現在,輕觸聯絡人後,就能與對方開始對話,而不會沒有反應:

b684a1ced0b226f9.png

10. 恭喜

恭喜!您已瞭解如何建構適用於 Wear OS 的資訊方塊!

後續步驟

詳情請參閱 GitHub 上的 Golden Tiles 實作方式Wear OS 資訊方塊指南設計指南