Listas com o Compose para Wear OS


Listas permitem que os usuários selecionem um item de um conjunto de opções em dispositivos Wear OS.

Muitos dispositivos Wear OS usam telas redondas, o que dificulta a visualização de itens de lista que aparecem perto das partes de cima e de baixo da tela. Por esse motivo, o Compose para Wear OS inclui uma versão da classe LazyColumn chamada TransformingLazyColumn, que oferece suporte a animações de dimensionamento e transformação. Quando os itens se movem para as bordas, eles ficam menores e desaparecem.

Para aplicar os efeitos de rolagem e escalonamento recomendados:

  1. Use Modifier.transformedHeight para permitir que o Compose calcule a mudança de altura à medida que o item rola pela tela.
  2. Use transformation = SurfaceTransformation(transformationSpec) para aplicar os efeitos visuais, incluindo a redução do conteúdo do item.
  3. Use um TransformationSpec personalizado para componentes que não usam transformation como parâmetro, como Text.

A animação a seguir mostra como um elemento de lista é dimensionado e muda de forma ao se aproximar da parte de cima e de baixo da tela:

O snippet de código a seguir mostra como criar uma lista usando o layout TransformingLazyColumn para criar conteúdo que fica ótimo em vários tamanhos de tela do Wear OS.

O snippet também demonstra o uso do modificador minimumVerticalContentPadding, que deve ser definido nos itens da lista para aplicar o padding correto na parte de cima e de baixo da lista.

Para mostrar o indicador de rolagem, compartilhe o columnState entre o ScreenScaffold e o TransformingLazyColumn:

val columnState = rememberTransformingLazyColumnState()
val transformationSpec = rememberTransformationSpec()
ScreenScaffold(
    scrollState = columnState
) { contentPadding ->
    TransformingLazyColumn(
        state = columnState,
        contentPadding = contentPadding
    ) {
        item {
            ListHeader(
                modifier = Modifier
                    .fillMaxWidth()
                    .transformedHeight(this, transformationSpec)
                    .minimumVerticalContentPadding(ListHeaderDefaults.minimumTopListContentPadding),
                transformation = SurfaceTransformation(transformationSpec)
            ) {
                Text(text = "Header")
            }
        }
        // ... other items
        item {
            Button(
                modifier = Modifier
                    .fillMaxWidth()
                    .transformedHeight(this, transformationSpec)
                    .minimumVerticalContentPadding(ButtonDefaults.minimumVerticalListContentPadding),
                transformation = SurfaceTransformation(transformationSpec),
                onClick = { /* ... */ },
                icon = {
                    Icon(
                        imageVector = Icons.Default.Build,
                        contentDescription = "build",
                    )
                },
            ) {
                Text(
                    text = "Build",
                    maxLines = 1,
                    overflow = TextOverflow.Ellipsis,
                )
            }
        }
    }
}

Adicionar um efeito de ajuste e deslize rápido

O ajuste garante que, quando um usuário termina um gesto de rolagem ou deslizar rapidamente, a lista se ajuste com um item posicionado precisamente em um ponto específico, geralmente o centro da tela. Em telas redondas, em que os itens são dimensionados e transformados à medida que se afastam do centro, o ajuste é particularmente útil para garantir que o item mais relevante permaneça totalmente visível e legível na área de visualização ideal.

Para adicionar um comportamento de ajuste e movimento rápido, defina o parâmetro flingBehavior como TransformingLazyColumnDefaults.snapFlingBehavior(columnState). Defina o rotaryScrollableBehavior para corresponder, usando RotaryScrollableDefaults.snapBehavior(columnState) para uma experiência consistente ao usar a coroa ou o bisel físico.

val columnState = rememberTransformingLazyColumnState()
ScreenScaffold(scrollState = columnState) {
    TransformingLazyColumn(
        state = columnState,
        flingBehavior = TransformingLazyColumnDefaults.snapFlingBehavior(columnState),
        rotaryScrollableBehavior = RotaryScrollableDefaults.snapBehavior(columnState)
    ) {
        // ...
        // ...
    }
}

Layout invertido

Por padrão, uma lista rolável é ancorada na borda superior. Se um usuário rolar até a parte de baixo de uma lista padrão e um novo item for adicionado ao final, a lista vai manter a visualização do usuário no item atual. Por exemplo, se o usuário estiver vendo o item 10 na parte de baixo da tela e o item 11 for adicionado, a visualização vai continuar focada no item 10, e o item 11 vai aparecer fora da tela abaixo da visualização atual.

Para casos de uso como aplicativos de mensagens ou registros ativos, esse comportamento geralmente não é desejado. Quando novos itens chegam, os usuários geralmente querem ver o conteúdo mais recente imediatamente se já estiverem na parte de baixo da lista. Se muitos itens chegarem de uma vez, a lista vai pular para mostrar o item mais recente na parte de baixo. Isso significa que alguns itens intermediários podem não ser exibidos, a menos que o usuário role a tela para cima.

Para oferecer suporte a esses casos de uso, o TransformingLazyColumn permite inverter o layout definindo reverseLayout = true. Isso muda a âncora da lista da borda superior para a inferior.

Para sua conveniência, definir reverseLayout = true também inverte a ordem visual dos itens e a direção dos gestos de rolagem:

  • Os itens são compostos de baixo para cima, ou seja, o índice 0 aparece na parte de baixo da tela.
  • Ao rolar para cima, os itens com índices mais altos aparecem.

Para adicionar um comportamento de ajuste e movimento rápido junto com o layout invertido, combine flingBehavior e rotaryScrollableBehavior, conforme mostrado no snippet a seguir:

val columnState = rememberTransformingLazyColumnState()
val transformationSpec = rememberTransformationSpec()
ScreenScaffold(scrollState = columnState) { contentPadding ->
    TransformingLazyColumn(
        state = columnState,
        contentPadding = contentPadding,
        reverseLayout = true,
        modifier = Modifier.fillMaxWidth()
    ) {
        items(10) { index ->
            Button(
                label = {
                    Text(
                        text = "Item ${index + 1}"
                    )
                },
                onClick = {},
                modifier = Modifier
                    .fillMaxWidth()
                    .transformedHeight(this, transformationSpec)
                    .minimumVerticalContentPadding(ButtonDefaults.minimumVerticalListContentPadding),
                transformation = SurfaceTransformation(transformationSpec)
            )
        }
        item {
            // With reverseLayout = true, the last item declared appears at the top.
            ListHeader(
                modifier = Modifier
                    .fillMaxWidth()
                    .transformedHeight(this, transformationSpec)
                    .minimumVerticalContentPadding(ListHeaderDefaults.minimumTopListContentPadding),
                transformation = SurfaceTransformation(transformationSpec)
            ) {
                Text("Header")
            }
        }
    }
}

As imagens a seguir mostram a diferença entre uma lista normal e uma invertida:

Uma TransformingLazyColumn com layout normal, mostrando o item 1 na parte superior e os itens em ordem crescente.
Figura 1. Um layout de lista padrão em que o conteúdo preenche de cima para baixo.
Uma TransformingLazyColumn com layout invertido, mostrando o item 1 na parte de baixo e os itens em ordem decrescente para cima.
Figura 2. Um layout de lista invertida em que o conteúdo é preenchido de baixo para cima.