Обновление путем оттягивания с модификаторами источникаPull-to-refresh with source modifiers

В этой статье более подробно рассматривается использование функции SourceModifier в составе InteractionTracker и демонстрируется ее использование путем создания пользовательского элемента управления обновлением путем оттягивания.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.

Предварительные требованияPrerequisites

Подразумевается, что вы знакомы с понятиями, которые рассматриваются в этих статьях:Here, we assume that you're familiar with the concepts discussed in these articles:

Что такое SourceModifier и в чем польза этих элементов?What is a SourceModifier and why are they useful?

Как и модификаторы InertiaModifiers, SourceModifiers обеспечивают более точный контроль над перемещением InteractionTracker.Like InertiaModifiers, SourceModifiers give you finer grain control over the motion of an InteractionTracker. Однако в отличие от InertiaModifiers, определяющих движение после перехода InteractionTracker в состояние инерции, SourceModifiers определяют движение InteractionTracker в состоянии взаимодействия.But unlike InertiaModifiers that define the motion after InteractionTracker enters inertia, SourceModifiers define the motion while InteractionTracker is still in its interacting state. В этих случаях требуется взаимодействие, отличное от традиционного "прилипания к пальцу",In these cases, you want a different experience than the traditional "stick to the finger".

Классический пример этого — обновление путем оттягивания, когда пользователь оттягивает список, чтобы обновить его содержимое, и список сдвигается с той же скоростью, что и палец, и останавливается, пройдя определенное расстояние. Такое движение выглядит резким и механическим.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. Гораздо более естественное взаимодействие — это ощущение сопротивления, когда список активно взаимодействует со списком.A more natural experience would be to introduce a feel of resistance while the user actively interacts with the list. Этот небольшой нюанс позволяет сделать взаимодействие конечного пользователя со списком более динамическим и привлекательным.This small nuance helps make the overall end user experience of interacting with a list more dynamic and appealing. В разделе "Пример" более подробно рассматривается построение этого взаимодействия.In the Example section, we go into more detail about how to build this.

Существует два типа модификаторов источника:There are 2 types of Source Modifiers:

  • DeltaPosition — это разница между текущим и предыдущим положением кадра для пальца во время взаимодействия "сдвиг касанием".DeltaPosition – is the delta between the current frame position and the previous frame position of the finger during the touch pan interaction. Этот модификатор источника позволяет модифицировать дельта-положение взаимодействия перед отправкой взаимодействия для дальнейшей обработки.This source modifier allows you to modify the delta position of the interaction before sending it for further processing. Это параметр типа Vector3, и разработчик может изменить любые атрибуты (X, Y или Z) положения, прежде чем передавать его 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 — это разница между текущим и предыдущим масштабом кадра, которая применялась во время взаимодействия "масштабирование касанием".DeltaScale - is the delta between the current frame scale and the previous frame scale that was applied during the touch zoom interaction. Этот модификатор источника позволяет изменять уровень масштаба взаимодействия.This source modifier allows you to modify the zooming level of the interaction. Это атрибут плавающего типа, который разработчик может изменить, прежде чем передавать в InteractionTracker.This is a float type attribute that the developer can modify before passing it to the InteractionTracker.

Если InteractionTracker находится в состоянии взаимодействия, он оценивает все назначенные ему модификаторы источника и определяет, применяются ли какие-либо из них.When InteractionTracker is in its Interacting state, it evaluates each of the Source Modifiers assigned to it and determines if any of them apply. Это означает, что модулю InteractionTracker можно назначить несколько созданных модификаторов источника.This means you can create and assign multiple Source Modifiers to an InteractionTracker. Однако при определении каждого из них необходимо сделать следующее:But, when defining each, you need to do the following:

  1. Определите состояние — выражение, которое определяет условный оператор, при котором должен применяться конкретный модификатор источника.Define the Condition – an Expression that defines the conditional statement when this specific Source Modifier should be applied.
  2. Определите параметр DeltaPosition/DeltaScale — выражение модификатора источника, которое меняет параметр DeltaPosition или DeltaScale, если определенное выше условие выполнено.Define the DeltaPosition/DeltaScale – The source modifier expression that alters the DeltaPosition or DeltaScale when the above defined condition is met.

