Puxe para baixo para atualizar

O componente "puxar para atualizar" permite que os usuários arrastem para baixo no início do conteúdo de um app para atualizar os dados.

Superfície da API

Use o elemento combinável PullToRefreshBox para implementar a ação de puxar para atualizar, que atua como um contêiner para o conteúdo rolável. Os seguintes parâmetros principais controlam o comportamento e a aparência da atualização:

  • isRefreshing: um valor booleano que indica se a ação de atualização está em andamento.
  • onRefresh: uma função lambda que é executada quando o usuário inicia uma atualização.
  • indicator: personaliza o indicador que é exibido no recurso de puxar para atualizar.

Exemplo básico

Este snippet demonstra o uso básico de PullToRefreshBox:

@Composable
fun PullToRefreshBasicSample(
    items: List<String>,
    isRefreshing: Boolean,
    onRefresh: () -> Unit,
    modifier: Modifier = Modifier
) {
    PullToRefreshBox(
        isRefreshing = isRefreshing,
        onRefresh = onRefresh,
        modifier = modifier
    ) {
        LazyColumn(Modifier.fillMaxSize()) {
            items(items) {
                ListItem({ Text(text = it) })
            }
        }
    }
}

Pontos principais sobre o código

  • PullToRefreshBox envolve um LazyColumn, que mostra uma lista de strings.
  • PullToRefreshBox requer os parâmetros isRefreshing e onRefresh.
  • O conteúdo dentro do bloco PullToRefreshBox representa o conteúdo rolável.

Resultado

Este vídeo demonstra a implementação básica de puxar para atualizar do código anterior:

Figura 1. Uma implementação básica de puxar para atualizar em uma lista de itens.

Exemplo avançado: personalizar a cor do indicador

@Composable
fun PullToRefreshCustomStyleSample(
    items: List<String>,
    isRefreshing: Boolean,
    onRefresh: () -> Unit,
    modifier: Modifier = Modifier
) {
    val state = rememberPullToRefreshState()

    PullToRefreshBox(
        isRefreshing = isRefreshing,
        onRefresh = onRefresh,
        modifier = modifier,
        state = state,
        indicator = {
            Indicator(
                modifier = Modifier.align(Alignment.TopCenter),
                isRefreshing = isRefreshing,
                containerColor = MaterialTheme.colorScheme.primaryContainer,
                color = MaterialTheme.colorScheme.onPrimaryContainer,
                state = state
            )
        },
    ) {
        LazyColumn(Modifier.fillMaxSize()) {
            items(items) {
                ListItem({ Text(text = it) })
            }
        }
    }
}

Pontos principais sobre o código

  • A cor do indicador é personalizada pelas propriedades containerColor e color no parâmetro indicator.
  • rememberPullToRefreshState() gerencia o estado da ação de atualização. Use esse estado com o parâmetro indicator.

Resultado

Este vídeo mostra uma implementação de puxar para atualizar com um indicador colorido:

Figura 2. Uma implementação de puxar para atualizar com um estilo personalizado.

Exemplo avançado: criar um indicador totalmente personalizado

É possível criar indicadores personalizados complexos usando elementos combináveis e animações.Este snippet demonstra como criar um indicador totalmente personalizado na implementação de puxar para atualizar:

@Composable
fun PullToRefreshCustomIndicatorSample(
    items: List<String>,
    isRefreshing: Boolean,
    onRefresh: () -> Unit,
    modifier: Modifier = Modifier
) {
    val state = rememberPullToRefreshState()

    PullToRefreshBox(
        isRefreshing = isRefreshing,
        onRefresh = onRefresh,
        modifier = modifier,
        state = state,
        indicator = {
            MyCustomIndicator(
                state = state,
                isRefreshing = isRefreshing,
                modifier = Modifier.align(Alignment.TopCenter)
            )
        }
    ) {
        LazyColumn(Modifier.fillMaxSize()) {
            items(items) {
                ListItem({ Text(text = it) })
            }
        }
    }
}

// ...
@Composable
fun MyCustomIndicator(
    state: PullToRefreshState,
    isRefreshing: Boolean,
    modifier: Modifier = Modifier,
) {
    Box(
        modifier = modifier.pullToRefreshIndicator(
            state = state,
            isRefreshing = isRefreshing,
            containerColor = PullToRefreshDefaults.containerColor,
            threshold = PositionalThreshold
        ),
        contentAlignment = Alignment.Center
    ) {
        Crossfade(
            targetState = isRefreshing,
            animationSpec = tween(durationMillis = CROSSFADE_DURATION_MILLIS),
            modifier = Modifier.align(Alignment.Center)
        ) { refreshing ->
            if (refreshing) {
                CircularProgressIndicator(Modifier.size(SPINNER_SIZE))
            } else {
                val distanceFraction = { state.distanceFraction.coerceIn(0f, 1f) }
                Icon(
                    imageVector = Icons.Filled.CloudDownload,
                    contentDescription = "Refresh",
                    modifier = Modifier
                        .size(18.dp)
                        .graphicsLayer {
                            val progress = distanceFraction()
                            this.alpha = progress
                            this.scaleX = progress
                            this.scaleY = progress
                        }
                )
            }
        }
    }
}

Pontos principais sobre o código

  • O snippet anterior usou o Indicator fornecido pela biblioteca. Esse snippet cria um elemento combinável de indicador personalizado chamado MyCustomIndicator. Neste elemento combinável, o modificador pullToRefreshIndicator processa o posicionamento e aciona uma atualização.
  • Como no snippet anterior, a instância PullToRefreshState foi extraida, então a mesma instância pode ser transmitida para o PullToRefreshBox e o pullToRefreshModifier.
  • A cor do contêiner e o limite de posição são usados da classe PullToRefreshDefaults. Dessa forma, você pode reutilizar o comportamento padrão e o estilo da biblioteca do Material, personalizando apenas os elementos em que você tem interesse.
  • O MyCustomIndicator usa Crossfade para fazer a transição entre um ícone de nuvem e um CircularProgressIndicator. O ícone de nuvem é redimensionado conforme o usuário puxa e faz a transição para um CircularProgressIndicator quando a ação de atualização começa.
    • targetState usa isRefreshing para determinar qual estado mostrar (o ícone de nuvem ou o indicador de progresso circular).
    • animationSpec define uma animação tween para a transição, com uma duração especificada de CROSSFADE_DURATION_MILLIS.
    • state.distanceFraction representa o quanto o usuário desceu, variando de 0f (sem puxar) a 1f (puxado totalmente).
    • O modificador graphicsLayer modifica a escala e a transparência.

Resultado

Este vídeo mostra o indicador personalizado do código anterior:

Figura 3. Uma implementação de puxar para atualizar com um indicador personalizado.

Outros recursos