Incorporación de cambios a la actualización con modificadores de origenPull-to-refresh with source modifiers

En este artículo, profundizaremos en el uso de la característica SourceModifier de InteractionTracker y demostrar su uso mediante la creación de un control personalizado de incorporación de cambios a la actualización.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.

PrerrequisitosPrerequisites

Aquí se supone que está familiarizado con los conceptos descritos en estos artículos:Here, we assume that you're familiar with the concepts discussed in these articles:

¿Qué es un SourceModifier y por qué son útiles?What is a SourceModifier and why are they useful?

Al igual que InertiaModifiers, SourceModifiers proporciona un control granular más preciso sobre el movimiento de una InteractionTracker.Like InertiaModifiers, SourceModifiers give you finer grain control over the motion of an InteractionTracker. Pero, a diferencia de InertiaModifiers, que define el movimiento después de que InteractionTracker entre en la inercia, SourceModifiers define el movimiento mientras InteractionTracker todavía está en su estado de interactuación.But unlike InertiaModifiers that define the motion after InteractionTracker enters inertia, SourceModifiers define the motion while InteractionTracker is still in its interacting state. En estos casos, desea una experiencia diferente a la del "palo" tradicional del dedo.In these cases, you want a different experience than the traditional "stick to the finger".

Un ejemplo clásico es la experiencia de extracción a actualización: cuando el usuario extrae la lista para actualizar el contenido y la lista se mueve a la misma velocidad que el dedo y se detiene después de cierta distancia, el movimiento se sentiría brusco y 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. Una experiencia más natural sería introducir una sensación de resistencia mientras el usuario interactúa activamente con la lista.A more natural experience would be to introduce a feel of resistance while the user actively interacts with the list. Este pequeño matiz ayuda a hacer que la experiencia global del usuario final interactúe con una lista más dinámica y atractiva.This small nuance helps make the overall end user experience of interacting with a list more dynamic and appealing. En la sección ejemplo, veremos con más detalle cómo crear esto.In the Example section, we go into more detail about how to build this.

Hay dos tipos de modificadores de origen:There are 2 types of Source Modifiers:

  • DeltaPosition: es la diferencia entre la posición del marco actual y la posición del marco anterior del dedo durante la interacción de la panorámica táctil.DeltaPosition – is the delta between the current frame position and the previous frame position of the finger during the touch pan interaction. Este modificador de origen permite modificar la posición Delta de la interacción antes de enviarla para su posterior procesamiento.This source modifier allows you to modify the delta position of the interaction before sending it for further processing. Se trata de un parámetro de tipo Vector3 y el desarrollador puede elegir modificar cualquiera de los atributos X o Y o Z de la posición antes de pasarlo a 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: es la diferencia entre la escala de fotogramas actual y la escala de fotograma anterior que se aplicó durante la interacción de zoom táctil.DeltaScale - is the delta between the current frame scale and the previous frame scale that was applied during the touch zoom interaction. Este modificador de origen le permite modificar el nivel de zoom de la interacción.This source modifier allows you to modify the zooming level of the interaction. Se trata de un atributo de tipo float que el desarrollador puede modificar antes de pasarlo a InteractionTracker.This is a float type attribute that the developer can modify before passing it to the InteractionTracker.

Cuando InteractionTracker está en su estado de interactuación, evalúa cada uno de los modificadores de origen asignados a él y determina si se aplica cualquiera de ellos.When InteractionTracker is in its Interacting state, it evaluates each of the Source Modifiers assigned to it and determines if any of them apply. Esto significa que puede crear y asignar varios modificadores de origen a un InteractionTracker.This means you can create and assign multiple Source Modifiers to an InteractionTracker. Sin embargo, al definir cada uno, debe hacer lo siguiente:But, when defining each, you need to do the following:

  1. Defina la condición: una expresión que define la instrucción condicional cuando se debe aplicar este modificador de origen específico.Define the Condition – an Expression that defines the conditional statement when this specific Source Modifier should be applied.
  2. Defina DeltaPosition/DeltaScale: expresión del modificador de origen que modifica DeltaPosition o DeltaScale cuando se cumple la condición definida anteriormente.Define the DeltaPosition/DeltaScale – The source modifier expression that alters the DeltaPosition or DeltaScale when the above defined condition is met.

