Puxar para atualizar

A ação deslizar para atualizar permite a um usuário extrair uma lista de dados com toque para recuperar mais dados. Deslizar para atualizar é amplamente usado em dispositivos com tela touch. Você pode usar a API mostrada aqui para implementar a ação deslizar para atualizar em seu aplicativo.

gif deslizar para atualizar

Esse é o controle correto?

Use a ação deslizar para atualizar quando você tiver uma lista ou uma grade de dados que o usuário talvez queira atualizar regularmente e será provável que seu aplicativo seja executado em dispositivos móveis touch-first.

Você também pode usar o RefreshVisualizer para criar uma experiência consistente de atualização que é invocada de outras maneiras, como por um botão Atualizar.

Controles de atualização

Deslizar para atualizar é habilitado por dois controles.

  • RefreshContainer – um ContentControl que fornece um wrapper para a experiência de deslizar para atualizar. Ele manipula as interações de toque e gerencia o estado do seu visualizador interno de atualização.
  • RefreshVisualizer – encapsula a visualização de atualização explicada na próxima seção.

O controle principal é o RefreshContainer, que você insere como um wrapper em torno do conteúdo que o usuário desliza para disparar uma atualização. RefreshContainer só funciona com toque, portanto, recomendamos que você também tenha um botão de atualização disponível para os usuários que não têm uma interface de toque. Você pode posicionar o botão Atualizar em uma localização adequada no aplicativo, em uma barra de comandos ou em uma localização próxima à superfície sendo atualizada.

Visualização de atualização

A visualização de atualização padrão é um controle giratório de progresso circular que é usado para comunicação para quando uma atualização ocorrer e o progresso da atualização depois que ele for iniciado. O visualizador de atualização tem cinco estados.

A distância que o usuário precisa deslizar para baixo em uma lista para iniciar uma atualização é chamada de limite. O Estado do visualizador é determinado pelo estado da ação de deslizar que diz respeito a esse limite. Os valores possíveis estão contidos na enumeração RefreshVisualizerState.

Idle

O estado padrão do visualizador é Ocioso. O usuário não está interagindo com o RefreshContainer por toque e não há uma atualização em andamento.

Visualmente, não há nenhuma evidência do visualizador de atualização.

Interação

Quando o usuário deslizar a lista na direção especificada pela propriedade PullDirection, antes de o limite ser alcançado, o visualizador estará no estado Interação.

  • Se o usuário liberar o controle nesse estado, o controle retornará para Ocioso.

    deslizar para atualizar antes do limite

    Visualmente, o ícone é exibido como desabilitado (60% de opacidade). Além disso, o ícone gira uma rotação completa com a ação de rolagem.

  • Se o usuário desliza a lista para depois do limite, o visualizador faz a transição de Interação para Pendente.

    deslizar para atualizar no limite

    Visualmente, o ícone alterna para 100% de opacidade e pulsos no tamanho de até 150% e, depois, retorna para 100% do tamanho durante a transição.

Pending (Pendente)

Quando o usuário desliza a lista para além do limite, o visualizador fica no estado Pendente.

  • Se o usuário move a lista de volta acima do limite sem liberá-lo, ele retorna para o estado Interação.
  • Se o usuário libera a lista, uma solicitação de atualização é iniciada e faz a transição para o estado Em atualização.

deslizar para atualizar após o limite

Visualmente, o ícone é 100% em tamanho e opacidade. Nesse estado, o ícone continua a se mover para baixo com a ação de rolagem, mas não gira.

Atualizando

Quando o usuário libera o visualizador além do limite, ele fica no estado Em atualização.

Quando esse estado é inserido, o evento RefreshRequested é acionado. Este é o sinal para começar a atualização de conteúdo do aplicativo. Argumentos de evento (RefreshRequestedEventArgs) contêm um objeto Deferral, que você deve executar um identificador para o manipulador de eventos. Em seguida, você deverá marcar o adiamento como concluído quando seu código para realizar a atualização tiver sido concluído.

Quando a atualização for concluída, o visualizador retornará para o estado Ocioso.

Visualmente, o ícone volta para a localização do limite e gira para a duração da atualização. Este giro é usado para mostrar o progresso da atualização e é substituído pela animação do conteúdo de entrada.

Espiar

Quando o usuário desliza na direção de atualização de uma posição inicial em que uma atualização não é permitida, o visualizador entra no estado Espiar. Isso normalmente acontece quando o ScrollViewer não está na posição 0 quando o usuário começa a deslizar.

  • Se o usuário liberar o controle nesse estado, o controle retornará para Ocioso.

Direção de deslizamento

Por padrão, o usuário desliza uma lista de cima para baixo para iniciar uma atualização. Se você tiver uma lista ou grade com uma orientação diferente, deverá alterar a direção de deslizamento do contêiner de atualização para corresponder.

A propriedade PullDirection usa um desses valores de RefreshPullDirection: BottomToTop, TopToBottom, RightToLeft ou LeftToRight.

