.NET Maui CollectionView滚动加载重复对象问题求助
.NET Maui CollectionView滚动时项重复显示问题
问题现象
在.NET Maui应用的Android设备/虚拟机上,滚动CollectionView时出现项内容重复:初始显示正常,但滚动后,原本该显示Product 4的位置重复显示Product 3,Product 5的位置重复显示Product 1,且交互效果对应原项。移除DataTemplate中的子CollectionView(UOFMs选择)或Picker后显示恢复正常,重新添加任一控件问题复现。
相关代码
CollectionView的XAML代码
<CollectionView x:Name="cartCollectionView" ZIndex="0" ItemsSource="{Binding CartItems}" CanReorderItems="False" SelectionMode="None" Margin="0, 10" Grid.Row="0"> <CollectionView.ItemsLayout> <LinearItemsLayout Orientation="Vertical" /> </CollectionView.ItemsLayout> <CollectionView.ItemTemplate> <DataTemplate> <Border Stroke="LightGray" Margin="0, 5" Padding="10"> <Border.StrokeShape> <RoundRectangle CornerRadius="5" /> </Border.StrokeShape> <Grid ColumnSpacing="10" RowSpacing="5"> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <Grid Grid.Row="0"> <Label Text="{Binding Name}" FontSize="18" VerticalOptions="Center" /> </Grid> <CollectionView Grid.Row="1" SelectionMode="Single" SelectedItem="{Binding selectedUOFM}" ItemsSource="{Binding UOFMs}" SelectionChanged="CollectionView_SelectionChanged"> <CollectionView.ItemsLayout> <LinearItemsLayout Orientation="Horizontal" ItemSpacing="10"/> </CollectionView.ItemsLayout> <CollectionView.ItemTemplate> <DataTemplate x:DataType="x:String"> <Border Stroke="DarkGray" Padding="10"> <Border.StrokeShape> <RoundRectangle CornerRadius="20"/> </Border.StrokeShape> <VisualStateManager.VisualStateGroups> <VisualStateGroup Name="CommonStates"> <VisualState Name="Normal"> <VisualState.Setters> <Setter Property="BackgroundColor" Value="Transparent"></Setter> </VisualState.Setters> </VisualState> <VisualState Name="Selected"> <VisualState.Setters> <Setter Property="BackgroundColor" Value="#6174b7"></Setter> </VisualState.Setters> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <Label Text="{Binding}" VerticalOptions="Center" HorizontalOptions="Center" /> </Border> </DataTemplate> </CollectionView.ItemTemplate> </CollectionView> <Grid Grid.Row="2" ColumnDefinitions="Auto, Auto, Auto, Auto, *"> <Label VerticalOptions="Center" Grid.Column="0" Text="Lot: "/> <Label VerticalOptions="Center" Grid.Column="1" Text="{Binding selectedLot.lottext}"/> <Label VerticalOptions="Center" Text="{x:Static local:AppResources.ExpirationDate}" Grid.Column="2" Margin="10,0,0,0"/> <Picker SelectedItem="{Binding selectedLot}" x:Name="pickerLot" Grid.Column="3" WidthRequest="1" Opacity="0" Loaded="Picker_Loaded" ItemsSource="{Binding lots}" ItemDisplayBinding="{Binding pickertext}" Title="{x:Static local:AppResources.SelectDateLot}" HorizontalOptions="StartAndExpand" SelectedIndexChanged="Picker_SelectedIndexChanged"/> <Label Text="{Binding selectedLot.expirationDateText}" Grid.Column="3" HorizontalTextAlignment="Center" VerticalTextAlignment="Center" /> <Button Text="..." Grid.Column="4" x:Name="bPickLot" Clicked="bPickLot_Clicked" HorizontalOptions="End" IsVisible="{Binding IsPickLotVisible}" BackgroundColor="#6174b7" CornerRadius="5" HeightRequest="5"/> </Grid> <Grid Grid.Row="3" ColumnDefinitions="Auto, Auto, Auto, *, Auto"> <Label VerticalOptions="Center" Grid.Column="0"> <Label.FormattedText> <FormattedString> <Span Text="{x:Static local:AppResources.Qty}" /> <Span Text=":" /> </FormattedString> </Label.FormattedText> </Label> <Entry Text="{Binding quantity}" Grid.Column="1" Unfocused="Entry_Unfocused" Keyboard="Numeric" HorizontalOptions="StartAndExpand" WidthRequest="50" /> <StackLayout Orientation="Horizontal" HorizontalOptions="Center" VerticalOptions="Center" Grid.Column="2"> <Label Text="{Binding SalePrice, StringFormat='{0:C}'}" FontSize="16" HorizontalOptions="Center" VerticalOptions="Center" /> <Label Text="{Binding Price, StringFormat='{0:C}'}" IsVisible="{Binding IsSale}" TextColor="Red" TextDecorations="Strikethrough" Margin="5,0,0,0" FontSize="16" HorizontalOptions="End" VerticalOptions="Center" /> </StackLayout> <Label Text="{Binding ItemTotal, StringFormat='{0:C}'}" FontSize="16" Grid.Column="3" HorizontalOptions="Center" VerticalOptions="Center" /> <ImageButton Grid.Column="4" VerticalOptions="Center" HorizontalOptions="Center" HeightRequest="15" WidthRequest="15" Source="x.png" Clicked="ImageButton_Clicked"/> </Grid> </Grid> </Border> </DataTemplate> </CollectionView.ItemTemplate> </CollectionView>
项加载逻辑
private async void ContentPage_NavigatedTo(object sender, NavigatedToEventArgs e) { await ViewModel?.LoadCartItems(); }
ViewModel相关代码
private List<CartItem> _cartItems { get; set; } public List<CartItem> CartItems { get => _cartItems; set { _cartItems = value; OnPropertyChanged(nameof(CartItems)); OnPropertyChanged(nameof(Total)); } } public async Task LoadCartItems() { CartItems = await databaseHelper.GetCartItems(); }
数据库助手GetCartItems方法
// return test objects, no need to fetch the DB to reproduce bug public async Task<List<CartItem>> GetCartItems() { return new List<CartItem>() { new CartItem() { itemnmbr = "12345", nameFR = "Produit 1", nameEN = "Product 1", quantity = 1, selectedUOFM = "kg", prices = new List<ProductPrice> { new ProductPrice { itemnmbr = "12345", price = 10.0m, uofm = "kg" } }, lots = new List<LotEntity> { new LotEntity { itemnmbr = "12345", lot = "Lot 1", expirationDate = "2024-12-31", isSelected = true } }, selectedLot = new LotEntity { itemnmbr = "12345", lot = "Lot 1", expirationDate = "2024-12-31", isSelected = true } }, new CartItem() { itemnmbr = "12345", nameFR = "Produit 2", nameEN = "Product 2", quantity = 2, selectedUOFM = "kg", prices = new List<ProductPrice> { new ProductPrice { itemnmbr = "12345", price = 20.0m, uofm = "kg" } }, lots = new List<LotEntity> { new LotEntity { itemnmbr = "12345", lot = "Lot 1", expirationDate = "2024-12-31", isSelected = true } }, selectedLot = new LotEntity { itemnmbr = "12345", lot = "Lot 1", expirationDate = "2024-12-31", isSelected = true } }, new CartItem() { itemnmbr = "12345", nameFR = "Produit 3", nameEN = "Product 3", quantity = 3, selectedUOFM = "kg", prices = new List<ProductPrice> { new ProductPrice { itemnmbr = "12345", price = 30.0m, uofm = "kg" } }, lots = new List<LotEntity> { new LotEntity { itemnmbr = "12345", lot = "Lot 1", expirationDate = "2024-12-31", isSelected = true } }, selectedLot = new LotEntity { itemnmbr = "12345", lot = "Lot 1", expirationDate = "2024-12-31", isSelected = true } }, new CartItem() { itemnmbr = "12345", nameFR = "Produit 4", nameEN = "Product 4", quantity = 4, selectedUOFM = "kg", prices = new List<ProductPrice> { new ProductPrice { itemnmbr = "12345", price = 40.0m, uofm = "kg" } }, lots = new List<LotEntity> { new LotEntity { itemnmbr = "12345", lot = "Lot 1", expirationDate = "2024-12-31", isSelected = true } }, selectedLot = new LotEntity { itemnmbr = "12345", lot = "Lot 1", expirationDate = "2024-12-31", isSelected = true } }, new CartItem() { itemnmbr = "12345", nameFR = "Produit 5", nameEN = "Product 5", quantity = 5, selectedUOFM = "kg", prices = new List<ProductPrice> { new ProductPrice { itemnmbr = "12345", price = 50.0m, uofm = "kg" } }, lots = new List<LotEntity> { new LotEntity { itemnmbr = "12345", lot = "Lot 1", expirationDate = "2024-12-31", isSelected = true } }, selectedLot = new LotEntity { itemnmbr = "12345", lot = "Lot 1", expirationDate = "2024-12-31", isSelected = true } } }; }
解决方案
1. 给根DataTemplate指定x:DataType
在CollectionView的根DataTemplate上添加x:DataType,明确绑定的实体类型,帮助Maui绑定系统正确识别数据上下文,避免视图复用时的绑定混乱:
<DataTemplate x:DataType="local:CartItem">
(注意替换local为你的CartItem类所在的命名空间)
2. 确保子控件绑定为双向模式
对于子CollectionView的SelectedItem,显式声明双向绑定,确保视图复用能正确同步数据:
SelectedItem="{Binding selectedUOFM, Mode=TwoWay}"
3. 将List替换为ObservableCollection
ViewModel中把List<CartItem>改为ObservableCollection<CartItem>,集合的变更会自动通知UI更新,避免视图复用时的数据不匹配:
private ObservableCollection<CartItem> _cartItems { get; set; } public ObservableCollection<CartItem> CartItems { get => _cartItems; set { _cartItems = value; OnPropertyChanged(nameof(CartItems)); OnPropertyChanged(nameof(Total)); } } public async Task LoadCartItems() { var items = await databaseHelper.GetCartItems(); CartItems = new ObservableCollection<CartItem>(items); }
4. 检查实体类的INotifyPropertyChanged实现
确保CartItem、LotEntity等所有绑定的实体类正确实现INotifyPropertyChanged接口,所有属性变更时触发OnPropertyChanged事件,保证UI能实时刷新数据:
public class CartItem : INotifyPropertyChanged { private string _nameEN; public string nameEN { get => _nameEN; set { _nameEN = value; OnPropertyChanged(nameof(nameEN)); OnPropertyChanged(nameof(Name)); // 如果Name是计算属性,也要触发其变更 } } // 其他属性同理实现... public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
5. 临时禁用视图复用(不推荐长期使用)
如果上述方法无效,可临时修改CollectionView的缓存策略禁用视图复用,该方法会影响列表性能,仅作为临时调试方案:
<CollectionView x:Name="cartCollectionView" ... CachingStrategy="RetainElement">
内容的提问来源于stack exchange,提问作者MyselfAndOnlyMe
相关产品推荐
相关产品推荐

