Skip to content

Commit 5aa3a84

Browse files
Vijay-NirmalRosuavio
authored andcommitted
Added SmoothScrollIntoView ListViewBase Extension Method
1 parent 28444e9 commit 5aa3a84

File tree

6 files changed

+290
-13
lines changed

6 files changed

+290
-13
lines changed

Microsoft.Toolkit.Uwp.SampleApp/Models/Sample.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
using Microsoft.Toolkit.Uwp.Helpers;
2424
using Microsoft.Toolkit.Uwp.Input.GazeInteraction;
2525
using Microsoft.Toolkit.Uwp.SampleApp.Models;
26+
using Microsoft.Toolkit.Uwp.UI;
2627
using Microsoft.Toolkit.Uwp.UI.Animations;
2728
using Microsoft.Toolkit.Uwp.UI.Controls;
2829
using Microsoft.Toolkit.Uwp.UI.Media;
@@ -674,8 +675,9 @@ private static Type LookForTypeByName(string typeName)
674675

675676
// TODO Reintroduce graph controls
676677
// typeof(UserToPersonConverter)) // Search in Microsoft.Toolkit.Graph.Controls
678+
ItemPlacement.Default.GetType(), // Search in Microsoft.Toolkit.Uwp.UI
677679
EasingType.Default.GetType(), // Microsoft.Toolkit.Uwp.UI.Animations
678-
ImageBlendMode.Multiply.GetType(), // Search in Microsoft.Toolkit.Uwp.UI
680+
ImageBlendMode.Multiply.GetType(), // Search in Microsoft.Toolkit.Uwp.UI.Media
679681
Interaction.Enabled.GetType(), // Microsoft.Toolkit.Uwp.Input.GazeInteraction
680682
DataGridGridLinesVisibility.None.GetType(), // Microsoft.Toolkit.Uwp.UI.Controls.DataGrid
681683
GridSplitter.GridResizeDirection.Auto.GetType(), // Microsoft.Toolkit.Uwp.UI.Controls.Layout

Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ListViewExtensions/ListViewExtensionsCode.bind

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,21 @@
88

99
<Page.Resources>
1010
<DataTemplate x:Name="NormalTemplate">
11-
<TextBlock Text="{Binding Title}" Foreground="Green"/>
11+
<TextBlock Text="{Binding}" Foreground="Green" VerticalAlignment="Center" FontWeight="Bold"></TextBlock>
1212
</DataTemplate>
1313

1414
<DataTemplate x:Name="AlternateTemplate">
15-
<TextBlock Text="{Binding Title}" Foreground="Red"/>
15+
<TextBlock Text="{Binding}" Foreground="Red" VerticalAlignment="Center" FontWeight="Bold"></TextBlock>
1616
</DataTemplate>
1717
</Page.Resources>
1818

1919
<Grid>
20+
<TextBlock x:Name="IndexInput" Text="@[Index:Slider:100:0-500]" Visibility="Collapsed"></TextBlock>
21+
<TextBlock x:Name="ItemPlacementInput" Text="@[Item Placement:Enum:ItemPlacement.Bottom]" Visibility="Collapsed"></TextBlock>
22+
<TextBlock x:Name="DisableAnimationInput" Text="@[Disable Animation:Bool:False]" Visibility="Collapsed"></TextBlock>
23+
<TextBlock x:Name="ScrollIfVisibileInput" Text="@[Scroll If Visibile:Bool:False]" Visibility="Collapsed"></TextBlock>
24+
<TextBlock x:Name="AdditionalHorizontalOffsetInput" Text="@[Additional Horizontal Offset:Slider:0:-500-500]" Visibility="Collapsed"></TextBlock>
25+
<TextBlock x:Name="AdditionalVerticalOffsetInput" Text="@[Additional Vertical Offset:Slider:0:-500-500]" Visibility="Collapsed"></TextBlock>
2026

2127
<ListView
2228
x:Name="SampleListView"

Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ListViewExtensions/ListViewExtensionsPage.xaml

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Page x:Class="Microsoft.Toolkit.Uwp.SampleApp.SamplePages.ListViewExtensionsPage"
1+
<Page x:Class="Microsoft.Toolkit.Uwp.SampleApp.SamplePages.ListViewExtensionsPage"
22
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
33
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
44
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
@@ -8,18 +8,16 @@
88

99
<Page.Resources>
1010
<DataTemplate x:Name="NormalTemplate">
11-
<TextBlock Foreground="Green"
12-
Text="{Binding Title}" />
11+
<TextBlock Text="{Binding}" Foreground="Green" VerticalAlignment="Center" FontWeight="Bold"></TextBlock>
1312
</DataTemplate>
1413

