Membuat Kartu pertama di Wear OS

1. Pengantar

jam animasi, pengguna menggeser tampilan jam ke kartu pertama yang merupakan perkiraan cuaca, lalu ke kartu timer, dan kembali

Kartu Wear OS menyediakan akses mudah ke informasi dan tindakan yang dibutuhkan pengguna untuk menyelesaikan berbagai hal. Hanya dengan menggeser dari tampilan jam, pengguna dapat mengetahui perkiraan cuaca terbaru atau memulai timer.

Kartu berjalan sebagai bagian dari UI sistem, bukan berjalan di container aplikasinya sendiri. Kita menggunakan Service untuk mendeskripsikan tata letak dan konten kartu. UI sistem kemudian akan merender kartu saat diperlukan.

Yang akan Anda lakukan

35a459b77a2c9d52.png

Anda akan membuat kartu untuk aplikasi pesan, yang menampilkan percakapan terbaru. Dari platform ini, pengguna dapat langsung melakukan tiga tugas umum:

  • Membuka percakapan
  • Menelusuri percakapan
  • Menulis pesan baru

Yang akan Anda pelajari

Dalam codelab ini, Anda akan mempelajari cara menulis Kartu Wear OS Anda sendiri, termasuk cara:

  • Membuat TileService
  • Menguji kartu di perangkat
  • Melakukan pratinjau UI untuk kartu di Android Studio
  • Mengembangkan UI untuk kartu
  • Menambahkan gambar
  • Menangani interaksi

Prasyarat

  • Pemahaman dasar tentang Kotlin

2. Mempersiapkan

Pada langkah ini, Anda akan menyiapkan lingkungan dan mendownload project awal.

Yang akan Anda butuhkan

Jika Anda tidak terbiasa menggunakan Wear OS, membaca panduan cepat ini sebelum memulai akan membantu. Panduan ini berisi petunjuk untuk menyiapkan emulator Wear OS dan menjelaskan cara bernavigasi di dalam sistem.

Mendownload kode

Jika sudah menginstal git, Anda dapat menjalankan perintah di bawah ini untuk meng-clone kode dari repositori ini.

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

Jika tidak memiliki git, Anda dapat mengklik tombol berikut untuk mendownload semua kode untuk codelab ini:

Membuka project di Android Studio

Pada jendela "Welcome to Android Studio", pilih c01826594f360d94.png Open an Existing Project atau File > Open, lalu pilih folder[Download Location].

3. Membuat kartu dasar

Titik entri untuk kartu adalah layanan kartu. Pada langkah ini, Anda akan mendaftarkan layanan kartu dan menentukan tata letak kartu.

HelloWorldTileService

Class yang menerapkan TileService harus menentukan dua metode:

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

Metode pertama menampilkan objek Resources yang memetakan ID string ke resource gambar yang akan kita gunakan dalam kartu.

Metode kedua menampilkan deskripsi kartu, termasuk tata letaknya. Di sini,kita menentukan tata letak kartu dan cara data terikat dengannya.

Buka HelloWorldTileService.kt dari modul start. Semua perubahan yang akan Anda buat ada dalam modul ini. Ada juga modul finished jika Anda ingin melihat hasil codelab ini.

HelloWorldTileService memperluas SuspendingTileService, yaitu wrapper yang cocok untuk coroutine Kotlin dari library Kartu Horologist. Horologist adalah sekumpulan library dari Google yang bertujuan melengkapi developer Wear OS dengan fitur yang umumnya diperlukan oleh developer, tetapi belum tersedia di Jetpack.

SuspendingTileService menyediakan dua fungsi penangguhan yang merupakan coroutine yang setara dengan fungsi dari TileService:

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

Untuk mempelajari lebih lanjut coroutine, lihat dokumentasi untuk coroutine Kotlin di Android.

HelloWorldTileService belum selesai. Kita harus mendaftarkan layanan dalam manifes dan juga perlu menyediakan implementasi untuk tileLayout.

Mendaftarkan layanan kartu

Setelah layanan kartu didaftarkan dalam manifes, layanan akan muncul dalam daftar kartu yang tersedia untuk ditambahkan oleh pengguna.

Tambahkan <service> di dalam elemen <application>:

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>

Ikon dan label digunakan (sebagai placeholder) saat kartu dimuat untuk pertama kalinya, atau jika terjadi error saat memuat kartu. Meta-data di bagian akhir menentukan gambar pratinjau yang ditampilkan dalam carousel saat pengguna menambahkan kartu.

Menentukan tata letak kartu

