Créer des mises en page à faire défiler pour la TV

Pour les applications TV, l'expérience de navigation repose sur une navigation efficace basée sur le focus. À l'aide des mises en page Lazy Compose Foundation standards, vous pouvez créer des listes verticales et horizontales performantes qui gèrent automatiquement le défilement axé sur le focus pour que les éléments actifs restent visibles.

Comportement de défilement par défaut optimisé pour la TV

À partir de Compose Foundation 1.7.0, les mises en page Lazy standards (comme LazyRow et LazyColumn) sont compatibles avec les fonctionnalités de positionnement du focus. Il s'agit de la méthode recommandée pour créer des catalogues pour les applications TV, car elle permet de garder les éléments ciblés visibles et positionnés de manière intuitive pour l'utilisateur.

Pour implémenter une liste de défilement de base, utilisez les composants Lazy standards. Ces composants gèrent automatiquement la navigation avec le pavé directionnel et affichent l'élément ciblé.

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

Personnaliser le comportement de défilement avec BringIntoViewSpec

Si votre conception nécessite un point de "pivot" spécifique (par exemple, pour que l'élément ciblé soit exactement à 30 % du bord gauche), vous pouvez personnaliser le comportement de défilement à l'aide d'un BringIntoViewSpec. Cela remplace l'ancienne fonctionnalité pivotOffsets en vous permettant de définir exactement comment la fenêtre d'affichage doit défiler pour s'adapter à un élément ciblé.

1. Définir un BringIntoViewSpec personnalisé

Le composable d'assistance suivant vous permet de définir un "pivot" en fonction des fractions parent et enfant. Le parentFraction détermine l'endroit où l'élément doit atterrir dans le conteneur, et le childFraction détermine quelle partie de l'élément s'aligne sur ce point.

@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. Appliquer la spécification personnalisée

Encapsulez vos mises en page avec l'assistance pour appliquer le positionnement. Cela est utile pour créer une "ligne de focus cohérente" sur différentes lignes de votre catalogue.

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. Désactiver des mises en page imbriquées spécifiques

Si vous disposez d'une mise en page imbriquée spécifique qui doit utiliser un comportement de défilement standard au lieu de votre pivot personnalisé, fournissez le 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 { ... }
            }
        }
    }
}

En effet, le fait de transmettre un BringIntoViewSpec vide permet au comportement par défaut du framework de prendre le relais.

Migration de TV Foundation vers Compose Foundation

Les mises en page Lazy spécifiques à la TV dans androidx.tv.foundation sont obsolètes au profit des mises en page Compose Foundation standards.

Mises à jour des dépendances

Vérifiez que votre build.gradle utilise la version 1.7.0 ou une version ultérieure pour les éléments suivants :

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

Mappage des composants

Pour effectuer la migration, mettez à jour vos importations et supprimez le préfixe Tv de vos composants :

Composant TV obsolète Remplacement Compose Foundation
TvLazyRow LazyRow
TvLazyColumn LazyColumn
TvLazyHorizontalGrid LazyHorizontalGrid
TvLazyVerticalGrid LazyVerticalGrid
pivotOffsets BringIntoViewSpec (via LocalBringIntoViewSpec)