ПримерExample

Посмотрим, как можно использовать модификаторы источника, чтобы создать пользовательское взаимодействие "обновление путем оттягивания" с существующим элементом управления XAML ListView.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. Для создания этого взаимодействия мы будем использовать холст в качестве панели обновления, которая будет располагаться над элементом XAML ListView.We will be using a Canvas as the “Refresh Panel” that will be stacked on top of a XAML ListView to build this experience.

Для конечного пользователя мы хотим создать взаимодействие с эффектом "сопротивления": пользователь активно сдвигает список касанием и перестает сдвигать его после того, как положение списка пересечет определенную точку.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.

Список с обновлением путем оттягивания

Рабочий код для этого взаимодействия можно найти в репозитории лабораторных работ по разработке пользовательского интерфейса Window в GitHub.The working code for this experience can be found in the Window UI Dev Labs repo on GitHub. Это пошаговое руководство по созданию такого взаимодействия.Here is the step-by-step walk through of building that experience. В коде разметки XAML присутствуют следующие элементы: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>

Поскольку ListView (ThumbnailList) — это элемент управления XAML, который уже прокручивается, прокрутка должна быть связана с родительским элементом (ContentPanel), когда достигается элемент самого верхнего уровня и дальнейшая прокрутка невозможна.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.) Для этого необходимо задать в разметке ListView значение true для свойства ScrollViewer.IsVerticalScrollChainingEnabled.(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. Кроме того, потребуется задать значение Всегда для режима сцепления в свойстве VisualInteractionSource.You will also need to set the chaining mode on the VisualInteractionSource to Always.

Необходимо задать значение true для обработчика PointerPressedEvent с параметром handledEventsToo.You need to set PointerPressedEvent handler with the handledEventsToo parameter as true. Без этого обработчик PointerPressedEvent не будет привязан к панели ContentPanel, поскольку элемент управления ListView пометит эти события как обработанные и они не будут отправлены по цепочке визуальных элементов.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);

Теперь вы готовы связать все это с InteractionTracker.Now, you're ready to tie this with InteractionTracker. Для начала настройте классы InteractionTracker, VisualInteractionSource и Expression, в которых будет использоваться положение 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);

После настройки этих параметров панель обновления будет находиться за пределами окна просмотра в исходном положении, и пользователь увидит только представление списка listView. Когда сдвиг достигнет панели ContentPanel, будет запущено событие PointerPressed, в котором вы укажете системе на необходимость использовать InteractionTracker для реализации указанного взаимодействия с манипуляциями.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));
 }
}

Примечание

Если связывать обработанные события не требуется, добавить обработчик PointerPressedEvent можно непосредственно с помощью разметки XAML, используя атрибут (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").

Следующий шаг — настройка модификаторов источника.The next step is to set up the source modifiers. Для получения этого поведения вы используете 2 модификатора источника: сопротивление и остановка.You will be using 2 source modifiers to get this behavior; Resistance and Stop.

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

Эта схема обеспечивает визуализацию настройки SourceModifiers.This diagram gives a visualization of the SourceModifiers setup.

Схема сдвига

При использовании SourceModifiers вы заметите, что при сдвиге ListView вниз и достижении самого верхнего элемента панель обновления оттягивается вниз с половинным темпом сдвига, пока не достигнет высоты RefreshPanel, после чего панель перестает двигаться.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.

В полном примере анимация ключевого кадра используется для вращения значка во время взаимодействия на холсте RefreshPanel.In the full sample, a keyframe animation is used to spin an icon during interaction in the RefreshPanel canvas. Вместо этого можно использовать любое содержимое; кроме того, можно использовать положение InteractionTracker для отдельного выполнения этой анимации.Any content can be used in its place or utilize InteractionTracker’s position to drive that animation separately.