Quando você altera a direção de pull, a posição inicial do girador de progresso do visualizador gira automaticamente para que a seta comece na posição apropriada para a direção de pull. Se necessário, você pode alterar a propriedade RefreshVisualizer.Orientation para substituir o comportamento automático. Na maioria dos casos, recomendamos deixar o valor padrão como Automático.

UWP e WinUI 2

Importante

As informações e exemplos neste artigo são otimizados para aplicativos que usam o SDK do Aplicativo Windows e o WinUI 3, mas geralmente são aplicáveis a aplicativos UWP que usam o WinUI 2. Consulte a referência da API da UWP para obter informações e exemplos específicos da plataforma.

Esta seção contém informações necessárias para usar o controle em um aplicativo UWP ou WinUI 2.

Os controles de atualização para aplicativos UWP são incluídos como parte da Biblioteca de Interface do Usuário do Windows 2. Para obter mais informações, incluindo instruções de instalação, confira Biblioteca de interface do usuário do Windows. As APIs para esse controle existem nos namespaces Windows.UI.Xaml.Controls (UWP) e Microsoft.UI.Xaml.Controls (WinUI).

É recomendável usar a WinUI 2 mais recente para obter os estilos, modelos e recursos mais atuais para todos os controles.

Para usar o código neste artigo com a WinUI 2, use um alias em XAML (usamos muxc) para representar as APIs da Biblioteca de Interface do Usuário do Windows incluídas em seu projeto. Confira Introdução à WinUI 2 para obter mais informações.

xmlns:muxc="using:Microsoft.UI.Xaml.Controls"

<muxc:RefreshContainer />

Implementar o padrão puxar para atualizar

O aplicativo Galeria da WinUI 3 inclui exemplos interativos da maioria dos controles, recursos e funcionalidades da WinUI 3. Obtenha o aplicativo na Microsoft Store ou o código-fonte no GitHub

Adicionar a funcionalidade deslizar para atualizar a uma lista requer apenas algumas etapas.

  1. Encapsule sua lista em um controle RefreshContainer.
  2. Manipule o evento RefreshRequested para atualizar seu conteúdo.
  3. Opcionalmente, inicie uma atualização chamando RequestRefresh (por exemplo, com o clique de um botão).

Observação

Você pode instanciar um RefreshVisualizer por conta própria. No entanto, recomendamos que você encapsule seu conteúdo em um RefreshContainer e use o RefreshVisualizer fornecido pela propriedade RefreshContainer.Visualizer, até mesmo para cenários não sensíveis ao toque. Neste artigo, assumimos que o visualizador é sempre obtido do contêiner de atualização.

Além disso, usamos os membros RequestRefresh e RefreshRequested do contêiner de atualização por conveniência. refreshContainer.RequestRefresh() é equivalente a refreshContainer.Visualizer.RequestRefresh() e nenhum acionará o evento RefreshContainer.RefreshRequested e os eventos RefreshVisualizer.RefreshRequested.

Solicitar uma atualização

O contêiner de atualização manipula interações de toque para permitir que o usuário atualize conteúdo por toque. Recomendamos que você forneça outras funcionalidades para interfaces não sensíveis ao toque, como um controle de voz ou botão de atualização.

Para iniciar uma atualização, chame o método RequestRefresh.

// See the Examples section for the full code.
private void RefreshButtonClick(object sender, RoutedEventArgs e)
{
    RefreshContainer.RequestRefresh();
}

Quando você chama RequestRefresh, o estado do visualizador vai diretamente de Ocioso para Em atualização.

Manipular uma solicitação de atualização

Para obter conteúdo atualizado quando necessário, manipule o evento RefreshRequested. No manipulador de eventos, você precisará de código específico ao seu aplicativo para obter o conteúdo atualizado.

Os argumentos de evento (RefreshRequestedEventArgs) contêm um objeto Deferral. Obtenha um identificador para o adiamento no manipulador de eventos. Em seguida, marque o adiamento como concluído quando seu código para executar a atualização for concluído.

// See the Examples section for the full code.
private async void RefreshContainer_RefreshRequested(RefreshContainer sender, RefreshRequestedEventArgs args)
{
    // Respond to a request by performing a refresh and using the deferral object.
    using (var RefreshCompletionDeferral = args.GetDeferral())
    {
        // Do some async operation to refresh the content

         await FetchAndInsertItemsAsync(3);

        // The 'using' statement ensures the deferral is marked as complete.
        // Otherwise, you'd call
        // RefreshCompletionDeferral.Complete();
        // RefreshCompletionDeferral.Dispose();
    }
}

Responder a alterações de estado

É possível responder a alterações no estado do visualizador, se necessário. Por exemplo, para evitar várias solicitações de atualização, você poderá desabilitar um botão de atualização enquanto o visualizador estiver sendo atualizado.

// See the Examples section for the full code.
private void Visualizer_RefreshStateChanged(RefreshVisualizer sender, RefreshStateChangedEventArgs args)
{
    // Respond to visualizer state changes.
    // Disable the refresh button if the visualizer is refreshing.
    if (args.NewState == RefreshVisualizerState.Refreshing)
    {
        RefreshButton.IsEnabled = false;
    }
    else
    {
        RefreshButton.IsEnabled = true;
    }
}

