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

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

Domyślne zachowanie przewijania zoptymalizowane pod kątem telewizorów

Od wersji 1.7.0 biblioteki Compose Foundation standardowe układy leniwe (np. LazyRow i LazyColumn) mają wbudowaną obsługę funkcji pozycjonowania fokusu. Jest to zalecany sposób tworzenia katalogów aplikacji na telewizory, ponieważ pomaga zachować widoczność elementów, na których skupia się uwaga, i intuicyjnie je pozycjonować.

Aby wdrożyć podstawową listę z możliwością przewijania, użyj standardowych komponentów leniwych. Te komponenty automatycznie obsługują nawigację za pomocą pada kierunkowego i wyświetlają element, na którym jest fokus.

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 przewijania za pomocą BringIntoViewSpec

Jeśli projekt wymaga określonego punktu „obrotu” (np. utrzymania zaznaczonego elementu dokładnie 30% od lewej krawędzi), możesz dostosować zachowanie przewijania za pomocą BringIntoViewSpec. Zastępuje to starszą funkcję pivotOffsets, ponieważ pozwala dokładnie określić, jak widoczny obszar ma się przewijać, aby dopasować się do elementu, na którym jest fokus.

1. Definiowanie niestandardowego BringIntoViewSpec

Poniższy komponent pomocniczy umożliwia zdefiniowanie „punktu obrotu” na podstawie ułamków nadrzędnych i podrzędnych. Atrybut parentFraction określa, gdzie w kontenerze ma się znaleźć element, a atrybut childFraction określa, która część elementu ma być wyrównana do tego punktu.

@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. Stosowanie specyfikacji niestandardowej

Owiń układy za pomocą funkcji pomocniczej, aby zastosować pozycjonowanie. 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 zagnieżdżony układ, który powinien używać standardowego przewijania zamiast niestandardowego obrotu, 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 { /* ... */ }
            }
        }
    }
}

Przekazanie pustego parametru BringIntoViewSpec powoduje przejęcie domyślnego zachowania frameworka.

Migracja z TV Foundation do Compose Foundation

Układy leniwe specyficzne dla telewizorów 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 telewizyjny Compose Foundation replacement
TvLazyRow LazyRow
TvLazyColumn LazyColumn
TvLazyHorizontalGrid LazyHorizontalGrid
TvLazyVerticalGrid LazyVerticalGrid
pivotOffsets BringIntoViewSpec (przez LocalBringIntoViewSpec)