制作了一个 ListBox 的子类
xaml
# 选中圆的边缘可以使用 blend 制作 ,见最后的链接中的视频演示
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:magicBarCtrls="clr-namespace:WpfDemoApp.MagicBarCtrls">
<!-- 每一项的数据模板 -->
<DataTemplate x:Key="MagicBarListBoxItemDataTemplate" DataType="{x:Type magicBarCtrls:MagicBarItem}">
<Grid>
<ContentControl
Width="38" Height="38"
HorizontalAlignment="Center" VerticalAlignment="Center"
Content="{Binding Icon}"
RenderTransformOrigin=".5 .5" TextElement.FontSize="38" TextElement.FontWeight="Bold">
<ContentControl.RenderTransform>
<!-- 平时 5 选中后 -32 -->
<TranslateTransform x:Name="IconTransform" Y="5" />
</ContentControl.RenderTransform>
</ContentControl>
<TextBlock
x:Name="NameTextBlock"
HorizontalAlignment="Center" VerticalAlignment="Center"
FontSize="14" FontWeight="Bold" Opacity="0" RenderTransformOrigin=".5 .5"
Text="{Binding Name}">
<TextBlock.RenderTransform>
<!-- 平时 20 , 选中后 5 -->
<TranslateTransform x:Name="NameTransform" Y="25" />
<!-- <TranslateTransform Y="5" /> -->
</TextBlock.RenderTransform>
</TextBlock>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem}, Path=IsSelected}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="IconTransform" Storyboard.TargetProperty="Y" To="-32" Duration="0:0:.3" />
<DoubleAnimation
Storyboard.TargetName="NameTextBlock" Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:.1" />
<DoubleAnimation
Storyboard.TargetName="NameTransform" Storyboard.TargetProperty="Y" To="20" Duration="0:0:.3" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="IconTransform" Storyboard.TargetProperty="Y" To="5" Duration="0:0:.3" />
<DoubleAnimation
Storyboard.TargetName="NameTextBlock" Storyboard.TargetProperty="Opacity" To="0" Duration="0:0:.1" />
<DoubleAnimation
Storyboard.TargetName="NameTransform" Storyboard.TargetProperty="Y" To="25" Duration="0:0:.3" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
<!-- 每一项中的模板, 我可能不需要 -->
<ControlTemplate x:Key="MagicBarListBoxItemControlTemplate" TargetType="{x:Type ListBoxItem}">
<Grid
Width="100" Height="70"
Background="Transparent">
<ContentPresenter Margin="5" />
</Grid>
</ControlTemplate>
<!-- 每一项的样式 -->
<Style
x:Key="MagicBarListBoxItemStyle"
BasedOn="{StaticResource {x:Type ListBoxItem}}"
TargetType="ListBoxItem">
<Setter Property="Template" Value="{StaticResource MagicBarListBoxItemControlTemplate}" />
<Setter Property="ContentTemplate" Value="{StaticResource MagicBarListBoxItemDataTemplate}" />
</Style>
<!-- 总的内容 -->
<ControlTemplate x:Key="MagicBarControlTemplate" TargetType="magicBarCtrls:MagicBar">
<Grid>
<!-- magic bar 的外边框 -->
<Grid
HorizontalAlignment="Center" VerticalAlignment="Bottom"
Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Border
Grid.Row="1"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
ClipToBounds="True" CornerRadius="10" />
<!-- 上面的圆球 -->
<Grid
x:Name="SelectedFlagGrid"
Grid.Row="1"
Width="100" Height="80"
Margin="10,-44.9,0,0" HorizontalAlignment="Left" VerticalAlignment="Top">
<Path Fill="{Binding RelativeSource={RelativeSource AncestorType=magicBarCtrls:MagicBar}, Path=SelectedBrush}" Stretch="Fill">
<Path.Data>
<PathGeometry Figures="M0,0 L100,0 100,37.5 100,50 93.849998,50 93.5,50 93.5,50.007744 93.399368,50.009972 C89.926254,50.163998 86.913689,52.089626 85.243385,54.905151 L84.995377,55.365295 84.988571,55.903366 C84.509415,74.81583 69.027935,90 50,90 30.972064,90 15.490582,74.81583 15.011431,55.903366 L15.004623,55.365295 14.756611,54.905151 C13.086311,52.089626 10.07375,50.163998 6.6006298,50.009972 L6.5,50.007744 6.5,50 6.1499996,50 0,50 0,37.5 z" />
</Path.Data>
</Path>
<Ellipse
Width="55" Height="55"
Margin="0,10,0,0"
Fill="#fdd70c" />
<Grid.RenderTransform>
<TranslateTransform x:Name="SelectedFlagGridTransform" X="400" />
</Grid.RenderTransform>
</Grid>
<ItemsPresenter
Grid.Row="1"
Margin="10,0,0,10" VerticalAlignment="Bottom" />
</Grid>
</Grid>
</ControlTemplate>
<ItemsPanelTemplate x:Key="MagicBarItemsPanelTemplate">
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
<!-- 样式 -->
<Style BasedOn="{StaticResource {x:Type ListBox}}" TargetType="magicBarCtrls:MagicBar">
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="UseLayoutRounding" Value="True" />
<Setter Property="Template" Value="{StaticResource MagicBarControlTemplate}" />
<Setter Property="ItemsPanel" Value="{StaticResource MagicBarItemsPanelTemplate}" />
<Setter Property="ItemContainerStyle" Value="{StaticResource MagicBarListBoxItemStyle}" />
</Style>
</ResourceDictionary>
cs
public class MagicBar : ListBox
{
public static readonly DependencyProperty SelectedBrushProperty = DependencyProperty.Register(
nameof(SelectedBrush), typeof(SolidColorBrush), typeof(MagicBar), new PropertyMetadata(new SolidColorBrush(Colors.BlueViolet)));
public MagicBar()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MagicBar), new FrameworkPropertyMetadata(typeof(MagicBar)));
this.Loaded += this.MagicBar_OnLoaded;
}
/// <summary>
/// 选择后的颜色
/// </summary>
public SolidColorBrush SelectedBrush
{
get => (SolidColorBrush)this.GetValue(SelectedBrushProperty);
set => this.SetValue(SelectedBrushProperty, value);
}
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
base.OnSelectionChanged(e);
// 计算移动动画
var lastItem = e.RemovedItems.Cast<MagicBarItem>().FirstOrDefault();
var barItem = e.AddedItems.Cast<MagicBarItem>().FirstOrDefault();
if (barItem == null)
{
return;
}
var index = this.Items.IndexOf(barItem);
if (index == -1)
{
return;
}
var lastIndex = lastItem == null ? -1 : this.Items.IndexOf(lastItem);
// 获取模板中名为 SelectedFlagGrid 的元素
if (this.Template.FindName("SelectedFlagGridTransform", this) is not TranslateTransform transform)
{
return;
}
// 原本根据距离计算移动速度,但是太远的话,会过慢,这里简单的只有一个时间来移动
var distance = Math.Abs(lastIndex == -1 ? 0 : index - lastIndex);
var animation = new DoubleAnimation
{
To = 100d * index,
// Duration = new Duration(distance * TimeSpan.FromMilliseconds(200)),
Duration = new Duration(1 * TimeSpan.FromMilliseconds(200)),
};
transform.BeginAnimation(TranslateTransform.XProperty, animation);
}
private void MagicBar_OnLoaded(object sender, RoutedEventArgs e)
{
if (sender is MagicBar { SelectedIndex: -1, Items.Count: > 0, } magicBar)
{
magicBar.SelectedIndex = 0;
}
}
}