Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
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:
- Animaciones controladas por entrada
- 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 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, se busca una experiencia diferente a la tradicional de pegar con el 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 la diferencia entre la posición del dedo en el fotograma actual y la posición del dedo en el fotograma anterior durante la interacción de desplazamiento 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 del fotograma actual y la escala del fotograma anterior que se aplicó durante la interacción de 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 veamos cómo se pueden utilizar los modificadores de fuente para crear una experiencia personalizada de deslizar para actualizar con un control ListView de XAML de WinUI ya existente. Utilizaremos un Canvas como RefreshPanel, que se superpondrá a un ListView de 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.
El ejemplo PullToRefresh contiene el código de trabajo de esta experiencia. La siguiente guía explica cómo crearlo. 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: mueve el valor DeltaPosition.Y a la mitad de la velocidad hasta que alcance la altura del 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;
- Parada: deja de moverlo una vez que todo el RefreshPanel esté en 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 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.