Tạo bố cục có thể cuộn cho TV

Đối với ứng dụng truyền hình, trải nghiệm duyệt xem dựa trên chế độ điều hướng hiệu quả dựa trên tiêu điểm. Khi sử dụng bố cục lười của Compose Foundation tiêu chuẩn, bạn có thể tạo các danh sách dọc và ngang có hiệu suất cao, tự động xử lý hoạt động cuộn dựa trên tiêu điểm để giữ các mục đang hoạt động trong chế độ xem.

Hành vi cuộn mặc định được tối ưu hoá cho TV

Kể từ Compose Foundation 1.7.0, các bố cục chuẩn tải từng phần (như LazyRowLazyColumn) có hỗ trợ sẵn cho các tính năng định vị tiêu điểm. Đây là cách nên dùng để tạo danh mục cho ứng dụng truyền hình vì cách này giúp các mục được tập trung vẫn hiển thị và được đặt ở vị trí trực quan cho người dùng.

Để triển khai một danh sách cơ bản có thể cuộn, hãy sử dụng các thành phần lười biếng tiêu chuẩn. Các thành phần này tự động xử lý thao tác điều hướng bằng D-pad và đưa mục được lấy tiêu điểm vào chế độ xem.

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 */ }
            )
        }
    }
}

Tuỳ chỉnh hành vi cuộn bằng BringIntoViewSpec

Nếu thiết kế của bạn yêu cầu một điểm "xoay" cụ thể (ví dụ: giữ mục được lấy tiêu điểm chính xác 30% từ cạnh trái), bạn có thể tuỳ chỉnh hành vi cuộn bằng cách sử dụng BringIntoViewSpec. Thao tác này sẽ thay thế chức năng pivotOffsets cũ bằng cách cho phép bạn xác định chính xác cách khung hiển thị sẽ cuộn để phù hợp với một mục được lấy tiêu điểm.

1. Xác định một BringIntoViewSpec tuỳ chỉnh

Thành phần kết hợp hỗ trợ sau đây cho phép bạn xác định một "trục" dựa trên các phân số mẹ và con. parentFraction xác định vị trí mà mục sẽ nằm trong vùng chứa và childFraction xác định phần nào của mục sẽ căn chỉnh với điểm đó.

@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. Áp dụng quy cách tuỳ chỉnh

Bao bọc bố cục của bạn bằng trình trợ giúp để áp dụng việc định vị. Điều này rất hữu ích khi tạo "đường lấy nét nhất quán" trên nhiều hàng trong danh mục của bạn.

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. Chọn không sử dụng cho các bố cục lồng cụ thể

Nếu bạn có một bố cục lồng cụ thể nên sử dụng hành vi cuộn tiêu chuẩn thay vì trục tuỳ chỉnh, hãy cung cấp 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 { ... }
            }
        }
    }
}

Trên thực tế, bằng cách truyền một BringIntoViewSpec trống, bạn có thể cho phép hành vi mặc định của khung tiếp quản.

Di chuyển từ TV Foundation sang Compose Foundation

Các bố cục lười biếng dành riêng cho TV trong androidx.tv.foundation không được dùng nữa để chuyển sang các bố cục Compose Foundation tiêu chuẩn.

Cập nhật phần phụ thuộc

Xác minh rằng build.gradle sử dụng phiên bản 1.7.0 trở lên cho:

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

Liên kết thành phần

Để di chuyển, hãy cập nhật các mục nhập và xoá tiền tố Tv khỏi các thành phần của bạn:

Thành phần TV không dùng nữa Thay thế Compose Foundation
TvLazyRow LazyRow
TvLazyColumn LazyColumn
TvLazyHorizontalGrid LazyHorizontalGrid
TvLazyVerticalGrid LazyVerticalGrid
pivotOffsets BringIntoViewSpec (thông qua LocalBringIntoViewSpec)