Condividi tramite


Aggiornamento tramite trascinamento verso il basso con SourceModifier

Questo articolo illustra in dettaglio come usare una funzionalità SourceModifier di InteractionTracker e ne dimostra l'uso creando un controllo personalizzato da pull ad aggiornamento.

Prerequisiti

In questo caso si presuppone che si abbia familiarità con i concetti illustrati in questi articoli:

Cos'è sourceModifier e perché è utile?

Come i InertiaModifier, i SourceModifier danno un controllo più granulare sul movimento di un InteractionTracker. A differenza degli InertiaModifier che definiscono il movimento dopo che InteractionTracker entra in inerzia, i SourceModifier definiscono il movimento mentre InteractionTracker è ancora nello stato di interazione. In questi casi, si desidera un'esperienza diversa rispetto al tradizionale tocco con il dito.

Un esempio classico di questo è l'esperienza di aggiornamento pull-to-refresh: quando l'utente tira un elenco per aggiornare il contenuto e l'elenco esegue il movimento alla stessa velocità del dito e si ferma dopo una certa distanza, il movimento apparirà brusco e meccanico. Un'esperienza più naturale sarebbe introdurre una sensazione di resistenza mentre l'utente interagisce attivamente tirando l'elenco. Questa piccola differenza consente di rendere l'esperienza utente finale con gli elenchi più dinamica e accattivante. Nella sezione Esempio vengono fornite informazioni più dettagliate su come creare questa operazione.

Esistono 2 tipi di modificatori di origine:

  • DeltaPosition: è il delta tra la posizione del fotogramma corrente e la precedente posizione del fotogramma del dito nel corso della panoramica gestuale. Questo modificatore di origine consente di modificare la posizione differenziale dell'interazione prima di inviarlo a un'ulteriore elaborazione. Si tratta di un parametro di tipo Vector3 e lo sviluppatore può scegliere di modificare uno qualsiasi degli attributi X, Y o Z della posizione prima di passarlo all'InteractionTracker.
  • DeltaScale: è il delta tra la scala dei fotogrammi corrente e la scala dei fotogrammi precedente applicata durante l'interazione con lo zoom gestuale. Questo modificatore di origine consente di modificare il livello di zoom dell'interazione. Si tratta di un attributo di tipo float che lo sviluppatore può modificare prima di passarlo a InteractionTracker.

Quando InteractionTracker è nello stato di interazione, valutare ognuno dei modificatori di origine assegnati e determinare se uno di essi si applica. Ciò significa che è possibile creare e assegnare più modificatori di origine a un InteractionTracker. Tuttavia, quando si definiscono singolarmente è necessario eseguire le operazioni seguenti:

  1. Definire la condizione: Expression che definisce l'istruzione condizionale quando deve essere applicato questo specifico modificatore di origine.
  2. Definire DeltaPosition/DeltaScale: Expression del modificatore di origine che modifica DeltaPosition o DeltaScale quando viene soddisfatta la condizione definita in precedenza.

Esempio

Ora esaminiamo come usare i modificatori di origine per creare un'esperienza di aggiornamento pull-to-refresh personalizzata con un controllo ListView XAML esistente. Verrà usata un'area di disegno come "pannello di aggiornamento" che verrà sovrapposto a un controllo ListView XAML per creare questa esperienza.

Per l'esperienza dell'utente finale, si desidera creare l'effetto di "resistenza" perché l'utente sta eseguendo attivamente la panoramica dell'elenco (con tocco) e si desidera interrompere la panoramica dopo che la posizione oltrepassa un determinato punto.

Elenco con aggiornamento pull-to-refresh

Il codice per questa esperienza è disponibile nel repository Window UI Dev Labs su GitHub. Ecco la procedura dettagliata per la creazione di questa esperienza. Nel codice di markup XAML sono disponibili gli elementi seguenti:

<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 già scorre, è necessario che lo scorrimento venga concatenato fino al relativo elemento padre (ContentPanel) quando raggiunge l'elemento più in alto e non può più scorrere. (ContentPanel è la posizione in cui si applicheranno i modificatori di origine). Affinché ciò accada, è necessario impostare ScrollViewer.IsVerticalScrollChainingEnabled su true nel markup ListView. Sarà anche necessario impostare la modalità di concatenamento in VisualInteractionSource su Always.

È necessario impostare il gestore PointerPressedEvent con il parametro handledEventsToo su true. Senza questa opzione, PointerPressedEvent non verrà concatenato a ContentPanel perché il controllo ListView contrassegnerà tali eventi come gestiti e non verranno inviati alla catena visiva.

//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);

Ora sarà possibile collegarlo con InteractionTracker. Iniziare configurando InteractionTracker, VisualInteractionSource e la Expression che userà la posizione di 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 esce dal riquadro di visualizzazione nella posizione iniziale e l'utente vede solo listView. Quando la panoramica raggiunge ContentPanel, verrà generato l'evento PointerPressed, in cui si chiede al sistema di usare InteractionTracker per guidare l'esperienza di manipolazione.

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

Il prossimo passaggio è la configurazione dei modificatori. Si usano 2 modificatori di origine per ottenere questo comportamento, Resistance e Stop.

  • Resistance: spostare DeltaPosition.Y a metà velocità fino a raggiungere l'altezza di 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;
  • Stop: interrompere il movimento dopo che l'intero oggetto RefreshPanel è sullo schermo.
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 offre una visualizzazione della configurazione di SourceModifier.

Diagramma di panoramica

Ora con SourceModifier, si noterà quando si esegue la panoramica di ListView verso il basso e si raggiunge l'elemento più in alto, il pannello di aggiornamento viene trascinato verso il basso a metà del ritmo della panoramica fino a raggiungere l'altezza di RefreshPanel e quindi interrompe lo spostamento.

Nell'esempio completo viene usata un'animazione basata su fotogrammi chiave per ruotare un'icona durante l'interazione nell'area di disegno RefreshPanel. Qualsiasi contenuto può essere usato al suo posto oppure è possibile utilizzare la posizione di InteractionTracker per guidare l'animazione separatamente.