HelloWorldTileService memiliki fungsi bernama tileLayout dengan TODO() sebagai isi. Sekarang, mari ganti fungsi tersebut dengan implementasi yang dapat kita gunakan untuk menentukan tata letak kartu, dan mengikat data:

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

private fun tileLayout(): LayoutElement {
    val text = getString(R.string.hello_tile_body)
    return LayoutElementBuilders.Box.Builder()
        .setVerticalAlignment(LayoutElementBuilders.VERTICAL_ALIGN_CENTER)
        .setWidth(DimensionBuilders.expand())
        .setHeight(DimensionBuilders.expand())
        .addContent(
            LayoutElementBuilders.Text.Builder()
                .setText(text)
                .build()
        )
        .build()
}

Kita membuat elemen Text dan menyetelnya di dalam Box sehingga kita dapat melakukan beberapa penyelarasan dasar.

Dan itu adalah Kartu Wear OS pertama Anda yang dibuat. Mari kita instal kartu ini dan lihat bagaimana tampilannya.

4. Menguji kartu di perangkat

Dengan modul awal yang dipilih di dropdown konfigurasi run, Anda dapat menginstal aplikasi (modul start) di perangkat atau emulator dan menginstal kartu secara manual, seperti yang dilakukan pengguna.

Namun untuk pengembangan, mari kita gunakan Direct Surface Launch, fitur yang diperkenalkan di Android Studio Dolphin, untuk membuat konfigurasi run baru guna meluncurkan kartu langsung dari Android Studio. Pilih "Edit Configurations..." dari menu dropdown di panel atas.

Jalankan dropdown konfigurasi dari panel atas di Android Studio. Edit konfigurasi ditandai.

Klik tombol "Add new configuration" dan pilih "Wear OS Tile". Tambahkan nama deskriptif, lalu pilih modul Tiles_Code_Lab.start dan kartu HelloWorldTileService.

Tekan "OK" untuk menyelesaikan.

Menu Edit Configuration dengan Kartu Wear OS yang disebut HelloTile sedang dikonfigurasi.

Direct Surface Launch memungkinkan kami menguji kartu dengan cepat di emulator Wear OS atau perangkat fisik. Cobalah dengan menjalankan "HelloTile". Tampilannya akan terlihat seperti screenshot di bawah.

Smartwatch bulat yang menampilkan tulisan putih 'Saatnya membuat kartu!' dengan latar belakang hitam

5. Membuat kartu pesan

Smartwatch bulat yang menampilkan 5 tombol bulat dalam susunan piramida 2x3. Tombol pertama dan ketiga menampilkan inisial dalam teks ungu, tombol kedua dan keempat menampilkan foto profil, dan tombol terakhir adalah ikon penelusuran. Di bawah tombol terdapat chip ringkas berwarna ungu yang bertuliskan 'New' dalam teks hitam.

Kartu pesan yang akan kita buat lebih mirip dengan kartu di dunia nyata. Tidak seperti contoh HelloWorld, contoh ini memuat data dari repositori lokal, mengambil gambar untuk ditampilkan dari jaringan dan menangani interaksi untuk membuka aplikasi, langsung dari kartu.

MessagingTileService

MessagingTileService memperluas class SuspendingTileService yang telah kita lihat sebelumnya.

Perbedaan utama antara hal ini dan contoh sebelumnya adalah saat ini kita mengamati data dari repositori, dan juga mengambil data gambar dari jaringan.

MessagingTileRenderer

MessagingTileRenderer memperluas class SingleTileLayoutRenderer (abstraksi lain dari Kartu Horologist). Class ini sepenuhnya sinkron: status diteruskan ke fungsi perender, yang mempermudah penggunaan dalam pengujian dan pratinjau Android Studio.

Pada langkah berikutnya, kita akan melihat cara menambahkan pratinjau Android Studio untuk kartu.

6. Menambahkan fungsi pratinjau

Kita dapat melihat pratinjau UI kartu di Android Studio menggunakan fungsi pratinjau Kartu, yang dirilis di library Jetpack Tiles versi 1.4 ( saat ini dalam versi alfa). Hal ini mempersingkat feedback loop saat mengembangkan UI, sehingga meningkatkan kecepatan pengembangan.

Tambahkan pratinjau kartu untuk MessagingTileRenderer di akhir file.

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

@Preview(device = WearDevices.SMALL_ROUND)
@Preview(device = WearDevices.LARGE_ROUND)
fun messagingTileLayoutPreview(context: Context): TilePreviewData {
    return TilePreviewData { request ->
        MessagingTileRenderer(context).renderTimeline(
            MessagingTileState(knownContacts),
            request
        )
    }
}

