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. LazyRowLazyColumn) mają wbudowaną obsługę funkcji pozycjonowania fokusu. Jest to zalecany sposób tworzenia katalogów aplikacji na telewizory, ponieważ pomaga utrzymać widoczność elementów, na których skupia się uwaga użytkownika, 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ą 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 przewijania za pomocą BringIntoViewSpec

Jeśli Twój 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ż umożliwia dokładne określenie sposobu przewijania widocznego obszaru w celu dopasowania go do zaznaczonego elementu.

1. Definiowanie niestandardowego celu BringIntoViewSpec

Poniższy komponent kompozycyjny pomocniczy umożliwia zdefiniowanie „punktu obrotu” na podstawie ułamków nadrzędnych i podrzędnych. Atrybut parentFraction określa, w którym miejscu kontenera 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. Zastosuj specyfikację niestandardową

Aby zastosować pozycjonowanie, umieść układy w funkcji pomocniczej. Jest to przydatne przy tworzeniu „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 platformy.

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)