如何阻止UWP ListView绑定的ObservableCollection更新时自动滚动到顶部
问题根因
你当前设置的ItemsUpdatingScrollMode="KeepItemsInView"仅在修改现有列表项、增量增删少量数据时生效,你代码中调用records.Clear()会一次性移除所有绑定项,此时ListView视口内容为空会自动滚动到顶部,后续重新添加所有项也不会恢复之前的滚动位置,因此之前的配置不生效。
解决方案
方案1(推荐):改为增量更新现有项,不执行全量清空操作
你的业务场景是拉取货币数据,每条数据的id为唯一标识,因此不需要全量清空重加,直接匹配更新现有项的属性即可,同时避免了列表项重绘的性能损耗。
- 首先让
Coin类实现INotifyPropertyChanged接口,保证属性变更时UI自动刷新:
using System.ComponentModel; using System.Runtime.CompilerServices; public class Coin : INotifyPropertyChanged { private string _id; public string id { get => _id; set { _id = value; OnPropertyChanged(); } } private string _name; public string name { get => _name; set { _name = value; OnPropertyChanged(); } } private decimal _price; public decimal price { get => _price; set { _price = value; OnPropertyChanged(); } } private string _logo; public string logo { get => _logo; set { _logo = value; OnPropertyChanged(); } } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
- 修改
getData方法逻辑,移除Clear调用,改为匹配更新:
using System.Linq; public async Task getData() { var client = new HttpClient(); HttpResponseMessage response = await client.GetAsync(new Uri("https://api.nomics.com/v1/currencies/ticker?key=<api key>&limit=10")); var jsonString = await response.Content.ReadAsStringAsync(); JsonArray root = JsonValue.Parse(jsonString).GetArray(); var validIds = new List<string>(); for (uint i = 0; i < root.Count; i++) { string id = root.GetObjectAt(i).GetNamedString("id"); validIds.Add(id); string name = root.GetObjectAt(i).GetNamedString("name"); decimal price = decimal.Parse(root.GetObjectAt(i).GetNamedString("price")); var updatedCoin = new Coin { id = id, name = name, price = Math.Round(price, 4), logo = "https://cryptoicon-api.vercel.app/api/icon/" + id.ToLower() }; // 匹配现有项更新属性 var existingCoin = records.FirstOrDefault(c => c.id == id); if (existingCoin != null) { existingCoin.name = updatedCoin.name; existingCoin.price = updatedCoin.price; existingCoin.logo = updatedCoin.logo; } else { // 新增数据直接添加 records.Add(updatedCoin); } } // 移除不在新数据中的旧项 var toRemove = records.Where(c => !validIds.Contains(c.id)).ToList(); foreach (var item in toRemove) { records.Remove(item); } }
方案2:全量刷新时手动记录并恢复滚动位置
如果业务场景必须全量清空重加,可以在更新前记录当前滚动偏移量,更新完成后恢复位置:
- 先实现获取ListView内嵌ScrollViewer的方法:
using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Controls; private ScrollViewer GetScrollViewer(DependencyObject element) { if (element is ScrollViewer viewer) return viewer; for (int i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++) { var child = VisualTreeHelper.GetChild(element, i); var result = GetScrollViewer(child); if (result != null) return result; } return null; }
- 修改
getData方法,添加滚动位置恢复逻辑:
using Windows.UI.Core; public async Task getData() { var scrollViewer = GetScrollViewer(CoinsLV); // 记录当前滚动偏移量 double currentOffset = scrollViewer.VerticalOffset; var client = new HttpClient(); HttpResponseMessage response = await client.GetAsync(new Uri("https://api.nomics.com/v1/currencies/ticker?key=<api key>&limit=10")); var jsonString = await response.Content.ReadAsStringAsync(); JsonArray root = JsonValue.Parse(jsonString).GetArray(); records.Clear(); for (uint i = 0; i < root.Count; i++) { string id = root.GetObjectAt(i).GetNamedString("id"); string name = root.GetObjectAt(i).GetNamedString("name"); decimal price = decimal.Parse(root.GetObjectAt(i).GetNamedString("price")); records.Add(new Coin { id = id, name = name, price = Math.Round(price, 4), logo = "https://cryptoicon-api.vercel.app/api/icon/" + id.ToLower() }); }; // 等待UI渲染完成后恢复滚动位置 await CoinsLV.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => { scrollViewer.ChangeView(null, currentOffset, null, false); }); }
内容的提问来源于stack exchange,提问作者Thilo Jaeggi
相关产品推荐
相关产品推荐