EjemploExample

Ahora veamos cómo se pueden usar los modificadores de origen para crear una experiencia de extracción a actualización personalizada con un control ListView de 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 un lienzo como el "panel de actualización" que se apilará sobre un ListView XAML para compilar esta experiencia.We will be using a Canvas as the “Refresh Panel” that will be stacked on top of a XAML ListView to build this experience.

En el caso de la experiencia del usuario final, queremos crear el efecto de "resistencia", ya que el usuario está colocando de forma activa la lista (con funcionalidad táctil) y ha dejado el movimiento panorámico una vez que la posición va más allá de un punto determinado.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 con extracción para actualizar

El código de trabajo para esta experiencia puede encontrarse en el repositorio de desarrollo de la interfaz de usuario de Windows en github.The working code for this experience can be found in the Window UI Dev Labs repo on GitHub. Este es el tutorial paso a paso para crear esa experiencia.Here is the step-by-step walk through of building that experience. En el código de marcado XAML, tiene lo siguiente: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 ListView ( ThumbnailList ) es un control XAML que ya se desplaza, necesita que el desplazamiento se encadene hasta su elemento primario ( ContentPanel ) cuando alcanza el elemento de nivel superior y ya no se puede desplazar.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. (ContentPanel es donde se aplicarán los modificadores de origen). Para que esto suceda, debe establecer ScrollViewer. IsVerticalScrollChainingEnabled en true en el marcado de 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. También tendrá que establecer el modo de encadenamiento en el VisualInteractionSource en Always.You will also need to set the chaining mode on the VisualInteractionSource to Always.

Debe establecer el controlador PointerPressedEvent con el parámetro handledEventsToo como true.You need to set PointerPressedEvent handler with the handledEventsToo parameter as true. Sin esta opción, PointerPressedEvent no se encadenará a ContentPanel, ya que el control ListView marcará esos eventos como controlados y no se enviarán a la cadena 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);

Ahora ya está listo para asociarlo con InteractionTracker.Now, you're ready to tie this with InteractionTracker. Empiece por configurar InteractionTracker, VisualInteractionSource y la expresión que aprovechará la posición de 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);

Con esta configuración, el panel de actualización está fuera de la ventanilla en su posición de inicio y todo lo que ve el usuario es listView cuando el movimiento panorámico alcanza el valor de ContentPanel, se desencadena el evento PointerPressed, donde se pide al sistema que use InteractionTracker para impulsar la experiencia de manipulación.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));
 }
}

Nota

Si no es necesario encadenar eventos controlados, se puede Agregar el controlador PointerPressedEvent directamente a través del marcado XAML mediante el 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").

El siguiente paso consiste en configurar los modificadores de origen.The next step is to set up the source modifiers. Usará 2 modificadores de origen para obtener este comportamiento; Resistencia y detención.You will be using 2 source modifiers to get this behavior; Resistance and Stop.

  • Resistencia: mueva el DeltaPosition. Y a la mitad de la velocidad hasta que alcance el alto de la 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;
  • Detener: dejar de moverse después de que todo el RefreshPanel esté en la pantalla.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 proporciona una visualización de la configuración de SourceModifiers.This diagram gives a visualization of the SourceModifiers setup.

Diagrama de movimiento panorámico

Ahora con el SourceModifiers, observará que cuando se desplaza hacia abajo el control ListView y llega al elemento superior, el panel de actualización se extrae a la mitad del ritmo del pan hasta que alcanza el alto de RefreshPanel y, a continuación, se detiene.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.

En el ejemplo completo, se usa una animación de fotogramas clave para girar un icono durante la interacción en el lienzo RefreshPanel.In the full sample, a keyframe animation is used to spin an icon during interaction in the RefreshPanel canvas. Cualquier contenido puede usarse en su lugar o usar la posición de InteractionTracker para dirigir esa animación por separado.Any content can be used in its place or utilize InteractionTracker’s position to drive that animation separately.