Perlu diperhatikan bahwa anotasi @Composable tidak disediakan—meskipun Kartu menggunakan UI pratinjau yang sama seperti Fungsi composable, Kartu tidak menggunakan Compose, dan bukan merupakan composable.

Gunakan mode editor "Split" (Terpisah) untuk melihat pratinjau kartu:

tampilan layar terpisah Android Studio dengan kode pratinjau di sebelah kiri dan gambar kartu di sebelah kanan.

Pada langkah berikutnya, kita akan menggunakan Tiles Material untuk mengupdate tata letak.

7. Menambahkan Tiles Material

Tiles Material menyediakan tata letak dan komponen Material dan tata letak bawaan, sehingga Anda dapat membuat kartu yang menggunakan Desain Material terbaru untuk Wear OS.

Tambahkan dependensi Tiles Material ke file build.gradle Anda:

start/build.gradle

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

Tambahkan kode untuk tombol di bagian bawah file perender, serta pratinjaunya:

start/src/main/java/MessagingTileRenderer.kt

private fun searchLayout(
    context: Context,
    clickable: ModifiersBuilders.Clickable,
) = Button.Builder(context, clickable)
    .setContentDescription(context.getString(R.string.tile_messaging_search))
    .setIconContent(MessagingTileRenderer.ID_IC_SEARCH)
    .setButtonColors(ButtonColors.secondaryButtonColors(MessagingTileTheme.colors))
    .build()

Kita juga dapat melakukan sesuatu yang serupa untuk membuat tata letak kontak:

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

private fun contactLayout(
    context: Context,
    contact: Contact,
    clickable: ModifiersBuilders.Clickable,
) = Button.Builder(context, clickable)
    .setContentDescription(contact.name)
    .apply {
        if (contact.avatarUrl != null) {
            setImageContent(contact.imageResourceId())
        } else {
            setTextContent(contact.initials)
            setButtonColors(ButtonColors.secondaryButtonColors(MessagingTileTheme.colors))
        }
    }
    .build()

Tiles Material tidak hanya menyertakan komponen. Daripada menggunakan serangkaian kolom dan baris bertingkat, kita dapat menggunakan tata letak dari Tiles Material untuk mendapatkan tampilan yang diinginkan dengan cepat.

Di sini, kita dapat menggunakan PrimaryLayout dan MultiButtonLayout untuk mengatur 4 kontak dan tombol penelusuran. Update fungsi messagingTileLayout() di MessagingTileRenderer dengan tata letak ini:

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

private fun messagingTileLayout(
    context: Context,
    deviceParameters: DeviceParametersBuilders.DeviceParameters,
    state: MessagingTileState
) = PrimaryLayout.Builder(deviceParameters)
    .setResponsiveContentInsetEnabled(true)
    .setContent(
        MultiButtonLayout.Builder()
            .apply {
                // In a PrimaryLayout with a compact chip at the bottom, we can fit 5 buttons.
                // We're only taking the first 4 contacts so that we can fit a Search button too.
                state.contacts.take(4).forEach { contact ->
                    addButtonContent(
                        contactLayout(
                            context = context,
                            contact = contact,
                            clickable = emptyClickable
                        )
                    )
                }
            }
            .addButtonContent(searchLayout(context, emptyClickable))
            .build()
    )
    .build()

96fee80361af2c0f.png

MultiButtonLayout mendukung hingga 7 tombol, dan akan menata letaknya dengan jarak yang sesuai untuk Anda.

Mari tambahkan CompactChip "Baru" sebagai chip "utama" PrimaryLayout di fungsi messagingTileLayout():

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

.setPrimaryChipContent(
        CompactChip.Builder(
            /* context = */ context,
            /* text = */ context.getString(R.string.tile_messaging_create_new),
            /* clickable = */ emptyClickable,
            /* deviceParameters = */ deviceParameters
        )
            .setChipColors(ChipColors.primaryChipColors(MessagingTileTheme.colors))
            .build()
    )

2041bdca8a46458b.png

Pada langkah berikutnya, kita akan memperbaiki gambar yang hilang.

8. Menambahkan gambar

Pada level tinggi, Kartu terdiri dari dua hal: elemen tata letak (yang mereferensikan resource berdasarkan ID string) dan resource itu sendiri (yang dapat berupa gambar).

Menyediakan gambar lokal adalah tugas yang mudah: meskipun Anda tidak dapat menggunakan resource drawable Android secara langsung, Anda dapat mengonversinya dengan mudah ke format yang diperlukan menggunakan fungsi praktis yang disediakan oleh Horologist. Selanjutnya, gunakan fungsi addIdToImageMapping untuk mengaitkan gambar dengan ID resource. Contoh:

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

