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. À l'aide des mises en page paresseuses Compose Foundation standards, vous pouvez créer des listes verticales et horizontales performantes qui gèrent automatiquement le défilement axé sur la sélection pour garder les éléments actifs visibles.

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

À partir de Compose Foundation 1.7.0, les mises en page standards à chargement différé (comme LazyRow et LazyColumn) incluent une compatibilité intégrée avec les fonctionnalités de positionnement de la mise au point. Il s'agit de la méthode recommandée pour créer des catalogues pour les applications TV, car elle permet de conserver les éléments sélectionné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 au 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, en gardant l'élément sélectionné 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 précisément 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 parent et enfant. parentFraction détermine l'emplacement de l'élément dans le conteneur, tandis que 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 est utile pour créer une "ligne de mise au point 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 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, en transmettant un BringIntoViewSpec vide, vous permettez 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 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 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)