Creare layout scorrevoli per la TV

Per le app TV, l'esperienza di navigazione si basa su una navigazione efficiente basata sulla messa a fuoco. Utilizzando i layout pigri standard di Compose Foundation, puoi creare elenchi verticali e orizzontali efficienti che gestiscono automaticamente lo scorrimento basato sullo stato attivo per mantenere in vista gli elementi attivi.

Comportamento di scorrimento predefinito ottimizzato per la TV

A partire da Compose Foundation 1.7.0, i layout pigri standard (come LazyRow e LazyColumn) includono il supporto integrato per le funzionalità di posizionamento della messa a fuoco. Questo è il modo consigliato per creare cataloghi per le app TV, in quanto consente di mantenere visibili e posizionati in modo intuitivo per l'utente gli elementi in primo piano.

Per implementare un elenco di base scorrevole, utilizza i componenti lazy standard. Questi componenti gestiscono automaticamente la navigazione con il D-pad e portano l'elemento selezionato in primo piano.

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

Personalizza il comportamento di scorrimento con BringIntoViewSpec

Se il tuo design richiede un punto di "pivot" specifico (ad esempio, mantenere l'elemento focalizzato esattamente al 30% dal bordo sinistro), puoi personalizzare lo scorrimento utilizzando un BringIntoViewSpec. Questa funzionalità sostituisce la precedente pivotOffsets consentendoti di definire esattamente come deve scorrere la finestra per adattarsi a un elemento selezionato.

1. Definisci un BringIntoViewSpec personalizzato

Il seguente composable helper ti consente di definire un "pivot" in base alle frazioni padre e figlio. parentFraction determina il punto del contenitore in cui deve essere posizionato l'elemento, mentre childFraction determina la parte dell'elemento che si allinea a quel punto.

@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. Applica la specifica personalizzata

Racchiudi i layout con l'helper per applicare il posizionamento. Questa funzionalità è utile per creare una "linea di messa a fuoco coerente" in diverse righe del catalogo.

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. Disattivazione per layout nidificati specifici

Se hai un layout nidificato specifico che deve utilizzare il comportamento di scorrimento standard anziché il pivot personalizzato, fornisci 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 { ... }
            }
        }
    }
}

In effetti, il passaggio di un BringIntoViewSpec vuoto consente di attivare il comportamento predefinito del framework.

Migrazione da TV Foundation a Compose Foundation

I layout pigri specifici per la TV in androidx.tv.foundation sono ritirati a favore dei layout standard di Compose Foundation.

Aggiornamenti delle dipendenze

Verifica che build.gradle utilizzi la versione 1.7.0 o successive per:

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

Mappatura dei componenti

Per eseguire la migrazione, aggiorna le importazioni e rimuovi il prefisso Tv dai componenti:

Componente TV ritirato Compose Foundation replacement
TvLazyRow LazyRow
TvLazyColumn LazyColumn
TvLazyHorizontalGrid LazyHorizontalGrid
TvLazyVerticalGrid LazyVerticalGrid
pivotOffsets BringIntoViewSpec (tramite LocalBringIntoViewSpec)