Como usar um ScrollViewer em um RefreshContainer

Observação

O conteúdo de um RefreshContainer precisa ser um controle rolável, como ScrollViewer, GridView, ListView etc. A definição do conteúdo como um controle como Grid resultará em um comportamento indefinido.

Este exemplo mostra como usar a ação deslizar para atualizar com um visualizador de rolagem.

<RefreshContainer>
    <ScrollViewer VerticalScrollMode="Enabled"
                  VerticalScrollBarVisibility="Auto"
                  HorizontalScrollBarVisibility="Auto">
 
        <!-- Scrollviewer content -->

    </ScrollViewer>
</RefreshContainer>

Como adicionar a ação deslizar para atualizar a um ListView

Este exemplo mostra como usar a ação deslizar para atualizar com uma exibição de lista.

<StackPanel Margin="0,40" Width="280">
    <CommandBar OverflowButtonVisibility="Collapsed">
        <AppBarButton x:Name="RefreshButton" Click="RefreshButtonClick"
                      Icon="Refresh" Label="Refresh"/>
        <CommandBar.Content>
            <TextBlock Text="List of items" 
                       Style="{StaticResource TitleTextBlockStyle}"
                       Margin="12,8"/>
        </CommandBar.Content>
    </CommandBar>

    <RefreshContainer x:Name="RefreshContainer">
        <ListView x:Name="ListView1" Height="400">
            <ListView.ItemTemplate>
                <DataTemplate x:DataType="local:ListItemData">
                    <Grid Height="80">
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto" />
                            <RowDefinition Height="Auto" />
                            <RowDefinition Height="*" />
                        </Grid.RowDefinitions>
                        <TextBlock Text="{x:Bind Path=Header}"
                                   Style="{StaticResource SubtitleTextBlockStyle}"
                                   Grid.Row="0"/>
                        <TextBlock Text="{x:Bind Path=Date}"
                                   Style="{StaticResource CaptionTextBlockStyle}"
                                   Grid.Row="1"/>
                        <TextBlock Text="{x:Bind Path=Body}"
                                   Style="{StaticResource BodyTextBlockStyle}"
                                   Grid.Row="2"
                                   Margin="0,4,0,0" />
                    </Grid>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </RefreshContainer>
</StackPanel>
public sealed partial class MainPage : Page
{
    public ObservableCollection<ListItemData> Items { get; set; } 
        = new ObservableCollection<ListItemData>();

    public MainPage()
    {
        this.InitializeComponent();

        Loaded += MainPage_Loaded;
        ListView1.ItemsSource = Items;
    }

    private async void MainPage_Loaded(object sender, RoutedEventArgs e)
    {
        Loaded -= MainPage_Loaded;
        RefreshContainer.RefreshRequested += RefreshContainer_RefreshRequested;
        RefreshContainer.Visualizer.RefreshStateChanged += Visualizer_RefreshStateChanged;

        // Add some initial content to the list.
        await FetchAndInsertItemsAsync(2);
    }

    private void RefreshButtonClick(object sender, RoutedEventArgs e)
    {
        RefreshContainer.RequestRefresh();
    }

    private async void RefreshContainer_RefreshRequested(RefreshContainer sender, RefreshRequestedEventArgs args)
    {
        // Respond to a request by performing a refresh and using the deferral object.
        using (var RefreshCompletionDeferral = args.GetDeferral())
        {
            // Do some async operation to refresh the content

            await FetchAndInsertItemsAsync(3);

            // The 'using' statement ensures the deferral is marked as complete.
            // Otherwise, you'd call
            // RefreshCompletionDeferral.Complete();
            // RefreshCompletionDeferral.Dispose();
        }
    }

    private void Visualizer_RefreshStateChanged(RefreshVisualizer sender, RefreshStateChangedEventArgs args)
    {
        // Respond to visualizer state changes.
        // Disable the refresh button if the visualizer is refreshing.
        if (args.NewState == RefreshVisualizerState.Refreshing)
        {
            RefreshButton.IsEnabled = false;
        }
        else
        {
            RefreshButton.IsEnabled = true;
        }
    }

    // App specific code to get fresh data.
    private async Task FetchAndInsertItemsAsync(int updateCount)
    {
        for (int i = 0; i < updateCount; ++i)
        {
            // Simulate delay while we go fetch new items.
            await Task.Delay(1000);
            Items.Insert(0, GetNextItem());
        }
    }

    private ListItemData GetNextItem()
    {
        return new ListItemData()
        {
            Header = "Header " + DateTime.Now.Second.ToString(),
            Date = DateTime.Now.ToLongDateString(),
            Body = DateTime.Now.ToLongTimeString()
        };
    }
}

public class ListItemData
{
    public string Header { get; set; }
    public string Date { get; set; }
    public string Body { get; set; }
}

Obter o código de exemplo