Compartilhar via


Atualizar puxando com modificadores de origem

Neste artigo, vamos nos aprofundar em como usar um recurso SourceModifier do InteractionTracker e demonstrar seu uso criando um controle personalizado de pull-to-refresh.

Pré-requisitos

Aqui, presumimos que você esteja familiarizado com os conceitos discutidos nestes artigos:

O que é um SourceModifier e por que eles são úteis?

Assim como InertiaModifiers, SourceModifiers oferece controle de grãos mais fino sobre o movimento de um InteractionTracker. Mas ao contrário de InertiaModifiers que definem o movimento depois que InteractionTracker entra em inércia, SourceModifiers definem o movimento enquanto InteractionTracker ainda está em seu estado de interação. Nesses casos, você deseja uma experiência diferente da tradicional "grude no dedo".

Um exemplo clássico disso é a experiência de pull-to-refresh - quando o usuário puxa a lista para atualizar o conteúdo e a lista se movimenta na mesma velocidade que o dedo e para após uma certa distância, o movimento parece abrupto e mecânico. Uma experiência mais natural seria introduzir uma sensação de resistência enquanto o usuário interage ativamente com a lista. Essa pequena nuance ajuda a tornar a experiência geral do usuário final de interagir com uma lista mais dinâmica e atraente. Nesta seção de exemplo, entramos em mais detalhes sobre como criar isso.

Há dois tipos de modificadores de origem:

  • DeltaPosition – é o delta entre a posição do quadro atual e a posição do quadro anterior do dedo durante a interação com o painel de toque. Esse modificador de origem permite modificar a posição delta da interação antes de enviá-la para processamento adicional. Esse é um parâmetro de tipo Vector3 e o desenvolvedor pode optar por modificar qualquer um dos atributos X ou Y ou Z da posição antes de passá-lo para o InteractionTracker.
  • DeltaScale - é o delta entre a escala do quadro atual e a escala do quadro anterior que foi aplicada durante a interação de zoom de toque. Esse modificador de origem permite modificar o nível de zoom da interação. Esse é um atributo de tipo float que o desenvolvedor pode modificar antes de passá-lo para o InteractionTracker.

Quando InteractionTracker está em seu estado de Interação, ele avalia cada um dos Modificadores de Origem atribuídos a ele e determina se algum deles se aplica. Isso significa que você pode criar e atribuir vários Modificadores de Origem a um InteractionTracker. Mas, ao definir cada um, você precisa fazer o seguinte:

  1. Definir a condição – uma expressão que define a instrução condicional quando esse modificador de origem específico deve ser aplicado.
  2. Definir a DeltaPosition/DeltaScale – a expressão modificadora de origem que altera DeltaPosition ou DeltaScale quando a condição definida acima é atendida.

Exemplo

Agora, vamos examinar como você pode usar Source Modifiers para criar uma experiência personalizada de "pull-to-refresh" com um controle WinUI XAML ListView existente. Usaremos um Canvas como o "Painel de Atualização" que será empilhado sobre um ListView XAML para criar essa experiência.

Para a experiência do usuário final, queremos criar o efeito de "resistência" enquanto o usuário está ativamente deslizando a lista (com toque) e parar o movimento quando a posição ultrapassa um determinado ponto.

Lista com puxar para atualizar

O código funcional para essa experiência pode ser encontrado no repositório Window UI Dev Labs no GitHub. Aqui está o passo a passo para construir essa experiência. No código de marcação XAML, você tem o seguinte:

<StackPanel Height="500" MaxHeight="500" x:Name="ContentPanel" HorizontalAlignment="Left" VerticalAlignment="Top" >
 <Canvas Width="400" Height="100" x:Name="RefreshPanel" >
<Image x:Name="FirstGear" Source="ms-appx:///Assets/Loading.png" Width="20" Height="20" Canvas.Left="200" Canvas.Top="70"/>
 </Canvas>
 <ListView x:Name="ThumbnailList"
 MaxWidth="400"
 Height="500"
ScrollViewer.VerticalScrollMode="Enabled" ScrollViewer.IsScrollInertiaEnabled="False" ScrollViewer.IsVerticalScrollChainingEnabled="True" >
 <ListView.ItemTemplate>
 ……
 </ListView.ItemTemplate>
 </ListView>
</StackPanel>

Como o ListView (ThumbnailList) é um controle XAML que já rola, é necessário que a rolagem seja repassada ao controle pai (ContentPanel) quando atingir o item superior e não puder mais rolar. (ContentPanel é onde você aplicará os Modificadores de Origem.) Para isso acontecer, você precisa definir ScrollViewer.IsVerticalScrollChainingEnabled como true na marcação ListView. Você também precisará definir o modo de encadeamento no VisualInteractionSource como Always.

Você precisa definir o manipulador PointerPressedEvent com o parâmetro handledEventsToo como true. Sem essa opção, o PointerPressedEvent não será encadeado ao ContentPanel, pois o controle ListView marcará esses eventos como manipulados e eles não serão enviados para a cadeia visual.