1514
<DataTemplate x:Name="AlternateTemplate">
16-
<TextBlock Foreground="Red"
17-
Text="{Binding Title}" />
15+
<TextBlock Text="{Binding}" Foreground="Red" VerticalAlignment="Center" FontWeight="Bold"></TextBlock>
1816
</DataTemplate>
1917
</Page.Resources>
2018

2119
<Grid>
22-
20+
<TextBlock x:Name="IndexInput" Text="[Index:String:100]" Visibility="Collapsed"></TextBlock>
2321
<ListView x:Name="SampleListView"
2422
Margin="12"
2523
ui:ListViewExtensions.AlternateColor="#33AAAAAA"

Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ListViewExtensions/ListViewExtensionsPage.xaml.cs

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// See the LICENSE file in the project root for more information.
44

55
using System;
6+
using System.Collections.ObjectModel;
67
using System.Windows.Input;
78
using Microsoft.Toolkit.Uwp.SampleApp.Common;
89
using Microsoft.Toolkit.Uwp.SampleApp.Data;
@@ -20,24 +21,54 @@ public ListViewExtensionsPage()
2021
this.InitializeComponent();
2122
}
2223

23-
public ICommand SampleCommand => new DelegateCommand<PhotoDataItem>(OnExecuteSampleCommand);
24+
public ICommand SampleCommand => new DelegateCommand<string>(OnExecuteSampleCommand);
2425

2526
public async void OnXamlRendered(FrameworkElement control)
2627
{
2728
var sampleListView = control.FindChild("SampleListView") as ListView;
29+
var indexInput = control.FindChild("IndexInput") as TextBlock;
30+
var itemPlacementInput = control.FindChild("ItemPlacementInput") as TextBlock;
31+
var disableAnimationInput = control.FindChild("DisableAnimationInput") as TextBlock;
32+
var scrollIfVisibileInput = control.FindChild("ScrollIfVisibileInput") as TextBlock;
33+
var additionalHorizontalOffsetInput = control.FindChild("AdditionalHorizontalOffsetInput") as TextBlock;
34+
var additionalVerticalOffsetInput = control.FindChild("AdditionalVerticalOffsetInput") as TextBlock;
35+
36+
SampleController.Current.RegisterNewCommand("Start Smooth Scroll", (sender, args) =>
37+
{
38+
var index = int.Parse(indexInput.Text);
39+
var itemPlacement = (ItemPlacement)Enum.Parse(typeof(ItemPlacement), itemPlacementInput.Text);
40+
var disableAnimation = bool.Parse(disableAnimationInput.Text);
41+
var scrollIfVisibile = bool.Parse(scrollIfVisibileInput.Text);
42+
var additionalHorizontalOffset = int.Parse(additionalHorizontalOffsetInput.Text);
43+
var additionalVerticalOffset = int.Parse(additionalVerticalOffsetInput.Text);
44+
sampleListView.SmoothScrollIntoViewWithIndex(index, itemPlacement, disableAnimation, scrollIfVisibile, additionalHorizontalOffset, additionalVerticalOffset);
45+
});
2846

2947
if (sampleListView != null)
3048
{
31-
sampleListView.ItemsSource = await new Data.PhotosDataSource().GetItemsAsync();
49+
sampleListView.ItemsSource = GetOddEvenSource(500);
3250
}
3351

3452
// Transfer Data Context so we can access SampleCommand
3553
control.DataContext = this;
3654
}
3755

