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 la sélection. En utilisant les mises en page différées standards de Compose Foundation, vous pouvez créer des listes verticales et horizontales performantes qui gèrent automatiquement le défilement basé sur la sélection pour que les éléments actifs restent visibles.

Comportement de défilement par défaut optimisé pour la télévision

À partir de Compose Foundation 1.7.0, les mises en page différées standards (comme LazyRow et LazyColumn) incluent une compatibilité intégrée avec les fonctionnalités de positionnement du curseur. 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 déroulante de base, utilisez les composants Lazy standards. Ces composants gèrent automatiquement la navigation à l'aide du pavé directionnel et affichent l'élément sélectionné.

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 sélectionné reste exactement à 30% du bord gauche), vous pouvez personnaliser le comportement de défilement à l'aide d'un BringIntoViewSpec. Cette fonctionnalité 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 sélectionné.

1. Définir un BringIntoViewSpec personnalisé

Le composable d'assistance suivant vous permet de définir un "pivot" en fonction des fractions parentes et enfants. parentFraction détermine l'emplacement où l'élément doit se trouver dans le conteneur, et childFraction détermine la partie de l'élément qui 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'assistant pour appliquer le positionnement. Cela permet de 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 avez une mise en page imbriquée spécifique qui doit utiliser le comportement de défilement standard au lieu de votre pivot personnalisé, fournissez l'élément 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, la transmission d'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 différées spécifiques à la télévision dans androidx.tv.foundation sont obsolètes et remplacées par les mises en page Compose Foundation standards.

Mises à jour des dépendances

Vérifiez que votre build.gradle utilise la version 1.7.0 ou ultérieure pour :

  • 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 de Compose Foundation
TvLazyRow LazyRow
TvLazyColumn LazyColumn
TvLazyHorizontalGrid LazyHorizontalGrid
TvLazyVerticalGrid LazyVerticalGrid
pivotOffsets BringIntoViewSpec (via LocalBringIntoViewSpec)