//The PointerPressed handler needs to be added using AddHandler method with the //handledEventsToo boolean set to "true"
//instead of the XAML element's "PointerPressed=Window_PointerPressed",
//because the list view needs to chain PointerPressed handled events as well.
ContentPanel.AddHandler(PointerPressedEvent, new PointerEventHandler( Window_PointerPressed), true);

Agora, você está pronto para vincular isso ao InteractionTracker. Comece configurando o InteractionTracker, o VisualInteractionSource e a Expressão que aproveitarão a posição do InteractionTracker.

// InteractionTracker and VisualInteractionSource setup.
_root = ElementCompositionPreview.GetElementVisual(Root);
_compositor = _root.Compositor;
_tracker = InteractionTracker.Create(_compositor);
_interactionSource = VisualInteractionSource.Create(_root);
_interactionSource.PositionYSourceMode = InteractionSourceMode.EnabledWithInertia;
_interactionSource.PositionYChainingMode = InteractionChainingMode.Always;
_tracker.InteractionSources.Add(_interactionSource);
float refreshPanelHeight = (float)RefreshPanel.ActualHeight;
_tracker.MaxPosition = new Vector3((float)Root.ActualWidth, 0, 0);
_tracker.MinPosition = new Vector3(-(float)Root.ActualWidth, -refreshPanelHeight, 0);

// Use the Tacker's Position (negated) to apply to the Offset of the Image.
// The -{refreshPanelHeight} is to hide the refresh panel
m_positionExpression = _compositor.CreateExpressionAnimation($"-tracker.Position.Y - {refreshPanelHeight} ");
m_positionExpression.SetReferenceParameter("tracker", _tracker);
_contentPanelVisual.StartAnimation("Offset.Y", m_positionExpression);

Com essa configuração, o painel de atualização está fora do visor em sua posição inicial, e tudo o que o usuário vê é a ListView. Quando o movimento panorâmico atinge o ContentPanel, o evento PointerPressed será acionado, momento em que se solicita ao sistema para usar o InteractionTracker para conduzir a experiência de manipulação.

private void Window_PointerPressed(object sender, PointerRoutedEventArgs e)
{
if (e.Pointer.PointerDeviceType == Windows.Devices.Input.PointerDeviceType.Touch) {
 // Tell the system to use the gestures from this pointer point (if it can).
 _interactionSource.TryRedirectForManipulation(e.GetCurrentPoint(null));
 }
}

Observação

Se não for necessário encadear eventos manipulados, a adição do manipulador PointerPressedEvent pode ser feita diretamente por meio da marcação XAML usando o atributo (PointerPressed="Window_PointerPressed").

A próxima etapa é configurar os modificadores de origem. Você usará dois modificadores de origem para obter esse comportamento; Resistência e Parada.

  • Resistência – mova o DeltaPosition.Y a metade da velocidade até atingir a altura do RefreshPanel.
CompositionConditionalValue resistanceModifier = CompositionConditionalValue.Create (_compositor);
ExpressionAnimation resistanceCondition = _compositor.CreateExpressionAnimation(
 $"-tracker.Position.Y < {pullToRefreshDistance}");
resistanceCondition.SetReferenceParameter("tracker", _tracker);
ExpressionAnimation resistanceAlternateValue = _compositor.CreateExpressionAnimation(
 "source.DeltaPosition.Y / 3");
resistanceAlternateValue.SetReferenceParameter("source", _interactionSource);
resistanceModifier.Condition = resistanceCondition;
resistanceModifier.Value = resistanceAlternateValue;
  • Parar – parar de se mover depois que o RefreshPanel inteiro estiver na tela.
CompositionConditionalValue stoppingModifier = CompositionConditionalValue.Create (_compositor);
ExpressionAnimation stoppingCondition = _compositor.CreateExpressionAnimation(
 $"-tracker.Position.Y >= {pullToRefreshDistance}");
stoppingCondition.SetReferenceParameter("tracker", _tracker);
ExpressionAnimation stoppingAlternateValue = _compositor.CreateExpressionAnimation("0");
stoppingModifier.Condition = stoppingCondition;
stoppingModifier.Value = stoppingAlternateValue;
Now add the 2 source modifiers to the InteractionTracker.
List<CompositionConditionalValue> modifierList = new List<CompositionConditionalValue>()
{ resistanceModifier, stoppingModifier };
_interactionSource.ConfigureDeltaPositionYModifiers(modifierList);

Este diagrama fornece uma visualização da instalação do SourceModifiers.

Diagrama de panorâmica

Agora, com os SourceModifiers, você observará que ao mover o ListView para baixo e alcançar o item mais superior, o painel de atualização é puxado para baixo a metade da velocidade do movimento até atingir a altura do RefreshPanel e, em seguida, para de se mover.

No exemplo completo, uma animação de quadro-chave é usada para girar um ícone durante a interação na tela RefreshPanel. Qualquer conteúdo pode ser usado em seu lugar ou utilizar a posição do InteractionTracker para conduzir essa animação separadamente.