wpf 导航栏

制作了一个 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;
        }
    }
}

https://www.bilibili.com/video/BV1Ui4y1a717

上一篇
下一篇