addIdToImageMapping(
    ID_IC_SEARCH,
    drawableResToImageResource(R.drawable.ic_search_24)
)

Untuk gambar jarak jauh, gunakan Coil, loader gambar berbasis coroutine Kotlin, untuk memuatnya melalui jaringan.

Kode untuk tindakan ini sudah ditulis:

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

override suspend fun resourcesRequest(requestParams: ResourcesRequest): Resources {
    val avatars = imageLoader.fetchAvatarsFromNetwork(
        context = this@MessagingTileService,
        requestParams = requestParams,
        tileState = latestTileState()
    )
    return renderer.produceRequestedResources(avatars, requestParams)
}

Karena perender kartu sepenuhnya sinkron, layanan kartu akan mengambil bitmap dari jaringan. Seperti sebelumnya, bergantung pada ukuran gambar, akan lebih tepat menggunakan WorkManager untuk mengambil gambar sebelumnya, tetapi untuk codelab ini, kita akan mengambilnya secara langsung.

Kita meneruskan peta avatars (Contact ke Bitmap) ke perender sebagai "status" untuk resource. Kini perender dapat mengubah bitmap ini ke resource gambar untuk kartu.

Kode ini juga sudah ditulis:

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

override fun ResourceBuilders.Resources.Builder.produceRequestedResources(
    resourceState: Map<Contact, Bitmap>,
    deviceParameters: DeviceParametersBuilders.DeviceParameters,
    resourceIds: List<String>
) {
    addIdToImageMapping(
        ID_IC_SEARCH,
        drawableResToImageResource(R.drawable.ic_search_24)
    )

    resourceState.forEach { (contact, bitmap) ->
        addIdToImageMapping(
            /* id = */ contact.imageResourceId(),
            /* image = */ bitmap.toImageResource()
        )
    }
}

Jadi, jika layanan mengambil bitmap, dan perender mengubah bitmap tersebut ke resource gambar, mengapa kartu tidak menampilkan gambar?

Tentu saja ada. Jika menjalankan kartu pada perangkat (dengan akses internet), Anda akan melihat gambar benar-benar dimuat. Masalah ini hanya berada dalam pratinjau, karena kita belum meneruskan resource apa pun ke TilePreviewData().

Untuk kartu yang sebenarnya, kita mengambil bitmap dari jaringan dan memetakannya ke kontak yang berbeda, tetapi untuk pratinjau dan pengujian, kita tidak perlu mencapai jaringan sama sekali.

Kita perlu membuat dua perubahan. Pertama, buat fungsi previewResources(), yang menampilkan objek Resources:

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

private fun previewResources() = Resources.Builder()
    .addIdToImageMapping(ID_IC_SEARCH, drawableResToImageResource(R.drawable.ic_search_24))
    .addIdToImageMapping(knownContacts[1].imageResourceId(), drawableResToImageResource(R.drawable.ali))
    .addIdToImageMapping(knownContacts[2].imageResourceId(), drawableResToImageResource(R.drawable.taylor))
    .build()

Kedua, update messagingTileLayoutPreview() untuk meneruskan resource:

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

@Preview(device = WearDevices.SMALL_ROUND)
@Preview(device = WearDevices.LARGE_ROUND)
fun messagingTileLayoutPreview(context: Context): TilePreviewData {
    return TilePreviewData({ previewResources() }) { request ->
        MessagingTileRenderer(context).renderTimeline(
            MessagingTileState(knownContacts),
            request
        )
    }
}

Sekarang, jika kita memuat ulang pratinjau, gambar akan ditampilkan:

3142b42717407059.png

Pada langkah berikutnya, kita akan menangani klik pada setiap elemen.

9. Menangani interaksi

Salah satu hal paling berguna yang dapat kita lakukan dengan kartu adalah menyediakan pintasan ke perjalanan penting pengguna. Ini berbeda dengan peluncur aplikasi yang hanya membuka aplikasi - di sini, kita memiliki ruang untuk menyediakan pintasan kontekstual ke layar tertentu dalam aplikasi Anda.

Sejauh ini, kita telah menggunakan emptyClickable untuk chip dan setiap tombol. Tindakan ini tidak masalah untuk pratinjau yang tidak interaktif, tetapi mari kita lihat cara menambahkan tindakan untuk elemen tersebut.

Dua builder dari class 'ActionBuilders' menentukan tindakan yang Dapat Diklik: LoadAction dan LaunchAction.

LoadAction

LoadAction dapat digunakan jika Anda ingin menjalankan logika di layanan kartu saat pengguna mengklik elemen, misalnya menambahkan penghitung.

