Extracción para actualizar con modificadores de origen
En este artículo, se profundiza en cómo usar la característica SourceModifier de InteractionTracker y se muestra su uso mediante la creación de un control personalizado de extracción para actualizar.
Requisitos previos
En este caso, se supone que está familiarizado con los conceptos descritos en estos artículos:
- Animaciones controladas por entradas
- Experiencias de manipulación personalizadas con InteractionTracker
- Animaciones basadas en relaciones
¿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 Las inercias que definen el movimiento después de que InteractionTracker entra en la inercia, 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 extracción para actualizar: cuando el usuario extrae la lista para actualizar el contenido y los paneles de lista a la misma velocidad que el dedo y se detiene después de una cierta distancia, el movimiento se sentirí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, vamos a obtener más detalles sobre cómo compilar 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:
- Definir la condición: expresión que define la instrucción condicional cuando se debe aplicar este modificador de origen específico.
- 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 origen para crear una experiencia personalizada de extracción para actualizar con un control ListView XAML 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" ya que el usuario está desplazando activamente la lista (con entrada táctil) y detener el movimiento panorámico después de que la posición supere un punto determinado.
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 establecer ScrollViewer.IsVerticalScrollChainingEnabled en true en el marcado 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á listo para vincularlo 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 de la ventanilla en su posición inicial y todo lo que ve el usuario es listView Cuando el movimiento panorámico llega al ContentPanel, se activará el evento PointerPressed, donde se pedirá al sistema que use InteractionTracker para impulsar la experiencia de 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 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.
Ahora con SourceModifiers, observarás al desplazar el control panorámico de ListView hacia abajo y llegar al elemento superior, el panel de actualización se extrae a la mitad del ritmo del panel hasta que alcanza el alto refreshPanel 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. Cualquier contenido se puede usar en su lugar o utilizar la posición de InteractionTracker para impulsar esa animación por separado.