我认为你正在寻找的是一种方法来实际滚动元素到ListView
的顶部。
在这篇文章中,我创建了一个扩展方法,用于在ScrollViewer
内滚动到特定元素。
在你的情况下,思路是相同的。
你需要先找到ListView
中的ScrollViewer
实例,然后找到要滚动到的实际项,即ListViewItem
。
这里是一个获取ScrollViewer
的扩展方法。
public static ScrollViewer GetScrollViewer(this DependencyObject element)
{
if (element is ScrollViewer)
{
return (ScrollViewer)element;
}
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++)
{
var child = VisualTreeHelper.GetChild(element, i);
var result = GetScrollViewer(child);
if (result == null)
{
continue;
}
else
{
return result;
}
}
return null;
}
一旦我获得了
ScrollViewer
实例,我创建了两个扩展方法,分别基于索引或附加对象滚动到一个项目。由于
ListView
和
GridView
共享相同的基类
ListViewBase
,所以这两个扩展方法也适用于
GridView
。
更新
基本上,这些方法将首先查找该项,如果已经呈现,则立即滚动到该项。如果该项为
null
,则表示启用了虚拟化,并且尚未实现该项。因此,要先实现该项,请调用
ScrollIntoViewAsync
(基于任务的方法来包装内置的
ScrollIntoView
,与
ChangeViewAsync
相同,提供更清晰的代码),计算位置并保存它。由于现在我知道要滚动到的位置,所以我需要先将该项全部回滚到其先前的位置
即时地(即没有动画),然后最终使用动画滚动到期望的位置。
public async static Task ScrollToIndex(this ListViewBase listViewBase, int index)
{
bool isVirtualizing = default(bool);
double previousHorizontalOffset = default(double), previousVerticalOffset = default(double);
var scrollViewer = listViewBase.GetScrollViewer();
var selectorItem = listViewBase.ContainerFromIndex(index) as SelectorItem;
if (selectorItem == null)
{
isVirtualizing = true;
previousHorizontalOffset = scrollViewer.HorizontalOffset;
previousVerticalOffset = scrollViewer.VerticalOffset;
await listViewBase.ScrollIntoViewAsync(listViewBase.Items[index]);
selectorItem = (SelectorItem)listViewBase.ContainerFromIndex(index);
}
var transform = selectorItem.TransformToVisual((UIElement)scrollViewer.Content);
var position = transform.TransformPoint(new Point(0, 0));
if (isVirtualizing)
{
await scrollViewer.ChangeViewAsync(previousHorizontalOffset, previousVerticalOffset, true);
}
scrollViewer.ChangeView(position.X, position.Y, null);
}
public async static Task ScrollToItem(this ListViewBase listViewBase, object item)
{
bool isVirtualizing = default(bool);
double previousHorizontalOffset = default(double), previousVerticalOffset = default(double);
var scrollViewer = listViewBase.GetScrollViewer();
var selectorItem = listViewBase.ContainerFromItem(item) as SelectorItem;
if (selectorItem == null)
{
isVirtualizing = true;
previousHorizontalOffset = scrollViewer.HorizontalOffset;
previousVerticalOffset = scrollViewer.VerticalOffset;
await listViewBase.ScrollIntoViewAsync(item);
selectorItem = (SelectorItem)listViewBase.ContainerFromItem(item);
}
var transform = selectorItem.TransformToVisual((UIElement)scrollViewer.Content);
var position = transform.TransformPoint(new Point(0, 0));
if (isVirtualizing)
{
await scrollViewer.ChangeViewAsync(previousHorizontalOffset, previousVerticalOffset, true);
}
scrollViewer.ChangeView(position.X, position.Y, null);
}
public static async Task ScrollIntoViewAsync(this ListViewBase listViewBase, object item)
{
var tcs = new TaskCompletionSource<object>();
var scrollViewer = listViewBase.GetScrollViewer();
EventHandler<ScrollViewerViewChangedEventArgs> viewChanged = (s, e) => tcs.TrySetResult(null);
try
{
scrollViewer.ViewChanged += viewChanged;
listViewBase.ScrollIntoView(item, ScrollIntoViewAlignment.Leading);
await tcs.Task;
}
finally
{
scrollViewer.ViewChanged -= viewChanged;
}
}
public static async Task ChangeViewAsync(this ScrollViewer scrollViewer, double? horizontalOffset, double? verticalOffset, bool disableAnimation)
{
var tcs = new TaskCompletionSource<object>();
EventHandler<ScrollViewerViewChangedEventArgs> viewChanged = (s, e) => tcs.TrySetResult(null);
try
{
scrollViewer.ViewChanged += viewChanged;
scrollViewer.ChangeView(horizontalOffset, verticalOffset, null, disableAnimation);
await tcs.Task;
}
finally
{
scrollViewer.ViewChanged -= viewChanged;
}
}
一种更简单的方法,但没有动画效果
您还可以使用ScrollIntoView
的新重载,通过指定第二个参数来确保项目与顶部对齐; 然而,这样做没有我之前的扩展方法中的平滑滚动转换。
MyListView?.ScrollIntoView(MyListView.Items[5], ScrollIntoViewAlignment.Leading)