.setClickable(
    Clickable.Builder()
        .setId(ID_CLICK_INCREMENT_COUNTER)
        .setOnClick(ActionBuilders.LoadAction.Builder().build())
        .build()
    )
)

Saat diklik, onTileRequest akan dipanggil dalam layanan Anda (tileRequest di SuspendingTileService) sehingga sebaiknya Anda memuat ulang UI kartu:

override suspend fun tileRequest(requestParams: TileRequest): Tile {
    if (requestParams.state.lastClickableId == ID_CLICK_INCREMENT_COUNTER) {
        // increment counter
    }
    // return an updated tile
}

LaunchAction

LaunchAction dapat digunakan untuk meluncurkan aktivitas. Di MessagingTileRenderer, mari kita perbarui tombol yang dapat diklik untuk tombol penelusuran.

Tombol penelusuran ditentukan oleh fungsi searchLayout() di MessagingTileRenderer. Fungsi ini sudah menggunakan Clickable sebagai parameter, tetapi sejauh ini, kita telah meneruskan emptyClickable, implementasi tanpa pengoperasian yang tidak akan melakukan apa pun saat tombol diklik.

Mari kita update messagingTileLayout() agar meneruskan tindakan klik nyata.

  1. Tambahkan parameter baru, searchButtonClickable (dari jenis ModifiersBuilders.Clickable).
  2. Teruskan parameter ini ke fungsi searchLayout() yang ada.

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

private fun messagingTileLayout(
    context: Context,
    deviceParameters: DeviceParametersBuilders.DeviceParameters,
    state: MessagingTileState,
    searchButtonClickable: ModifiersBuilders.Clickable
...
    .addButtonContent(searchLayout(context, searchButtonClickable))

Kita juga perlu memperbarui renderTile yang merupakan tempat kita memanggil messagingTileLayout karena kita baru saja menambahkan parameter baru (searchButtonClickable). Kita akan menggunakan fungsi launchActivityClickable() untuk membuat fungsi baru yang dapat diklik dengan meneruskan openSearch() ActionBuilder sebagai tindakan:

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

override fun renderTile(
    state: MessagingTileState,
    deviceParameters: DeviceParametersBuilders.DeviceParameters
): LayoutElementBuilders.LayoutElement {
    return messagingTileLayout(
        context = context,
        deviceParameters = deviceParameters,
        state = state,
        searchButtonClickable = launchActivityClickable("search_button", openSearch())
    )
}

Buka launchActivityClickable untuk melihat cara kerja fungsi ini (sudah ditentukan):

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

internal fun launchActivityClickable(
    clickableId: String,
    androidActivity: ActionBuilders.AndroidActivity
) = ModifiersBuilders.Clickable.Builder()
    .setId(clickableId)
    .setOnClick(
        ActionBuilders.LaunchAction.Builder()
            .setAndroidActivity(androidActivity)
            .build()
    )
    .build()

Hal ini sangat mirip dengan LoadAction - perbedaan utamanya adalah kita memanggil setAndroidActivity. Dalam file yang sama, kita memiliki berbagai contoh ActionBuilder.AndroidActivity.

Untuk openSearch, yang kita gunakan untuk tombol yang dapat diklik ini, kita memanggil setMessagingActivity dan meneruskan string tambahan untuk mengidentifikasi tombol mana yang diklik.

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

internal fun openSearch() = ActionBuilders.AndroidActivity.Builder()
    .setMessagingActivity()
    .addKeyToExtraMapping(
        MainActivity.EXTRA_JOURNEY,
        ActionBuilders.stringExtra(MainActivity.EXTRA_JOURNEY_SEARCH)
    )
    .build()

...

internal fun ActionBuilders.AndroidActivity.Builder.setMessagingActivity(): ActionBuilders.AndroidActivity.Builder {
    return setPackageName("com.example.wear.tiles")
        .setClassName("com.example.wear.tiles.messaging.MainActivity")
}

Jalankan kartu (pastikan untuk menjalankan kartu "pesan", bukan kartu "halo") dan klik tombol telusuri. Tindakan ini akan membuka MainActivity dan menampilkan teks untuk mengonfirmasi bahwa tombol penelusuran diklik.

Langkah untuk menambahkan tindakan pada contoh lainnya serupa. ClickableActions berisi fungsi yang Anda butuhkan. Jika Anda memerlukan petunjuk, lihat MessagingTileRenderer dari modul finished.

10. Selamat

Selamat! Anda telah mempelajari cara membuat kartu untuk Wear OS.

Apa selanjutnya?

Untuk informasi selengkapnya, lihat penerapan Golden Tiles di GitHub, panduan Kartu Wear OS dan panduan desain.