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:
- Animações controladas por entrada
- Experiências personalizadas de manipulação com o InteractionTracker
- Animações baseadas em relações
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:
- Defina a Condição – uma Expressão que define a instrução condicional quando esse Modificador de Origem específico deve ser aplicado.
- 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.
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.
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.