Membuat tata letak yang dapat di-scroll untuk TV

Untuk aplikasi TV, pengalaman penjelajahan mengandalkan navigasi berbasis fokus yang efisien. Dengan tata letak lambat Compose Foundation standar, Anda dapat membuat daftar vertikal dan horizontal berperforma tinggi yang otomatis menangani scrolling yang didorong fokus untuk menjaga item aktif tetap terlihat.

Perilaku scroll default yang dioptimalkan untuk TV

Mulai dari Compose Foundation 1.7.0, tata letak lambat standar (seperti LazyRow dan LazyColumn) menyertakan dukungan bawaan untuk fitur pemosisian fokus. Cara ini adalah cara yang direkomendasikan untuk membuat katalog aplikasi TV karena membantu menjaga item yang menjadi fokus tetap terlihat dan diposisikan secara intuitif bagi pengguna.

Untuk menerapkan daftar yang dapat di-scroll dasar, gunakan komponen lazy standar. Komponen ini otomatis menangani navigasi D-pad dan menampilkan item yang difokuskan.

import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items

@Composable
fun MovieCatalog(movies: List<Movie>) {
    LazyRow {
        items(movies) { movie ->
            MovieCard(
                movie = movie,
                onClick = { /* Handle click */ }
            )
        }
    }
}

Menyesuaikan perilaku scroll dengan BringIntoViewSpec

Jika desain Anda memerlukan titik "pivot" tertentu (misalnya, menjaga item yang difokuskan tepat 30% dari tepi kiri), Anda dapat menyesuaikan perilaku scrolling menggunakan BringIntoViewSpec. Hal ini menggantikan fungsi pivotOffsets yang lebih lama dengan memungkinkan Anda menentukan secara persis cara area tampilan harus men-scroll untuk mengakomodasi item yang difokuskan.

1. Menentukan BringIntoViewSpec kustom

Composable helper berikut memungkinkan Anda menentukan "pivot" berdasarkan fraksi induk dan turunan. parentFraction menentukan di mana item harus ditempatkan dalam penampung, dan childFraction menentukan bagian item mana yang sejajar dengan titik tersebut.

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun PositionFocusedItemInLazyLayout(
    parentFraction: Float = 0.3f,
    childFraction: Float = 0f,
    content: @Composable () -> Unit,
) {
    val bringIntoViewSpec = remember(parentFraction, childFraction) {
        object : BringIntoViewSpec {
            override fun calculateScrollDistance(
                offset: Float,       // Item's initial position
                size: Float,         // Item's size
                containerSize: Float // Container's size
            ): Float {
                // Calculate the offset position of the item's leading edge.
                val initialTargetForLeadingEdge =
                    parentFraction * containerSize - (childFraction * size)
                // If the item fits in the container, and scrolling would cause
                // its trailing edge to be clipped, adjust targetForLeadingEdge
                // to prevent over-scrolling near the end of list.
                val targetForLeadingEdge = if (size <= containerSize &&
                    (containerSize - initialTargetForLeadingEdge) < size) {
                    // If clipped, align the item's trailing edge with the
                    // container's trailing edge.
                    containerSize - size
                } else {
                    initialTargetForLeadingEdge
                }
                // Return scroll distance relative to initial item position.
                return offset - targetForLeadingEdge
            }
        }
    }

    // Apply the spec to all scrollables in the hierarchy
    CompositionLocalProvider(
        LocalBringIntoViewSpec provides bringIntoViewSpec,
        content = content,
    )
}

2. Menerapkan spesifikasi kustom

Bungkus tata letak Anda dengan helper untuk menerapkan penentuan posisi. Hal ini berguna untuk membuat "garis fokus yang konsisten" di berbagai baris katalog Anda.

PositionFocusedItemInLazyLayout(
    parentFraction = 0.3f, // Pivot 30% from the edge
    childFraction = 0.5f   // Center of the item aligns with the pivot
) {
    LazyColumn {
        items(sectionList) { section ->
            // This row and its items will respect the 30% pivot
            LazyRow { ... }
        }
    }
}

3. Memilih tidak ikut untuk tata letak bertingkat tertentu

Jika Anda memiliki tata letak bertingkat tertentu yang harus menggunakan perilaku scrolling standar, bukan pivot kustom, berikan DefaultBringIntoViewSpec:

private val DefaultBringIntoViewSpec = object : BringIntoViewSpec {}

PositionFocusedItemInLazyLayout {
    LazyColumn {
        item {
            // This row will ignore the custom pivot and use default behavior
            CompositionLocalProvider(LocalBringIntoViewSpec provides DefaultBringIntoViewSpec) {
                LazyRow { ... }
            }
        }
    }
}

Pada dasarnya, dengan meneruskan BringIntoViewSpec kosong, perilaku default framework akan mengambil alih.

Migrasi dari TV Foundation ke Compose Foundation

Tata letak lambat khusus TV di androidx.tv.foundation tidak digunakan lagi dan digantikan dengan tata letak Compose Foundation standar.

Pembaruan dependensi

Pastikan build.gradle Anda menggunakan versi 1.7.0 atau yang lebih tinggi untuk:

  • androidx.compose.foundation
  • androidx.compose.runtime

Pemetaan komponen

Untuk melakukan migrasi, perbarui impor dan hapus awalan Tv dari komponen Anda:

Komponen TV yang tidak digunakan lagi Penggantian Compose Foundation
TvLazyRow LazyRow
TvLazyColumn LazyColumn
TvLazyHorizontalGrid LazyHorizontalGrid
TvLazyVerticalGrid LazyVerticalGrid
pivotOffsets BringIntoViewSpec (melalui LocalBringIntoViewSpec)