使用來源修飾詞進行拖動以重新整理

在本文中,我們會深入探討如何使用 InteractionTracker 的 SourceModifier 功能,並藉由建立自訂的拖動以重新整理控制項來示範其使用方式。

必要條件

在這裡,我們假設您已熟悉下列文章中討論的概念:

什麼是 SourceModifier,以及它們為何有用?

如同 InertiaModifiers,SourceModifiers 可讓您更精細地控制 InteractionTracker 的動作。 但不同的之處在於,InertiaModifiers 是在 InteractionTracker 進入慣性之後定義動作,SourceModifiers 卻是在 InteractionTracker 仍處於其互動狀態時定義動作。 在這些情況下,您想要不同於傳統「黏住手指」的體驗。

其中一個典型的範例就是拖動以重新整理體驗 - 當使用者拖動清單以重新整理內容,清單平移的速度與手指相同,並在某個距離後停止之時,動作會感覺突兀又機械。 更自然的體驗是在使用者主動與清單互動時,引入阻力的感覺。 這個小小的細微差別,有助於提升整體終端使用者體驗,讓清單互動更具動態和吸引力。 在「範例」一節中,我們會進一步詳細說明如何建置此功能。

來源修飾詞有 2 種類型:

  • DeltaPosition – 是觸控平移互動期間,手指在目前畫面位置與上一個畫面位置之間的差異。 此來源修飾詞可讓您先修改互動的差異位置,再傳送它以供進一步處理。 這是 Vector3 類型參數,開發人員可以選擇先修改位置的任何 X 或 Y 或 Z 屬性,然後再將它傳遞至 InteractionTracker。
  • DeltaScale - 是目前畫面縮放比例與上一個畫面縮放比例 (在觸控縮放互動期間所套用) 之間的差異。 此來源修飾詞可讓您修改互動的縮放程度。 這是浮點數類型屬性,開發人員可以在傳遞至 InteractionTracker 之前加以修改。

當 InteractionTracker 處於互動狀態時,它會評估指派給它的每個來源修飾詞,並判斷其中是否有任何一個適用。 這表示您可以建立多個來源修飾詞,並指派給 InteractionTracker。 但是,在定義每個項目時,您需要執行下列動作:

  1. 定義 Condition – 這是一個運算式,定義何時套用此特定來源修飾詞的條件陳述式。
  2. 定義 DeltaPosition/DeltaScale – 這個來源修飾詞運算式在符合上述定義條件時,會改變 DeltaPosition 或 DeltaScale。

範例

現在讓我們看看如何使用來源修飾詞,透過現有的 XAML ListView 控制項,建立自訂的拖動以重新整理體驗。 我們將使用 Canvas 做為「重新整理面板」,此面板將會堆疊在 XAML ListView 的上層,以建置此體驗。

為了使用者體驗的緣故,我們想要在使用者主動平移清單 (透過觸控),並在位置超過某個點後停止平移時,建立「阻力」的效果。

具有提取重新整理的清單

此體驗可運作的程式碼能夠在 GitHub 上的 Window UI Dev Labs 存放庫中找到。 以下是建置該體驗的逐步解說。 在您的 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 就是您將要套用來源修飾詞的位置。若要確保其發生,您必須在 ListView 標記中將 ScrollViewer.IsVerticalScrollChainingEnabled 設定為 true。 您也必須將 VisualInteractionSource 上的鏈結模式設定為 Always

您必須使用 handledEventsToo 參數將 PointerPressedEvent 處理常式設定為 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 位置的 Expression 這三項開始。

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

注意

如果不需要鏈結 Handled 事件,可以直接透過 XAML 標記使用屬性來新增 PointerPressedEvent 處理常式 (PointerPressed="Window_PointerPressed")。

下一步是設定來源修飾詞。 您將使用 2 個來源修飾詞來取得此行為;ResistanceStop

  • Resistance – 阻力,將 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;
  • Stop – 停止,當整個 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 的位置來個別驅動該動畫。