Compartilhar via


Puxar para atualizar com modificadores de origem

Neste artigo, nos aprofundamos em como usar o recurso SourceModifier de um InteractionTracker e demonstramos seu uso criando um controle personalizado de puxar para atualizar.

Pré-requisitos

Aqui, presumimos que você esteja familiarizado com os conceitos abordados neste artigo:

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

Como InertiaModifiers, SourceModifiers oferece controle mais refinado sobre o movimento de um InteractionTracker. Mas, ao contrário dos InertiaModifiers que definem o movimento depois que o InteractionTracker entra na inércia, os SourceModifiers definem o movimento enquanto o InteractionTracker ainda está em seu estado de interação. Nesses casos, você quer uma experiência diferente do tradicional "stick to the finger".

Um exemplo clássico disso é a experiência de puxar para atualizar - quando o usuário puxa a lista para atualizar o conteúdo e a lista se move 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. Na seção Exemplo, entramos em mais detalhes sobre como construir isso.

Existem 2 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 panorâmica de toque. Esse modificador de origem permite que você modifique a posição delta da interação antes de enviá-la para processamento adicional. Esse é um parâmetro do tipo Vector3 e o desenvolvedor pode optar por modificar qualquer um dos atributos X, Y ou Z da posição antes de passá-lo para o InteractionTracker.
  • DeltaScale - é o delta entre a escala de quadros atual e a escala de quadros anterior que foi aplicada durante a interação de zoom por toque. Esse modificador de origem permite que você modifique 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 o 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. Defina a Condição – uma Expressão que define a instrução condicional quando esse Modificador de Origem específico deve ser aplicado.
  2. Definir o DeltaPosition/DeltaScale – a expressão modificadora de origem que altera o DeltaPosition ou o DeltaScale quando a condição definida acima é atendida.

Exemplo

Agora vamos ver como você pode usar Modificadores de Origem para criar uma experiência personalizada de puxar para atualizar com um Controle ListView XAML 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", pois o usuário está movendo ativamente a lista (com toque) e parar de fazer o movimento panorâmico depois que a posição ultrapassar um determinado ponto.

Lista com pull-to-refresh

O código de trabalho para essa experiência pode ser encontrado no repositório do Windows UI Dev Labs no GitHub. Aqui está o passo a passo da construção dessa 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, você precisa que a rolagem seja encadeada até seu pai (ContentPanel) quando atingir o item mais alto e não puder mais rolar. (ContentPanel é onde você aplicará os modificadores de origem.) Para que isso aconteça, 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 cima na 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ê é o listView Quando o movimento panorâmico atinge o ContentPanel, o evento PointerPressed será disparado, onde você solicita que o sistema use 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 o encadeamento de eventos Handled não for necessário, a adição do manipulador PointerPressedEvent poderá 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á 2 modificadores de origem para obter esse comportamento; Resistência e parada.

  • Resistência – Mova o DeltaPosition.Y na 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 – Pare de se mover depois que todo o RefreshPanel 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 configuração de SourceModifiers.

Diagrama de movimento panorâmico

Agora, com os SourceModifiers, você notará que, ao deslocar o ListView para baixo e alcançar o item superior, o painel de atualização é puxado para baixo na metade do ritmo do movimento panorâmico 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.