Поделиться через


Обновление по запросу с помощью модификаторов источника

В этой статье мы более подробно рассмотрим, как использовать функцию SourceModifier в InteractionTracker и продемонстрируем ее применение, создав пользовательский элемент управления "потяни, чтобы обновить".

Необходимые условия

Здесь предполагается, что вы знакомы с понятиями, описанными в следующих статьях:

Что такое SourceModifier и почему они полезны?

Как и инерционные модификаторы, SourceModifiers предоставляют более детальный контроль над движением InteractionTracker. Но в отличие от InertiaModifiers, которые определяют движение после того, как InteractionTracker входит в состояние инерции, SourceModifiers определяют движение, пока InteractionTracker находится в состоянии активного взаимодействия. В таких случаях вы хотите получить другое впечатление, нежели традиционное "прилипание к пальцам".

Классическим примером этого является опыт обновления списка движением - когда пользователь тянет список вниз, чтобы обновить содержимое, и список движется с той же скоростью, что и палец, а затем останавливается через определённое расстояние, движение кажется резким и механическим. Более естественным взаимодействием будет создать ощущение сопротивления, пока пользователь активно взаимодействует со списком. Этот маленький нюанс помогает сделать общий интерфейс пользователя взаимодействия со списком более динамичным и привлекательным. В разделе "Пример" мы подробно рассмотрим, как создать это.

Существует 2 типа модификаторов источника:

  • DeltaPosition — это разность между текущей позицией пальца и предыдущей позицией пальца во время взаимодействия касания с панорамированием. Этот модификатор источника позволяет изменить разностную позицию взаимодействия перед отправкой его для дальнейшей обработки. Это параметр типа Vector3, и разработчик может изменить любой из атрибутов позиции X или Y или Z, прежде чем передать его в InteractionTracker.
  • DeltaScale — это разностная разница между текущим масштабом кадра и предыдущим масштабом кадра, который был применен во время сенсорного масштабирования. Этот модификатор источника позволяет изменить уровень масштабирования взаимодействия. Это атрибут типа с плавающей запятой, который разработчик может изменить перед передачей в InteractionTracker.

Когда InteractionTracker находится в состоянии взаимодействия, он оценивает каждый из модификаторов источника, назначенных ему, и определяет, применяются ли какие-либо из них. Это означает, что вы можете создать и назначить несколько модификаторов источника элементу InteractionTracker. Но при определении каждого необходимо сделать следующее:

  1. Определите условие — выражение, определяющее условную инструкцию при применении этого конкретного модификатора источника.
  2. Определите deltaPosition/DeltaScale — исходное модификаторное выражение, которое изменяет DeltaPosition или DeltaScale при выполнении указанного выше условия.

Пример

Теперь давайте рассмотрим, как использовать модификаторы источника для создания пользовательского интерфейса по запросу к обновлению с помощью существующего элемента управления WinUI XAML ListView. Мы будем использовать холст в качестве панели обновления, которая будет размещена на вершине XAML ListView для создания этого интерфейса.

Для взаимодействия с конечным пользователем мы хотим создать эффект "сопротивления", когда пользователь активно листает список с помощью касания и прекращает движение, когда позиция выходит за пределы определенной точки.

Список с функцией «потянуть для обновления»

Рабочий код для этого интерфейса можно найти в репозитории Windows UI Dev Labs на GitHub. Вот пошаговые инструкции по созданию этого опыта. В коде разметки XAML есть следующее:

<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>

Так как ListView (ThumbnailList) — это элемент управления XAML, который уже прокручивается, прокрутка должна быть привязана к родительскому элементу (ContentPanel) при достижении самого верхнего элемента и больше не может прокручиваться. (ContentPanel — это место, где будут применяться модификаторы источника.) Для этого необходимо задать ScrollViewer.IsVerticalScrollChainingEnabled true в разметке ListView. Кроме того, необходимо задать режим цепочки в VisualInteractionSource на Always.

Необходимо задать обработчик PointerPressedEvent с параметром handledEventsToo, равным true. Без этого параметра pointerPressedEvent не будет привязан к ContentPanel, так как элемент управления ListView помечает эти события как обработанные, и они не будут отправляться вверх по визуальной цепочке.

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

Теперь вы готовы связать это с InteractionTracker. Начните с настройки InteractionTracker, VisualInteractionSource и выражения, которое будет использовать положение 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);

При этой настройке панель обновления в начальной позиции находится вне области просмотра, и пользователь видит только listView. Когда сдвиг достигает ContentPanel, будет сгенерировано событие PointerPressed, на котором вы запрашиваете у системы использование InteractionTracker для управления процессом манипуляции.

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

Замечание

Если события обработки цепочки не требуются, добавление обработчика PointerPressedEvent можно выполнить непосредственно с помощью разметки XAML с помощью атрибута (PointerPressed="Window_PointerPressed").

Следующим шагом является настройка модификаторов источника. Для получения этого поведения вы будете использовать 2 модификатора источника; Сопротивление и остановка.

  • Сопротивление — переместить DeltaPosition.Y на половину скорости, пока она не достигнет высоты 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;
  • Остановка — завершите перемещение, когда весь элемент RefreshPanel окажется на экране.
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);

На этой схеме показана визуализация установки SourceModifiers.

Схема панорамирования

Теперь с SourceModifiers вы заметите, что когда сдвигаете ListView вниз и достигаете самого верхнего элемента, панель обновления опускается с половинной скоростью прокрутки, пока она не достигнет высоты RefreshPanel, и затем прекращает движение.

В полном варианте используется анимация с ключевыми кадрами для вращения значка в процессе взаимодействия на холсте RefreshPanel. Любое содержимое можно использовать на месте или использовать положение InteractionTracker для управления анимацией отдельно.