UWP/XAML:如何扩大ListView鼠标悬停的Reveal效果(侧边扩展)
我之前也碰到过这个棘手的问题——默认的ListViewItemPresenter Reveal悬停效果被控件自身的布局边界卡得死死的,光改RevealBorderThickness根本没法让它往侧边延伸。结合你提供的默认模板,给你几个可行的实现思路:
思路一:用容器打破布局边界(最直接有效)
默认的ListViewItemPresenter是完全贴合ListViewItem的布局范围的,所以Reveal效果只能在这个框内显示。我们可以把Presenter放在一个更宽的Grid容器里,给Grid设置负的左右Margin,让Presenter能“延伸”到Item的外侧,同时调整Content的位置保证内容不偏移。
修改你提供的模板,把直接放置ListViewItemPresenter的部分改成如下结构:
<ControlTemplate TargetType="ListViewItem"> <!-- 用Grid包裹,左右各向外扩展8px,数值可按需调整 --> <Grid Margin="-8,0,-8,0"> <ListViewItemPresenter x:Name="Root" CheckBrush="{ThemeResource ListViewItemCheckBrush}" <!-- 调整ContentMargin,抵消Grid的负Margin,保证内容位置不变 --> <!-- 原Padding是12,0,12,0,加上Grid向外扩展的8px,所以ContentMargin设为20,0,20,0 --> ContentMargin="20,0,20,0" CheckBoxBrush="{ThemeResource ListViewItemCheckBoxBrush}" ContentTransitions="{TemplateBinding ContentTransitions}" CheckMode="{ThemeResource ListViewItemCheckMode}" DragOpacity="{ThemeResource ListViewItemDragThemeOpacity}" DisabledOpacity="{ThemeResource ListViewItemDisabledThemeOpacity}" DragBackground="{ThemeResource ListViewItemDragBackground}" DragForeground="{ThemeResource ListViewItemDragForeground}" FocusBorderBrush="{ThemeResource ListViewItemFocusBorderBrush}" FocusVisualMargin="{TemplateBinding FocusVisualMargin}" FocusSecondaryBorderBrush="{ThemeResource ListViewItemFocusSecondaryBorderBrush}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" Control.IsTemplateFocusTarget="True" PressedBackground="{ThemeResource ListViewItemBackgroundPressed}" PlaceholderBackground="{ThemeResource ListViewItemPlaceholderBackground}" PointerOverForeground="{ThemeResource ListViewItemForegroundPointerOver}" PointerOverBackground="{ThemeResource ListViewItemBackgroundPointerOver}" RevealBorderThickness="2" ReorderHintOffset="{ThemeResource ListViewItemRevealBorderThemeOffset}" RevealBorderBrush="{ThemeResource ListViewItemRevealBorderBrush}" RevealBackground="{ThemeResource ListViewItemRevealBackground}" SelectedForeground="{ThemeResource ListViewItemForegroundSelected}" SelectionCheckMarkVisualEnabled="{ThemeResource ListViewItemSelectionCheckMarkVisualEnabled}" SelectedBackground="{ThemeResource ListViewItemBackgroundSelected}" SelectedPressedBackground="{ThemeResource ListViewItemBackgroundSelectedPressed}" SelectedPointerOverBackground="{ThemeResource ListViewItemBackgroundSelectedPointerOver}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"> <!-- 原VisualStateGroups保持不变 --> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="CommonStates"> <VisualState x:Name="Normal"/> <VisualState x:Name="Selected"/> <VisualState x:Name="PointerOver"> <VisualState.Setters> <Setter Target="Root.(RevealBrush.State)" Value="PointerOver"/> <Setter Target="Root.RevealBorderBrush" Value="{ThemeResource ListViewItemRevealBorderBrushPointerOver}"/> </VisualState.Setters> </VisualState> <VisualState x:Name="PointerOverSelected"> <VisualState.Setters> <Setter Target="Root.(RevealBrush.State)" Value="PointerOver"/> <Setter Target="Root.RevealBorderBrush" Value="{ThemeResource ListViewItemRevealBorderBrushPointerOver}"/> </VisualState.Setters> </VisualState> <VisualState x:Name="PointerOverPressed"> <VisualState.Setters> <Setter Target="Root.(RevealBrush.State)" Value="Pressed"/> <Setter Target="Root.RevealBorderBrush" Value="{ThemeResource ListViewItemRevealBorderBrushPressed}"/> </VisualState.Setters> </VisualState> <VisualState x:Name="Pressed"> <VisualState.Setters> <Setter Target="Root.(RevealBrush.State)" Value="Pressed"/> <Setter Target="Root.RevealBorderBrush" Value="{ThemeResource ListViewItemRevealBorderBrushPressed}"/> </VisualState.Setters> </VisualState> <VisualState x:Name="PressedSelected"> <VisualState.Setters> <Setter Target="Root.(RevealBrush.State)" Value="Pressed"/> <Setter Target="Root.RevealBorderBrush" Value="{ThemeResource ListViewItemRevealBorderBrushPressed}"/> </VisualState.Setters> </VisualState> </VisualStateGroup> <VisualStateGroup x:Name="DisabledStates"> <VisualState x:Name="Enabled"/> <VisualState x:Name="Disabled"> <VisualState.Setters> <Setter Target="Root.RevealBorderThickness" Value="0"/> </VisualState.Setters> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> </ListViewItemPresenter> </Grid> </ControlTemplate>
这里的核心逻辑是:Grid的负Margin让Presenter获得了额外的侧边空间,而调整后的ContentMargin确保Item内容仍保持在原来的视觉位置,不会跟着偏移。
思路二:自定义RevealBrush的作用范围
RevealBrush有个附加属性RevealBrush.TargetBoundingRect,可以直接指定它的作用范围。我们可以通过绑定+转换器的方式,把这个范围往左右两侧扩展:
第一步:添加转换器代码
public class RectExpanderConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, string language) { if (value is Size renderSize && int.TryParse(parameter?.ToString(), out int expandAmount)) { // 往左右各扩展指定像素,生成新的Rect范围 return new Rect(-expandAmount, 0, renderSize.Width + expandAmount * 2, renderSize.Height); } return Rect.Empty; } public object ConvertBack(object value, Type targetType, object parameter, string language) { throw new NotImplementedException(); } }
第二步:在模板中应用属性
在ListViewItemPresenter上添加RevealBrush.TargetBoundingRect绑定:
<ListViewItemPresenter x:Name="Root" RevealBorderThickness="2" <!-- 绑定RenderSize,通过转换器扩展左右各10px --> RevealBrush.TargetBoundingRect="{Binding RelativeSource={RelativeSource Self}, Path=RenderSize, Converter={StaticResource RectExpanderConverter}, ConverterParameter=10}" <!-- 其他原有属性保持不变... --> >
注意:如果ListView设置了ClipToBounds="True",扩展的Reveal效果会被父容器裁剪,需要把这个属性设为False才能看到完整效果。
思路三:调整Item的Margin和Padding(最省事)
直接给ListViewItem设置负的左右Margin,同时增大Padding值抵消负Margin的影响,这样Item的点击交互范围不变,但Reveal效果可以延伸到负Margin的区域:
<Style x:Key="ListViewItemStyle1" TargetType="ListViewItem"> <!-- 原有Setter不变,新增以下两个Setter --> <Setter Property="Margin" Value="-8,0,-8,0"/> <!-- 左右向外扩展8px --> <Setter Property="Padding" Value="20,0,20,0"/> <!-- 原Padding是12,0,12,0,加上8px保证内容位置不变 --> <!-- 原有Setter和Template保持不变... --> </Style>
这个方法的缺点是会影响ListView的Item间距,如果原来的Item有默认间距,需要同时调整ListView的ItemSpacing属性来抵消负Margin的影响。
内容的提问来源于stack exchange,提问作者nioKi

