Condividi tramite


Aggiornamento con scorrimento verso il basso e modificatori di fonte

Questo articolo illustra in dettaglio come usare la caratteristica SourceModifier di InteractionTracker e illustrarne l'uso creando un controllo personalizzato di trascina-per-aggiornare.

Prerequisiti

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

Che cos'è sourceModifier e perché sono utili?

Come InertiaModifiers, SourceModifiers ti dà un controllo più granulare sul movimento di un InteractionTracker. A differenza di InertiaModifiers che definiscono il movimento dopo che InteractionTracker entra in inerzia, SourceModifiers definisce il movimento mentre InteractionTracker è ancora nello stato di interazione. In questi casi, si vuole un'esperienza diversa rispetto al tradizionale "bastone al dito".

Un esempio classico di questo fenomeno è l'esperienza di "pull-to-refresh": quando l'utente tira l'elenco per aggiornare i contenuti e l'elenco si muove alla stessa velocità del dito per poi fermarsi dopo una certa distanza, il movimento sembrerebbe brusco e meccanico. Un'esperienza più naturale sarebbe introdurre una sensazione di resistenza mentre l'utente interagisce attivamente con l'elenco. Questa piccola sfumatura consente di rendere l'esperienza utente finale complessiva di interagire con un elenco più dinamico 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 posizione precedente del fotogramma del dito durante l'interazione tramite panoramica del tocco. Questo modificatore di origine consente di modificare la posizione differenziale dell'interazione prima di inviarlo per un'ulteriore elaborazione. Si tratta di un parametro di tipo Vector3 e lo sviluppatore può scegliere di modificare uno qualsiasi degli attributi X o Y o Z della posizione prima di passarlo all'InteractionTracker.
  • DeltaScale: è la differenza tra la scala del fotogramma attuale e quella del fotogramma precedente applicata durante l'interazione di zoom tattile. 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, valuta ognuno dei modificatori di origine assegnati e determina se uno di essi si applica. Ciò significa che è possibile creare e assegnare più modificatori di origine a un InteractionTracker. Tuttavia, quando si definiscono ognuna di esse, è necessario eseguire le operazioni seguenti:

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

Esempio

Vediamo ora come usare i modificatori di origine per creare un'esperienza di aggiornamento pull-to-refresh personalizzata con un controllo ListView XAML WinUI esistente. Verrà usato un Canvas come "Pannello di Aggiornamento" che verrà sovrapposto a un controllo ListView XAML per creare questa esperienza.

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

Elenco con funzione pull-to-refresh

Il codice di lavoro per questa esperienza è disponibile nel repository Window UI Dev Labs in GitHub. Ecco la procedura dettagliata per la creazione di tale 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 applicherete 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 come 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);

A questo momento, è possibile collegarlo con InteractionTracker. Per iniziare, configurare InteractionTracker, VisualInteractionSource e 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 si trova fuori dal riquadro di visualizzazione nella posizione iniziale e tutto ciò che l'utente vede è il "listView". Quando lo scorrimento raggiunge il "ContentPanel", verrà generato l'evento PointerPressed, in cui si chiede al sistema di usare InteractionTracker per gestire 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));
 }
}

Annotazioni

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 passaggio successivo consiste nel configurare i modificatori di origine. Si usano 2 modificatori di origine per ottenere questo comportamento; Resistenza e stop.

  • Resistenza: spostare DeltaPosition.Y a metà della 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;
  • Arresta: ferma il movimento dopo che l'intero pannello 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 dell'installazione di SourceModifiers.

Diagramma di panoramica

Ora con SourceModifiers, 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 o utilizzare la posizione di InteractionTracker per guidare l'animazione separatamente.