WinUI3中动态切换ListView的ItemTemplate如何避免闪烁?
切换ItemTemplate时出现闪烁,本质是因为ListView会销毁旧模板对应的所有可视化控件,重新创建新模板的控件,这个销毁-重建的过程会引发界面重绘,从而产生闪烁。下面是几个实用的解决思路:
1. 用DataTemplateSelector替代直接切换ItemTemplate
不要直接替换ListView的ItemTemplate属性,而是通过DataTemplateSelector根据绑定数据的某个标识(比如新增一个视图类型枚举)来动态选择模板。这种方式下ListView可以复用现有的Item容器,避免大规模的控件销毁重建。
示例代码:
首先定义一个枚举标识视图类型:
public enum ListViewDisplayMode { GridView, CardView }
然后创建自定义的DataTemplateSelector:
public class OrderTemplateSelector : DataTemplateSelector { public DataTemplate GridTemplate { get; set; } public DataTemplate CardTemplate { get; set; } protected override DataTemplate SelectTemplateCore(object item, DependencyObject container) { if (item is OrderViewModel orderVm) { // 可根据ViewModel的DisplayMode属性或全局状态选择模板 return orderVm.DisplayMode == ListViewDisplayMode.GridView ? GridTemplate : CardTemplate; } return base.SelectTemplateCore(item, container); } }
在XAML中声明Selector并绑定模板:
<Page.Resources> <!-- 模板1:Grid样式 --> <DataTemplate x:Key="GridTemplate"> <Grid Padding="0,10,0,10" Background="#f5f7fe" HorizontalAlignment="Stretch"> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <TextBlock Text="{Binding Prop0}" Foreground="#62686E" Grid.Column="0" HorizontalAlignment="Left" FontWeight="SemiBold"/> <TextBlock Text="{Binding Prop1}" Foreground="#62686E" Grid.Column="1" HorizontalAlignment="Left" FontWeight="SemiBold"/> <TextBlock Text="{Binding Prop2}" Foreground="#62686E" Grid.Column="2" HorizontalAlignment="Left" FontWeight="SemiBold"/> <TextBlock Text="{Binding Prop3}" Foreground="#62686E" Grid.Column="3" HorizontalAlignment="Left" FontWeight="SemiBold"/> <TextBlock Text="{Binding Prop4}" Foreground="#62686E" Grid.Column="4" HorizontalAlignment="Left" FontWeight="SemiBold"/> <TextBlock Text="{Binding Prop5}" Foreground="#62686E" Grid.Column="5" HorizontalAlignment="Left" FontWeight="SemiBold"/> <TextBlock Text="{Binding Prop6}" Foreground="#62686E" Grid.Column="6" HorizontalAlignment="Left" FontWeight="SemiBold"/> </Grid> </DataTemplate> <!-- 模板2:Card样式 --> <DataTemplate x:Key="CardTemplate"> <Grid VerticalAlignment="Stretch" HorizontalAlignment="Stretch"> <Grid Margin="3,16,8,16"> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <StackPanel Grid.Column="0" Orientation="Vertical"> <TextBlock AutomationProperties.AutomationId="OrderNo" Foreground="{ThemeResource Foreground}" Style="{StaticResource DefaultMediumTextBlockStyle}" HorizontalAlignment="Left" Text="{Binding Prop0}" /> <TextBlock TextTrimming="CharacterEllipsis" Margin="0,8,0,0" AutomationProperties.AutomationId="UserName" Foreground="{ThemeResource Foreground}" MaxWidth="200" HorizontalAlignment="Left" Text="{Binding Prop1}" /> </StackPanel> <StackPanel Grid.Column="1" Orientation="Vertical" HorizontalAlignment="Right"> <TextBlock AutomationProperties.AutomationId="OrderAmount" Foreground="{ThemeResource Foreground}" Margin="0,0,0,0" HorizontalAlignment="Right" Text="{Binding Prop2}" /> <TextBlock Margin="0,8,0,0" Foreground="{Binding StatusColor}" AutomationProperties.AutomationId="Status" HorizontalAlignment="Right" Text="{Binding StatusText}" /> </StackPanel> </Grid> <Border BorderThickness="0,0,0,1" BorderBrush="{ThemeResource ListViewSeparatorBorderBrush}" Margin="-20,0,-6,0"/> </Grid> </DataTemplate> <local:OrderTemplateSelector x:Key="OrderTemplateSelector" GridTemplate="{StaticResource GridTemplate}" CardTemplate="{StaticResource CardTemplate}"/> </Page.Resources> <ListView ItemTemplateSelector="{StaticResource OrderTemplateSelector}" ItemsSource="{Binding Orders}"/>
切换视图时,只需要更新每个ItemViewModel的DisplayMode属性(或全局控制Selector的逻辑),ListView会自动切换模板且复用容器,减少闪烁。
2. 优化ListView虚拟化设置
确保ListView启用容器回收虚拟化,旧模板的容器会被回收复用给新模板,避免频繁创建销毁控件。在XAML中设置:
<ListView> <ListView.ItemsPanel> <ItemsPanelTemplate> <VirtualizingStackPanel VirtualizationMode="Recycling"/> </ItemsPanelTemplate> </ListView.ItemsPanel> </ListView>
注意:虚拟化需要ListView的Item容器高度可被正确测量,若模板高度差异大,可配合CacheLength属性调整缓存范围。
3. 给模板控件添加缓存优化
给模板的根元素设置CacheMode="BitmapCache",让系统将控件渲染为位图缓存,切换时减少重绘开销:
<DataTemplate x:Key="GridTemplate"> <Grid CacheMode="BitmapCache" Padding="0,10,0,10" Background="#f5f7fe" HorizontalAlignment="Stretch"> <!-- 模板内容 --> </Grid> </DataTemplate>
4. 避免切换时触发不必要的集合刷新
如果绑定集合是ObservableCollection,切换模板时不要调用Clear()再重新添加数据,也不要触发不必要的CollectionChanged事件。若需更新视图,优先通过Selector方式,而非全量刷新集合。
内容的提问来源于stack exchange,提问作者Bevara Satyasairam