38-
private async void OnExecuteSampleCommand(PhotoDataItem item)
56+
private async void OnExecuteSampleCommand(string item)
57+
{
58+
await new MessageDialog($"You clicked {item} via the 'ListViewExtensions.Command' binding", "Item Clicked").ShowAsync();
59+
}
60+
61+
public ObservableCollection<string> GetOddEvenSource(int count)
3962
{
40-
await new MessageDialog($"You clicked {item.Title} via the 'ListViewExtensions.Command' binding", "Item Clicked").ShowAsync();
63+
var oddEvenSource = new ObservableCollection<string>();
64+
65+
for (int number = 0; number <= count; number++)
66+
{
67+
var item = (number % 2) == 0 ? $"{number} - Even" : $"{number} - Odd";
68+
oddEvenSource.Add(item);
69+
}
70+
71+
return oddEvenSource;
4172
}
4273
}
4374
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
namespace Microsoft.Toolkit.Uwp.UI
6+
{
7+
/// <summary>
8+
/// Item Position
9+
/// </summary>
10+
public enum ItemPlacement
11+
{
12+
/// <summary>
13+
/// If visible then it will not scroll, if not then item will be aligned to the nearest edge
14+
/// </summary>
15+
Default,
16+
17+
/// <summary>
18+
/// Aligned left
19+
/// </summary>
20+
Left,
21+
22+
/// <summary>
23+
/// Aligned top
24+
/// </summary>
25+
Top,
26+
27+
/// <summary>
28+
/// Aligned centre
29+
/// </summary>
30+
Centre,
31+
32+
/// <summary>
33+
/// Aligned right
34+
/// </summary>
35+
Right,
36+
37+
/// <summary>
38+
/// Aligned bottom
39+
/// </summary>
40+
Bottom
41+
}
42+
}
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.Threading.Tasks;
7+
using Windows.Foundation;
8+
using Windows.UI.Xaml;
9+
using Windows.UI.Xaml.Controls;
10+
using Windows.UI.Xaml.Controls.Primitives;
11+
12+
namespace Microsoft.Toolkit.Uwp.UI
13+
{
14+
/// <summary>
15+
/// Smooth scroll the list to bring specified item into view
16+
/// </summary>
17+
public static class SmoothScrollIntoView
18+
{
19+
/// <summary>
20+
/// Smooth scrolling the list to bring the specified index into view
21+
/// </summary>
22+
/// <param name="listViewBase">List to scroll</param>
23+
/// <param name="index">The intex to bring into view</param>
24+
/// <param name="itemPlacement">Set the item placement after scrolling</param>
25+
/// <param name="disableAnimation">Set true to disable animation</param>
26+
/// <param name="scrollIfVisibile">Set true to disable scrolling when the corresponding item is in view</param>
27+
/// <param name="additionalHorizontalOffset">Adds additional horizontal offset</param>
28+
/// <param name="additionalVerticalOffset">Adds additional vertical offset</param>
29+
/// <returns>Note: Even though this return <see cref="Task"/>, it will not wait until the scrolling completes</returns>
30+
public static async Task SmoothScrollIntoViewWithIndex(this ListViewBase listViewBase, int index, ItemPlacement itemPlacement = ItemPlacement.Default, bool disableAnimation = false, bool scrollIfVisibile = true, int additionalHorizontalOffset = 0, int additionalVerticalOffset = 0)
31+
{
32+
if (index > listViewBase.Items.Count)
33+
{
34+
index = listViewBase.Items.Count;
35+
}
36+
37+
index = (index < 0) ? (index + listViewBase.Items.Count) : index;
38+
39+
bool isVirtualizing = default;
40+
double previousXOffset = default, previousYOffset = default;
41+
42+
var scrollViewer = listViewBase.FindDescendant<ScrollViewer>();
43+
var selectorItem = listViewBase.ContainerFromIndex(index) as SelectorItem;
44+
45+
if (selectorItem == null)
46+
{
47+
isVirtualizing = true;
48+
49+
previousXOffset = scrollViewer.HorizontalOffset;
50+
previousYOffset = scrollViewer.VerticalOffset;
51+
52+
var tcs = new TaskCompletionSource<object>();
53+
54+
void viewChanged(object _, ScrollViewerViewChangedEventArgs __) => tcs.TrySetResult(result: null);
55+
56+
try
57+
{
58+
scrollViewer.ViewChanged += viewChanged;
59+
listViewBase.ScrollIntoView(listViewBase.Items[index], ScrollIntoViewAlignment.Leading);
60+
await tcs.Task;
61+
}
62+
finally
63+
{
64+
scrollViewer.ViewChanged -= viewChanged;
65+
}
66+
67+
selectorItem = (SelectorItem)listViewBase.ContainerFromIndex(index);
68+
}
69+
70+
var transform = selectorItem.TransformToVisual((UIElement)scrollViewer.Content);
71+
var position = transform.TransformPoint(new Point(0, 0));
72+
73+
if (isVirtualizing)
74+
{
75+
var tcs = new TaskCompletionSource<object>();
76+
77+
void viewChanged(object _, ScrollViewerViewChangedEventArgs __) => tcs.TrySetResult(result: null);
78+
79+
try
80+
{
81+
scrollViewer.ViewChanged += viewChanged;
82+
scrollViewer.ChangeView(previousXOffset, previousYOffset, zoomFactor: null, disableAnimation: true);
83+
await tcs.Task;
84+
}
85+
finally
86+
{
87+
scrollViewer.ViewChanged -= viewChanged;
88+
}
89+
}
90+
91+
var listViewBaseWidth = listViewBase.ActualWidth;
92+
var selectorItemWidth = selectorItem.ActualWidth;
93+
var listViewBaseHeight = listViewBase.ActualHeight;
94+
var selectorItemHeight = selectorItem.ActualHeight;
95+
96+
previousXOffset = scrollViewer.HorizontalOffset;
97+
previousYOffset = scrollViewer.VerticalOffset;
98+
99+
var minXPosition = position.X - listViewBaseWidth + selectorItemWidth;
100+
var minYPosition = position.Y - listViewBaseHeight + selectorItemHeight;
101+
102+
var maxXPosition = position.X;
103+
var maxYPosition = position.Y;
104+
105+
double finalXPosition, finalYPosition;
106+
107+
if (!scrollIfVisibile && (previousXOffset <= maxXPosition && previousXOffset >= minXPosition) && (previousYOffset <= maxYPosition && previousYOffset >= minYPosition))
108+
{
109+
finalXPosition = previousXOffset;
110+
finalYPosition = previousYOffset;
111+
}
112+
else
113+
{
114+
switch (itemPlacement)
115+
{
116+
case ItemPlacement.Default:
117+
if (previousXOffset <= maxXPosition && previousXOffset >= minXPosition)
118+
{
119+
finalXPosition = previousXOffset + additionalHorizontalOffset;
120+
}
121+
else if (Math.Abs(previousXOffset - minXPosition) < Math.Abs(previousXOffset - maxXPosition))
122+
{
123+
finalXPosition = minXPosition + additionalHorizontalOffset;
124+
}
125+
else
126+
{
127+
finalXPosition = maxXPosition + additionalHorizontalOffset;
128+
}
129+
130+
if (previousYOffset <= maxYPosition && previousYOffset >= minYPosition)
131+
{
132+
finalYPosition = previousYOffset + additionalVerticalOffset;
133+
}
134+
else if (Math.Abs(previousYOffset - minYPosition) < Math.Abs(previousYOffset - maxYPosition))
135+
{
136+
finalYPosition = minYPosition + additionalVerticalOffset;
137+
}
138+
else
139+
{
140+
finalYPosition = maxYPosition + additionalVerticalOffset;
141+
}
142+
143+
break;
144+
145+
case ItemPlacement.Left:
146+
finalXPosition = maxXPosition + additionalHorizontalOffset;
147+
finalYPosition = previousYOffset + additionalVerticalOffset;
148+
break;
149+
150+
case ItemPlacement.Top:
151+
finalXPosition = previousXOffset + additionalHorizontalOffset;
152+
finalYPosition = maxYPosition + additionalVerticalOffset;
153+
break;
154+
155+
case ItemPlacement.Centre:
156+
var centreX = (listViewBaseWidth - selectorItemWidth) / 2.0;
157+
var centreY = (listViewBaseHeight - selectorItemHeight) / 2.0;
158+
finalXPosition = maxXPosition - centreX + additionalHorizontalOffset;
159+
finalYPosition = maxYPosition - centreY + additionalVerticalOffset;
160+
break;
161+
162+
case ItemPlacement.Right:
163+
finalXPosition = minXPosition + additionalHorizontalOffset;
164+
finalYPosition = previousYOffset + additionalVerticalOffset;
165+
break;
166+
167+
case ItemPlacement.Bottom:
168+
finalXPosition = previousXOffset + additionalHorizontalOffset;
169+
finalYPosition = minYPosition + additionalVerticalOffset;
170+
break;
171+
172+
default:
173+
finalXPosition = previousXOffset + additionalHorizontalOffset;
174+
finalYPosition = previousYOffset + additionalVerticalOffset;
175+
break;
176+
}
177+
}
178+
179+
scrollViewer.ChangeView(finalXPosition, finalYPosition, zoomFactor: null, disableAnimation);
180+
}
181+
182+
/// <summary>
183+
/// Smooth scrolling the list to bring the specified data item into view
184+
/// </summary>
185+
/// <param name="listViewBase">List to scroll</param>
186+
/// <param name="item">The data item to bring into view</param>
187+
/// <param name="itemPlacement">Set the item placement after scrolling</param>
188+
/// <param name="disableAnimation">Set true to disable animation</param>
189+
/// <param name="scrollIfVisibile">Set true to disable scrolling when the corresponding item is in view</param>
190+
/// <param name="additionalHorizontalOffset">Adds additional horizontal offset</param>
191+
/// <param name="additionalVerticalOffset">Adds additional vertical offset</param>
192+
/// <returns>Note: Even though this return <see cref="Task"/>, it will not wait until the scrolling completes</returns>
193+
public static async Task SmoothScrollIntoViewWithItem(this ListViewBase listViewBase, object item, ItemPlacement itemPlacement = ItemPlacement.Default, bool disableAnimation = false, bool scrollIfVisibile = true, int additionalHorizontalOffset = 0, int additionalVerticalOffset = 0)
194+
{
195+
await SmoothScrollIntoViewWithIndex(listViewBase, listViewBase.Items.IndexOf(item), itemPlacement, disableAnimation, scrollIfVisibile, additionalHorizontalOffset, additionalVerticalOffset);
196+
}
197+
}
198+
}

0 commit comments

Comments
 (0)