Pull-to-refresh con modificatori di originePull-to-refresh with source modifiers

Questo articolo illustra in dettaglio come usare la funzionalità SourceModifier di InteractionTracker e come dimostrarne l'uso creando un controllo pull-to-refresh personalizzato.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.

PrerequisitiPrerequisites

In questo articolo si presuppone che l'utente abbia familiarità con i concetti illustrati in questi articoli:Here, we assume that you're familiar with the concepts discussed in these articles:

Che cos'è un SourceModifier e perché è utile?What is a SourceModifier and why are they useful?

Analogamente a InertiaModifiers, SourceModifiers fornisce un controllo più granulare sul movimento di un InteractionTracker.Like InertiaModifiers, SourceModifiers give you finer grain control over the motion of an InteractionTracker. Tuttavia, a differenza di InertiaModifiers che definiscono il movimento dopo che InteractionTracker entra nell'inerzia, SourceModifiers definisce il movimento mentre InteractionTracker è ancora nello stato interagisce.But unlike InertiaModifiers that define the motion after InteractionTracker enters inertia, SourceModifiers define the motion while InteractionTracker is still in its interacting state. In questi casi, si vuole un'esperienza diversa rispetto a quella tradizionale "attenersi al dito".In these cases, you want a different experience than the traditional "stick to the finger".

Un esempio classico è l'esperienza pull-to-refresh: quando l'utente estrae l'elenco per aggiornare il contenuto e le padelle dell'elenco alla stessa velocità del dito e si arresta dopo una certa distanza, il movimento risulterà brusco e meccanico.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. Un'esperienza più naturale consiste nel introdurre una sensazione di resistenza mentre l'utente interagisce attivamente con l'elenco.A more natural experience would be to introduce a feel of resistance while the user actively interacts with the list. Questa piccola sfumatura contribuisce a rendere l'esperienza complessiva dell'utente finale nell'interazione con un elenco più dinamico e accattivante.This small nuance helps make the overall end user experience of interacting with a list more dynamic and appealing. Nella sezione di esempio vengono illustrati in modo più dettagliato come compilare questa operazione.In the Example section, we go into more detail about how to build this.

Sono disponibili 2 tipi di modificatori di origine:There are 2 types of Source Modifiers:

  • DeltaPosition: Delta tra la posizione corrente del frame e la posizione del fotogramma precedente del dito durante l'interazione con il tocco.DeltaPosition – is the delta between the current frame position and the previous frame position of the finger during the touch pan interaction. Questo modificatore di origine consente di modificare la posizione Delta dell'interazione prima di inviarla per un'ulteriore elaborazione.This source modifier allows you to modify the delta position of the interaction before sending it for further processing. Si tratta di un parametro di tipo Vector3 e lo sviluppatore può scegliere di modificare gli attributi X o Y o Z della posizione prima di passarli 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: Delta tra la scala dei fotogrammi corrente e la scala dei fotogrammi precedente applicata durante l'interazione con lo zoom del tocco.DeltaScale - is the delta between the current frame scale and the previous frame scale that was applied during the touch zoom interaction. Questo modificatore di origine consente di modificare il livello di zoom dell'interazione.This source modifier allows you to modify the zooming level of the interaction. Si tratta di un attributo di tipo float che lo sviluppatore può modificare prima di passarlo a InteractionTracker.This is a float type attribute that the developer can modify before passing it to the InteractionTracker.

Quando InteractionTracker è nello stato interagisce, valuta ognuno dei modificatori di origine assegnati e determina se uno di essi viene applicato.When InteractionTracker is in its Interacting state, it evaluates each of the Source Modifiers assigned to it and determines if any of them apply. Ciò significa che è possibile creare e assegnare più modificatori di origine a un InteractionTracker.This means you can create and assign multiple Source Modifiers to an InteractionTracker. Tuttavia, quando si definiscono ogni, è necessario eseguire le operazioni seguenti:But, when defining each, you need to do the following:

  1. Definire la condizione, ovvero un'espressione che definisce l'istruzione condizionale quando deve essere applicato questo modificatore di origine specifico.Define the Condition – an Expression that defines the conditional statement when this specific Source Modifier should be applied.
  2. Definire DeltaPosition/DeltaScale: l'espressione del modificatore di origine che modifica DeltaPosition o DeltaScale quando viene soddisfatta la condizione definita sopra.Define the DeltaPosition/DeltaScale – The source modifier expression that alters the DeltaPosition or DeltaScale when the above defined condition is met.

