ソース修飾子を使用した引っ張って更新Pull-to-refresh with source modifiers

この記事では、InteractionTracker の SourceModifier 機能を使用する方法について詳しく説明します。また、カスタムの引っ張って更新コントロールを作成して、具体的な使用方法を紹介します。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 の説明、および SourceModifier が便利である理由What is a SourceModifier and why are they useful?

InertiaModifier と同様に、SourceModifier を使用すると、InteractionTracker のモーションを微調整することができます。Like InertiaModifiers, SourceModifiers give you finer grain control over the motion of an InteractionTracker. ただし、InteractionTracker が慣性状態に移行した後のモーションを定義する InertiaModifier とは異なり、SourceModifier は、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.

ソース修飾子には、次の 2 種類があります。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 型のパラメーターで、開発者は、パラメーターを InteractionTracker に渡す前に、位置の X、Y、Z 属性を変更することができます。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 が対話状態になっているとき、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. つまり、1 つの 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 UI Dev Labs repo on GitHub」(GitHub の Windows UI 開発ラボ リポジトリ) をご覧ください。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 はソース修飾子が適用されます) です。この ScrollViewer.IsVerticalScrollChainingEnabled に設定する必要があるためtrue 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. また、VisualInteractionSource のチェーン モードを Always に設定する必要もあります。You will also need to set the chaining mode on the VisualInteractionSource to Always.

handledEventsToo パラメーターを使用して、PointerPressedEvent ハンドラーを true に設定する必要があります。You need to set PointerPressedEvent handler with the handledEventsToo parameter as true. このオプションを使用しないと、PointerPressedEvent は ListView コントロールとして ContentPanel にチェーンされず、これらのイベントは処理済みとしてマークされ、これらのイベントはビジュアル チェーンに送信されなくなります。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、および 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));
 }
}

注意

Handled イベントのチェーンが必要でない場合は、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 つのソース修飾子 ResistanceStop を使用します。You will be using 2 source modifiers to get this behavior; Resistance and Stop.

  • Resistance – 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;
  • Stop – 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);

次の図は、SourceModifier のセットアップを説明したものです。This diagram gives a visualization of the SourceModifiers setup.

パンの図

以上で、SourceModifier を使用して、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.

完全なサンプルでは、KeyFrame アニメーションを使用して、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.