Tworzenie układów z możliwością przewijania na potrzeby telewizorów

W przypadku aplikacji TV przeglądanie opiera się na wydajnej nawigacji opartej na zaznaczeniu. Korzystając ze standardowych leniwych układów Compose Foundation, możesz tworzyć wydajne listy pionowe i poziome, które automatycznie obsługują przewijanie sterowane zaznaczeniem, aby aktywne elementy były widoczne.

Domyślne przewijanie zoptymalizowane pod kątem telewizorów

Od wersji Compose Foundation 1.7.0 standardowe układy Lazy (takie jak LazyRow i LazyColumn) zawierają wbudowaną obsługę funkcji pozycjonowania zaznaczenia. Jest to zalecany sposób tworzenia katalogów dla aplikacji TV, ponieważ pomaga utrzymać zaznaczone elementy w widocznym miejscu i intuicyjnie je pozycjonować.

Aby zaimplementować podstawową listę przewijaną, użyj standardowych komponentów Lazy. Te komponenty automatycznie obsługują nawigację za pomocą pada kierunkowego i wyświetlają zaznaczony element.

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

Dostosowywanie działania przewijania za pomocą BringIntoViewSpec

Jeśli Twój projekt wymaga określonego punktu „obrotu” (np. utrzymania elementu, na którym jest ustawiony fokus, dokładnie w odległości 30% od lewej krawędzi), możesz dostosować zachowanie przewijania za pomocą BringIntoViewSpec. Zastępuje to starszą funkcję pivotOffsets, umożliwiając dokładne określenie, jak widoczny obszar powinien się przewijać, aby pomieścić zaznaczony element.

1. Definiowanie niestandardowego elementu BringIntoViewSpec

Poniższy pomocniczy element kompozycyjny umożliwia zdefiniowanie „punktu obrotu” na podstawie ułamków nadrzędnych i podrzędnych. parentFraction określa, gdzie w kontenerze powinien znaleźć się element, a childFraction określa, która część elementu jest wyrównana z tym punktem.

@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. Zastosuj specyfikację niestandardową

Aby zastosować pozycjonowanie, opakuj układy za pomocą pomocnika. Jest to przydatne do tworzenia „spójnej linii ostrości” w różnych wierszach katalogu.

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. Rezygnacja z określonych zagnieżdżonych układów

Jeśli masz konkretny układ zagnieżdżony, który powinien używać standardowego przewijania zamiast niestandardowego przestawiania, podaj 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 { /* ... */ }
            }
        }
    }
}

W efekcie przekazanie pustego BringIntoViewSpec umożliwia przejęcie kontroli przez działanie domyślne platformy.

Migracja z TV Foundation do Compose Foundation

Układy Lazy przeznaczone na telewizory w androidx.tv.foundation zostały wycofane na rzecz standardowych układów Compose Foundation.

Aktualizacje zależności

Sprawdź, czy build.gradle używa wersji 1.7.0 lub nowszej w przypadku:

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

Mapowanie komponentów

Aby przeprowadzić migrację, zaktualizuj importy i usuń prefiks Tv z komponentów:

Wycofany komponent TV Zamiennik Compose Foundation
TvLazyRow LazyRow
TvLazyColumn LazyColumn
TvLazyHorizontalGrid LazyHorizontalGrid
TvLazyVerticalGrid LazyVerticalGrid
pivotOffsets BringIntoViewSpec (przez LocalBringIntoViewSpec)