EsempioExample

Si esaminerà ora come è possibile usare i modificatori di origine per creare un'esperienza di pull-aggiornamento personalizzata con un controllo ListView XAML esistente.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. Verrà usata un'area di disegno come "pannello di aggiornamento" che verrà impilata sopra un ListView XAML per compilare questa esperienza.We will be using a Canvas as the “Refresh Panel” that will be stacked on top of a XAML ListView to build this experience.

Per l'esperienza dell'utente finale, si vuole creare l'effetto della "resistenza" perché l'utente esegue attivamente la panoramica dell'elenco (con tocco) e si arresta la panoramica dopo che la posizione va oltre un determinato punto.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.

Elenco con pull-to-refresh

Il codice di lavoro per questa esperienza è disponibile nel repository di Windows UI dev Labs su GitHub.The working code for this experience can be found in the Window UI Dev Labs repo on GitHub. Ecco la procedura dettagliata per la creazione di questa esperienza.Here is the step-by-step walk through of building that experience. Nel codice markup XAML sono disponibili gli elementi seguenti: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>

Poiché ListView ( ThumbnailList ) è un controllo XAML che scorre già, è necessario che lo scorrimento venga concatenato al padre ( ContentPanel ) quando raggiunge l'elemento più in alto e non può più scorrere.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 è la posizione in cui verranno applicati i modificatori di origine). Per eseguire questa operazione, è necessario impostare ScrollViewer. IsVerticalScrollChainingEnabled su true nel markup 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. Sarà anche necessario impostare la modalità di concatenamento sul VisualInteractionSource su Always.You will also need to set the chaining mode on the VisualInteractionSource to Always.

È necessario impostare il gestore PointerPressedEvent con il parametro handledEventsToo su true.You need to set PointerPressedEvent handler with the handledEventsToo parameter as true. Senza questa opzione, PointerPressedEvent non verrà concatenato a ContentPanel poiché il controllo ListView contrassegna tali eventi come gestiti e non verranno inviati alla catena visiva.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);

A questo punto, è possibile associarlo a InteractionTracker.Now, you're ready to tie this with InteractionTracker. Per iniziare, impostare InteractionTracker, VisualInteractionSource e l'espressione che utilizzerà la posizione di 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 questa configurazione, il pannello di aggiornamento è fuori dal viewport nella posizione iniziale e tutti gli utenti vedono il listView quando il panning raggiunge il ContentPanel, viene generato l'evento PointerPressed, in cui si chiede al sistema di usare InteractionTracker per guidare l'esperienza di manipolazione.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

Se non è necessario concatenare gli eventi gestiti, l'aggiunta del gestore PointerPressedEvent può essere eseguita direttamente tramite il markup XAML usando l'attributo ( 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").

Il passaggio successivo consiste nell'impostare i modificatori di origine.The next step is to set up the source modifiers. Per ottenere questo comportamento, verranno utilizzati 2 modificatori di origine. Resistenza e arresto.You will be using 2 source modifiers to get this behavior; Resistance and Stop.

  • Resistenza: spostare DeltaPosition. Y alla metà della velocità fino a raggiungere l'altezza di 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;
  • Interrompi-Interrompi lo stato dopo l'intero RefreshPanel sullo schermo.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);

Questo diagramma fornisce una visualizzazione dell'installazione di SourceModifiers.This diagram gives a visualization of the SourceModifiers setup.

Diagramma Panoramica

A questo punto, con SourceModifiers, si noterà che quando si esegue il panning del controllo ListView verso il basso e si raggiunge l'elemento più in alto, il pannello di aggiornamento viene premuto a metà della velocità della padella fino a quando non raggiunge l'altezza RefreshPanel e quindi si interrompe lo spostamento.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.

Nell'esempio completo, un'animazione del fotogramma chiave viene usata per ruotare un'icona durante l'interazione nell'area di disegno RefreshPanel.In the full sample, a keyframe animation is used to spin an icon during interaction in the RefreshPanel canvas. Qualsiasi contenuto può essere usato al suo posto o usare la posizione di InteractionTracker per guidare l'animazione separatamente.Any content can be used in its place or utilize InteractionTracker’s position to drive that animation separately.