สร้างเลย์เอาต์ที่เลื่อนได้สำหรับทีวี

สำหรับแอปทีวี ประสบการณ์การเรียกดูจะขึ้นอยู่กับการนำทางที่อิงตามโฟกัสที่มีประสิทธิภาพ การใช้เลย์เอาต์แบบเลื่อนที่ขี้เกียจของ Compose Foundation มาตรฐานจะช่วยให้คุณสร้างรายการแนวตั้งและแนวนอนที่มีประสิทธิภาพ ซึ่งจะจัดการการเลื่อนที่ขับเคลื่อนด้วยโฟกัสโดยอัตโนมัติ เพื่อให้รายการที่ใช้งานอยู่แสดงอยู่

ลักษณะการเลื่อนเริ่มต้นที่ปรับให้เหมาะกับทีวี

ตั้งแต่ Compose Foundation 1.7.0 เป็นต้นไป เลย์เอาต์ Lazy มาตรฐาน (เช่น LazyRow และ LazyColumn) จะรองรับฟีเจอร์การวางตำแหน่งโฟกัสในตัว วิธีนี้เป็นวิธีที่แนะนำในการสร้างแคตตาล็อกสำหรับแอปทีวี เนื่องจากจะช่วยให้รายการที่โฟกัสยังคงมองเห็นได้และอยู่ในตำแหน่งที่ผู้ใช้เข้าใจได้ง่าย

หากต้องการใช้รายการที่เลื่อนได้พื้นฐาน ให้ใช้คอมโพเนนต์ Lazy มาตรฐาน คอมโพเนนต์เหล่านี้จะจัดการการนำทางด้วยแป้นลูกศรและนำรายการที่โฟกัสมาไว้ในมุมมองโดยอัตโนมัติ

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

ปรับแต่งลักษณะการเลื่อนด้วย BringIntoViewSpec

หากการออกแบบของคุณต้องมีจุด "หมุน" ที่เฉพาะเจาะจง (เช่น การให้รายการที่โฟกัสอยู่ห่างจากขอบด้านซ้าย 30% พอดี) คุณสามารถปรับแต่งลักษณะการเลื่อนได้โดยใช้ BringIntoViewSpec ซึ่งจะแทนที่pivotOffsets ฟังก์ชันการทำงานรุ่นเก่าด้วยการให้คุณกำหนดวิธีที่วิวพอร์ตควรเลื่อน เพื่อรองรับรายการที่โฟกัสได้อย่างแม่นยำ

1. กำหนด BringIntoViewSpec ที่กำหนดเอง

Composable ตัวช่วยต่อไปนี้ช่วยให้คุณกำหนด "จุดหมุน" ตามเศษส่วนขององค์ประกอบหลักและองค์ประกอบย่อยได้ parentFraction จะกำหนดตำแหน่งที่ควรวางรายการในคอนเทนเนอร์ และ childFraction จะกำหนดส่วนของรายการที่สอดคล้องกับจุดนั้น

@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. ใช้ข้อมูลจำเพาะที่กำหนดเอง

วางเลย์เอาต์ด้วยตัวช่วยเพื่อใช้การจัดตำแหน่ง ซึ่งมีประโยชน์ในการ สร้าง "เส้นโฟกัสที่สอดคล้องกัน" ในแคตตาล็อกแถวต่างๆ

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. เลือกไม่ใช้เลย์เอาต์ที่ซ้อนกันบางรายการ

หากคุณมีการจัดวางซ้อนกันที่เฉพาะเจาะจงซึ่งควรใช้ลักษณะการเลื่อนมาตรฐาน แทนการหมุนที่กำหนดเอง ให้ระบุ 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 { ... }
            }
        }
    }
}

กล่าวคือ การส่ง BringIntoViewSpec ที่ว่างเปล่าจะทำให้ลักษณะการทำงานเริ่มต้นของเฟรมเวิร์ก มีผล

การย้ายข้อมูลจาก TV Foundation ไปยัง Compose Foundation

เลย์เอาต์เลซี่สำหรับทีวีใน androidx.tv.foundation จะเลิกใช้งานเพื่อ ใช้เลย์เอาต์ Compose Foundation มาตรฐานแทน

การอัปเดตการขึ้นต่อกัน

ตรวจสอบว่า build.gradle ใช้เวอร์ชัน 1.7.0 ขึ้นไปสำหรับรายการต่อไปนี้

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

การแมปคอมโพเนนต์

หากต้องการย้ายข้อมูล ให้อัปเดตการนำเข้าและนำคำนำหน้า Tv ออกจากคอมโพเนนต์โดยทำดังนี้

คอมโพเนนต์ทีวีที่เลิกใช้งานแล้ว การแทนที่ Compose Foundation
TvLazyRow LazyRow
TvLazyColumn LazyColumn
TvLazyHorizontalGrid LazyHorizontalGrid
TvLazyVerticalGrid LazyVerticalGrid
pivotOffsets BringIntoViewSpec (ผ่าน LocalBringIntoViewSpec)