Puxar para atualizar com modificadores de origemPull-to-refresh with source modifiers

Neste artigo, abordaremos detalhadamente como usar o recurso SourceModifier de um InteractionTracker e demonstrar seu uso através da criação de um controle personalizado de puxar para atualizar.In this article, we take a deeper dive into how to use an InteractionTracker’s SourceModifier feature and demonstrate its use by creating a custom pull-to-refresh control.

Pré-requisitosPrerequisites

Aqui, presumimos que você esteja familiarizado com os conceitos abordados neste artigo:Here, we assume that you're familiar with the concepts discussed in these articles:

O que é um SourceModifier e por que eles são úteis?What is a SourceModifier and why are they useful?

Assim como os InertiaModifiers, os SourceModifiers proporcionam um controle mais refinado sobre o movimento de um InteractionTracker.Like InertiaModifiers, SourceModifiers give you finer grain control over the motion of an InteractionTracker. Mas, diferente dos InertiaModifiers, que definem o movimento depois que o InteractionTracker entra em inércia, os SourceModifiers definem o movimento enquanto o InteractionTracker ainda está no estado de interação.But unlike InertiaModifiers that define the motion after InteractionTracker enters inertia, SourceModifiers define the motion while InteractionTracker is still in its interacting state. Nesses casos, você deseja uma experiência diferente do tradicional "controle pelos dedos".In these cases, you want a different experience than the traditional "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 aparece na mesma velocidade do dedo e para após determinada distância, o movimento parece abrupto e mecânico.A classic example of this is the pull-to-refresh experience - when the user pulls the list to refresh the contents and the list pans at the same speed as the finger and stops after a certain distance, the motion would feel abrupt and mechanical. A experiência mais natural seria apresentar uma sensação de resistência enquanto o usuário interage ativamente com a lista.A more natural experience would be to introduce a feel of resistance while the user actively interacts with the list. Essa pequena diferença torna a experiência geral do usuário final na interação com uma lista mais dinâmica e atrativa.This small nuance helps make the overall end user experience of interacting with a list more dynamic and appealing. Na seção de exemplo, explicaremos em mais detalhes como desenvolver isso.In the Example section, we go into more detail about how to build this.

Existem dois tipos de modificadores de origem:There are 2 types of Source Modifiers:

  • DeltaPosition – é o delta entre a posição atual do quadro e a posição anterior do quadro no dedo durante a interação da panorâmico de toque.DeltaPosition – is the delta between the current frame position and the previous frame position of the finger during the touch pan interaction. Esse modificador de origem permite modificar a posição delta da interação antes de enviá-lo para processamento posterior.This source modifier allows you to modify the delta position of the interaction before sending it for further processing. Este é 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.This is a Vector3 type parameter and the developer can choose to modify any of the X or Y or Z attributes of the position before passing it to the InteractionTracker.
  • DeltaScale - é o delta entre a escala atual do quadro e a escala anterior do quadro aplicada durante a interação do zoom de toque.DeltaScale - is the delta between the current frame scale and the previous frame scale that was applied during the touch zoom interaction. Esse modificador de origem permite modificar o nível de zoom da interação.This source modifier allows you to modify the zooming level of the interaction. Este é um atributo do tipo float que o desenvolvedor pode modificar antes de passá-lo para o InteractionTracker.This is a float type attribute that the developer can modify before passing it to the InteractionTracker.

Quando o InteractionTracker estiver no estado de interação, ele avaliará cada um dos modificadores de origem atribuídos a ele e determinará se algum deles é aplicável.When InteractionTracker is in its Interacting state, it evaluates each of the Source Modifiers assigned to it and determines if any of them apply. Isso significa que você pode criar e atribuir vários modificadores de origem a um InteractionTracker.This means you can create and assign multiple Source Modifiers to an InteractionTracker. Mas, ao definir cada um, você precisará fazer o seguinte:But, when defining each, you need to do the following:

  1. Definir a condição – uma expressão que define a instrução condicional quando este modificador de origem precisar ser aplicado.Define the Condition – an Expression that defines the conditional statement when this specific Source Modifier should be applied.
  2. Definir DeltaPosition/DeltaScale – a expressão de modificador de origem que altera a DeltaPosition ou a DeltaScale quando a condição definida acima é atendida.Define the DeltaPosition/DeltaScale – The source modifier expression that alters the DeltaPosition or DeltaScale when the above defined condition is met.

ExemploExample

Agora vamos ver como você pode usar os modificadores de origem para criar uma experiência personalizada de puxar para atualizar com um controle ListView XAML existente.Now let’s look at how you can use Source Modifiers to create a custom pull-to-refresh experience with an existing XAML ListView Control. Usaremos uma Tela como "Painel de Atualização", que será empilhada sobre um ListView XAML para criar essa experiência.We will be using a Canvas as the “Refresh Panel” that will be stacked on top of a XAML ListView to build this experience.

