Para apps de TV, a experiência de navegação depende de uma navegação eficiente baseada em foco. Usando layouts lentos padrão do Compose Foundation, é possível criar listas verticais e horizontais eficientes que processam automaticamente a rolagem orientada por foco para manter os itens ativos em exibição.
Comportamento de rolagem padrão otimizado para TV
A partir do Compose Foundation 1.7.0, layouts lentos padrão (como LazyRow
e LazyColumn) incluem suporte integrado para recursos de posicionamento do foco. Essa
é a maneira recomendada de criar catálogos para apps de TV, porque ajuda a manter os itens
em foco visíveis e posicionados de forma intuitiva para o usuário.
Para implementar uma lista rolável básica, use os componentes lazy padrão. Esses componentes processam automaticamente a navegação com o botão direcional e trazem o item em foco para a tela.
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 */ }
)
}
}
}
Personalizar o comportamento de rolagem com BringIntoViewSpec
Se o design exigir um ponto de "pivô" específico (por exemplo, manter o item em foco exatamente a 30% da borda esquerda), personalize o comportamento de rolagem usando um BringIntoViewSpec. Isso substitui a funcionalidade mais antiga pivotOffsets, permitindo definir exatamente como a janela de visualização deve rolar para acomodar um item em foco.
1. Definir um BringIntoViewSpec personalizado
O elemento combinável auxiliar a seguir permite definir um "pivô" com base em frações de elementos pai e
filho. O parentFraction determina onde no contêiner o item
deve ficar, e o childFraction determina qual parte do item se alinha
com esse ponto.
@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. Aplicar a especificação personalizada
Encapsule seus layouts com o auxiliar para aplicar o posicionamento. Isso é útil para criar uma "linha de foco consistente" em diferentes linhas do seu catálogo.
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. Desativar layouts aninhados específicos
Se você tiver um layout aninhado específico que precise usar o comportamento de rolagem padrão
em vez do seu pivô personalizado, forneça o 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 { ... }
}
}
}
}
Na prática, transmitir um BringIntoViewSpec vazio permite que o comportamento padrão do framework assuma o controle.
Migração da TV Foundation para a Compose Foundation
Os layouts lentos específicos para TV em androidx.tv.foundation foram descontinuados em favor
dos layouts padrão do Compose Foundation.
Atualizações de dependências
Verifique se o build.gradle usa a versão 1.7.0 ou mais recente para:
androidx.compose.foundationandroidx.compose.runtime
Mapeamento de componentes
Para migrar, atualize as importações e remova o prefixo Tv dos componentes:
| Componente de TV descontinuado | Substituição do Compose Foundation |
|---|---|
| TvLazyRow | LazyRow |
| TvLazyColumn | LazyColumn |
| TvLazyHorizontalGrid | LazyHorizontalGrid |
| TvLazyVerticalGrid | LazyVerticalGrid |
| pivotOffsets | BringIntoViewSpec (via LocalBringIntoViewSpec) |