Compartir a través de


Deslizar para actualizar con modificadores de fuente

En este artículo, profundizamos en cómo utilizar la función SourceModifier de InteractionTracker y demostramos su aplicación creando un control personalizado de deslizar para actualizar.

Prerrequisitos

Aquí se supone que está familiarizado con los conceptos descritos en estos artículos:

¿Qué es sourceModifier y por qué son útiles?

Al igual que InerciaModifiers, SourceModifiers proporciona un control más preciso sobre el movimiento de un InteractionTracker. Sin embargo, a diferencia de los InertiaModifiers que definen el movimiento después de que InteractionTracker entra en estado de inercia, los SourceModifiers definen el movimiento mientras InteractionTracker sigue en su estado de interacción. En estos casos, quieres una experiencia diferente a la tradicional "pega al dedo".

Un ejemplo clásico de esto es la experiencia de tirar para refrescar: cuando el usuario tira de la lista para actualizar el contenido y la lista se desplaza a la misma velocidad del dedo y se detiene tras recorrer cierta distancia, el movimiento resultaría abrupto y mecánico. Una experiencia más natural sería introducir una sensación de resistencia mientras el usuario interactúa activamente con la lista. Este pequeño matiz ayuda a que la experiencia general del usuario final interactúe con una lista más dinámica y atractiva. En la sección Ejemplo, profundizamos sobre cómo construir esto.

Hay dos tipos de modificadores de origen:

  • DeltaPosition: es el delta entre la posición actual del marco y la posición del marco anterior del dedo durante la interacción del panel táctil. Este modificador de origen permite modificar la posición diferencial de la interacción antes de enviarlo para su posterior procesamiento. 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.
  • DeltaScale: es la diferencia entre la escala de fotogramas actual y la escala de fotogramas anterior que se aplicó durante la interacción del zoom táctil. Este modificador de origen permite modificar el nivel de zoom de la interacción. Se trata de un atributo de tipo float que el desarrollador puede modificar antes de pasarlo a InteractionTracker.

Cuando InteractionTracker está en su estado de interacción, evalúa cada uno de los modificadores de origen asignados a él y determina si se aplica alguno de ellos. Esto significa que puede crear y asignar varios modificadores de origen a interactionTracker. Pero, al definir cada uno, debe hacer lo siguiente:

  1. Definir la condición: expresión que define la instrucción condicional cuando se debe aplicar este modificador de origen específico.
  2. Definir DeltaPosition/DeltaScale: expresión modificadora de origen que modifica DeltaPosition o DeltaScale cuando se cumple la condición definida anteriormente.

Ejemplo

Ahora echemos un vistazo a cómo puedes usar modificadores de código fuente para crear una experiencia personalizada de extracción para actualizar con un control ListView XAML de WinUI existente. Usaremos un canvas como el "Panel de actualización" que se apilará encima de un control ListView XAML para crear esta experiencia.

Para la experiencia del usuario final, queremos crear el efecto de "resistencia" cuando el usuario esté desplazando activamente la lista (con entrada táctil) y dejar de desplazar después de que la posición supere un punto determinado.

Lista con deslizar para actualizar

El código de trabajo de esta experiencia se puede encontrar en el repositorio de Windows UI Dev Labs en GitHub. Este es el tutorial paso a paso de la creación de esa experiencia. En el código de marcado XAML, tienes lo siguiente:

<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>

Dado que ListView (ThumbnailList) es un control XAML que ya se desplaza, necesitas que el desplazamiento se encadene a su elemento primario (ContentPanel) cuando llegue al elemento superior y ya no se pueda desplazar. (ContentPanel es donde se aplicarán los modificadores de origen). Para que esto suceda, debe configurar ScrollViewer.IsVerticalScrollChainingEnabled en true en el marcado de ListView. También deberá establecer el modo de encadenamiento en VisualInteractionSource en Always.

Debe establecer el controlador PointerPressedEvent con el parámetro handledEventsToo como 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.

//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ás listo para conectar esto con InteractionTracker. Comience configurando InteractionTracker, VisualInteractionSource y expression que aprovecharán la posición de 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 del viewport en su posición inicial, y lo único que ve el usuario es el ListView. Cuando el desplazamiento alcance el ContentPanel, se activará el evento PointerPressed, donde se solicita al sistema que utilice InteractionTracker para potenciar la experiencia de interacción y manipulación.

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").

El siguiente paso consiste en configurar los modificadores de origen. Usará 2 modificadores de origen para obtener este comportamiento; Resistencia y parada.

  • Resistencia: mueva deltaPosition.Y a la mitad de la velocidad hasta que alcance el alto de 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: Deje de moverse después de que todo el RefreshPanel esté en la pantalla.
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.

Diagrama panorámico

Ahora, con los SourceModifiers, observarás que al desplazar hacia abajo el ListView y llegar al elemento más alto, el panel de actualización se desplaza hacia abajo a la mitad de la velocidad del movimiento de desplazamiento hasta que alcanza la altura del panel de actualización y luego deja de moverse.

En el ejemplo completo, se usa una animación de fotograma clave para girar un icono durante la interacción en el lienzo RefreshPanel. Se puede utilizar cualquier contenido en su lugar o usar la posición de InteractionTracker para controlar esa animación de forma independiente.