Para a experiência de usuário final, queremos criar o efeito de "resistência", pois o usuário está fazendo ativamente a panorâmica da lista (com o toque) e interrompendo a panorâmica quando a posição ultrapassa um determinado ponto.For the end user experience, we want to create the effect of "resistance" as the user is actively panning the list (with touch) and stop panning after the position goes beyond a certain point.

Lista com puxar para atualizar

O código de trabalho dessa experiência pode ser encontrado no repositório de laboratórios de desenvolvimento de interface do usuário do Windows no GitHub.The working code for this experience can be found in the Window UI Dev Labs repo on GitHub. Esta é a instrução passo a passo de como criar essa experiência.Here is the step-by-step walk through of building that experience. No código de marcação XAML, você terá o seguinte:In your XAML markup code, you have the following:

<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 a ListView (ThumbnailList) é um controle XAML que já faz a rolagem, você precisará que a rolagem seja encadeada até o item pai (ContentPanel) quando esta atingir o primeiro item e não puder mais avançar a rolagem.Because the ListView (ThumbnailList) is a XAML control that already scrolls, you need the scrolling to be chained up to its parent (ContentPanel) when it reaches the topmost item and can’t scroll anymore. (É no ContentPanel que você aplicará os modificadores de origem). Para que isso aconteça, é necessário definir ScrollViewer.IsVerticalScrollChainingEnabled como true na marcação ListView.(ContentPanel is where you will apply the Source Modifiers.) For this to happen you need to set ScrollViewer.IsVerticalScrollChainingEnabled to true in the ListView markup. Você também precisará definir o modo de encadeamento no VisualInteractionSource para Always.You will also need to set the chaining mode on the VisualInteractionSource to Always.

Você precisa definir o manipulador PointerPressedEvent com o parâmetro handledEventsToo como true.You need to set PointerPressedEvent handler with the handledEventsToo parameter as true. Sem essa opção, o PointerPressedEvent não será encadeado até o ContentPanel, pois o controle ListView marcará esses eventos como manipulados e eles não serão enviados à cadeia visual.Without this option, the PointerPressedEvent will not be chained to the ContentPanel as the ListView control will mark those events as handled and they will not be sent up the visual chain.

//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.Now, you're ready to tie this with InteractionTracker. Comece configurando o InteractionTracker, o VisualInteractionSource e a expressão que aproveitará a posição do InteractionTracker.Start by setting up InteractionTracker, the VisualInteractionSource and the Expression that will leverage the position of 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 isso configurado, o painel de atualização ficará fora do visor em sua posição inicial e o usuário verá somente o listView. Quando a panorâmica atingir o ContentPanel, o evento PointerPressed será acionado, momento em que você solicitará que o sistema use o InteractionTracker para acionar a experiência de manipulação.With this set up, the refresh panel is out of the viewport in its starting position and all the user sees is the listView When the panning reaches the ContentPanel, the PointerPressed event will be fired, where you ask the System to use InteractionTracker to drive the manipulation experience.

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 os eventos Handled, a adição de PointerPressedEvent poderá ser feita diretamente através da marcação XAML usando o atributo (PointerPressed="Window_PointerPressed").If chaining Handled events is not needed, adding PointerPressedEvent handler can be done directly through XAML markup using the attribute (PointerPressed="Window_PointerPressed").

A próxima etapa é configurar os modificadores de origem.The next step is to set up the source modifiers. Você usará dois modificadores de origem para obter esse comportamento: Resistance e Stop.You will be using 2 source modifiers to get this behavior; Resistance and Stop.

  • Resistance – Move a DeltaPosition.Y na metade da velocidade até atingir a altura do RefreshPanel.Resistance – Move the DeltaPosition.Y at half the speed until it reaches the height of the 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;
  • Stop – Interromperá a movimentação quando todo o RefreshPanel estiver na tela.Stop – Stop moving after the entire RefreshPanel is on the screen.
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 oferece uma visualização da configuração dos SourceModifiers.This diagram gives a visualization of the SourceModifiers setup.

Diagrama de panorâmica

Agora, com os SourceModifiers, você perceberá que, ao fazer a panorâmica do ListView para baixo e atingir o primeiro item, o painel de atualização será puxado a uma velocidade equivalente à metade da velocidade da panorâmica, até atingir a altura do RefreshPanel, quando ele será interrompido.Now with the SourceModifiers, you will notice when panning the ListView down and reach the topmost item, the refresh panel is pulled down at half the pace of the pan until it reaches the RefreshPanel height and then stops moving.

No exemplo completo, uma animação de quadro chave é usada para girar um ícone durante a interação na tela do RefreshPanel.In the full sample, a keyframe animation is used to spin an icon during interaction in the RefreshPanel canvas. Qualquer conteúdo pode ser usado em seu lugar ou utilize a posição do InteractionTracker para acionar essa animação separadamente.Any content can be used in its place or utilize InteractionTracker’s position to drive that animation separately.