WPF 应用 - 走马灯动画

1. 功能

  • 做一个图片集合控件,在指定大小的区域内,以走马灯的动画效果呈现图片。
  • 能根据图片的数量决定动画的方向。当图片集合的大小大于指定的大小,图片往反方向走,以便于能看到底部的图片;当图片集合的大小小于指定的大小时,图片往正方向走,以便于保证所有图片都一直在可视区域内。
  • 根据图片的数量,决定动画的时间,保持速度不变。

2. 效果

3. 代码

3.1 内部动画流动控件 FlowImagesUserControl
<UserControl x:Class="WpfAppTemplate.FlowImagesUserControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:WpfAppTemplate"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800" x:Name="selfUserControl">
    <StackPanel Orientation="Horizontal">
        <ItemsControl x:Name="itemsControl" 
                      ItemsSource="{Binding}"                   
                      Loaded="itemsControl_Loaded"
                      MouseEnter="itemsControl_MouseEnter"
                      MouseLeave="itemsControl_MouseLeave">
            <ItemsControl.RenderTransform>
                <TransformGroup>
                    <TranslateTransform X="0" Y="0"></TranslateTransform>
                </TransformGroup>
            </ItemsControl.RenderTransform>

            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <StackPanel Orientation="Horizontal"/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>

            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Image Source="{Binding PicPath}" ToolTip="{Binding Name}" x:Name="img" 
                           Width="{Binding ImageWidth, ElementName=selfUserControl}"/>
                </DataTemplate>
            </ItemsControl.ItemTemplate>

            <ItemsControl.Resources>
                <Storyboard x:Key="storyBoard">
                    <DoubleAnimation Storyboard.TargetName="itemsControl"  
                                     Storyboard.TargetProperty="(RenderTransform).(TransformGroup.Children)[0].(TranslateTransform.X)" 
                                     To="{Binding ToX, ElementName=selfUserControl}" 
                                     Duration="{Binding Duration, ElementName=selfUserControl}"                                     
                                     AutoReverse="True"
                                     RepeatBehavior="Forever" />
                </Storyboard>
            </ItemsControl.Resources>
        </ItemsControl>
    </StackPanel>
</UserControl>
public partial class FlowImagesUserControl : UserControl
{
    /// <summary>
    /// 图片大小
    /// </summary>
    public double ImageWidth
    {
        get { return (double)GetValue(ImageWidthProperty); }
        set { SetValue(ImageWidthProperty, value); }
    }

    public static readonly DependencyProperty ImageWidthProperty =
        DependencyProperty.Register("ImageWidth", typeof(double), typeof(FlowImagesUserControl), new PropertyMetadata(60.0));

    /// <summary>
    /// 动画结束值
    /// </summary>
    public double ToX
    {
        get { return (double)GetValue(ToXProperty); }
        set { SetValue(ToXProperty, value); }
    }

    public static readonly DependencyProperty ToXProperty =
        DependencyProperty.Register("ToX", typeof(double), typeof(FlowImagesUserControl), new PropertyMetadata(500.0));
        
    /// <summary>
    /// 动画时间
    /// </summary>
    public Duration Duration
    {
        get { return (Duration)GetValue(DurationProperty); }
        set { SetValue(DurationProperty, value); }
    }

    public static readonly DependencyProperty DurationProperty =
        DependencyProperty.Register("Duration", typeof(Duration), typeof(FlowImagesUserControl), new PropertyMetadata(new Duration(new System.TimeSpan(0,0,2))));

    private Storyboard _storyboard;
    
    public FlowItemsUserControl()
    {
        InitializeComponent();

        _storyboard = itemsControl.FindResource("storyBoard") as Storyboard;

        itemsControl.SizeChanged += (s, e) =>
        {
            if (this.IsLoaded)
            {
                InitParamsOfStoryboard();
            }
        };
    }

    private void itemsControl_Loaded(object sender, RoutedEventArgs e)
    {
        InitParamsOfStoryboard(); 
    }

    private void itemsControl_MouseEnter(object sender, MouseEventArgs e)
    {             
        _storyboard.Pause();
    }

    private void itemsControl_MouseLeave(object sender, MouseEventArgs e)
    {
        _storyboard.Resume();
    }

    private void InitParamsOfStoryboard()
    {
        _storyboard.Stop();

        // 当集合的大小发生改变时,根据集合的大小决定动画的位移
        ToX = this.ActualWidth - itemsControl.ActualWidth;

        // 根据位移来决定动画的总时间,以每秒走 25px 的速度,避免不同位移相同时间导致速度不可控
        Duration = new Duration(new System.TimeSpan(0, 0, System.Math.Abs((int)ToX/25)));

        _storyboard.Begin();
    }
}
3.2 在窗口中添加 FlowImagesUserControl 控件
<local:FlowItemsUserControl DataContext="{Binding Cars}" ImageWidth="100"/>
<Button Grid.Row="1" Content="删除一辆车" Click="Button_Click" 
        Width="100" Background="DarkSalmon"/>       
public partial class ContentWindow : Window
{        
    public ContentWindow()
    {
        InitializeComponent();
        this.DataContext = new CarsViewModel();
    }
    
    private void Button_Click_DeleteACar(object sender, RoutedEventArgs e)
    {            
        var carList = (this.DataContext as CarsViewModel).Cars;
    
        if (carList != null && carList.Count > 0)
        {
            carList.RemoveAt(carList.Count - 1);
        }
    }
}
public class Car
{        
    public string Name { get; set; }
    public string PicPath { get; set; }        
}

public class NotifyPropertyChanged : System.ComponentModel.INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public void OnProperty(string propertyName)
    {
        PropertyChangedEventHandler propertyChanged = PropertyChanged;
        if (propertyChanged != null)
        {
            propertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

public class CarsViewModel : NotifyPropertyChanged
{
    private ObservableCollection<Car> _Cars;
    public ObservableCollection<Car> Cars
    {
        get { return _Cars; }
        set 
        {
            _Cars = value;
            OnProperty("Cars");
        }
    }        

    public CarsViewModel()
    {
        InitCars();
    }

    private void InitCars()
    {
        string path = AppDomain.CurrentDomain.BaseDirectory + "/Resources/Images/Cars";
        if (!Directory.Exists(path))
        {
            return;
        }

        Cars = new ObservableCollection<Car>();

        // 获取文件夹下的所有车辆图片
        FileInfo[] fileArr = new DirectoryInfo(path).GetFiles();

        for (int i = 0, l = fileArr.Count(); i < l; i++)
        {
            Cars.Add(new Car()
            {
                PicPath = fileArr[i].FullName,
                Name = fileArr[i].Name.Split('.')[0] //fileArr[i].Name = 宝马.jpg
            });
        }
    }
}
3.3 鼠标移入图片效果

鼠标移入图片时,动画会停止。这里再给图片添加一个鼠标移入效果,当鼠标移入时,图片变大,鼠标移出后,图片恢复大小。

<Style TargetType="Image" x:Key="MoveInBiggerImageStyle">
    <Setter Property="RenderTransform">
        <Setter.Value>
            <TransformGroup>
                <ScaleTransform/>
            </TransformGroup>
        </Setter.Value>
    </Setter>
    <Style.Triggers>
        <Trigger Property="IsMouseOver" Value="True">
            <Setter Property="Cursor" Value="Hand"/>
            <Trigger.EnterActions>
                <BeginStoryboard>
                    <Storyboard>
                        <DoubleAnimation Storyboard.TargetProperty="(RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)"
                                         To="1.1" Duration="0:0:0.2"/>
                        <DoubleAnimation Storyboard.TargetProperty="(RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)"
                                         To="1.1" Duration="0:0:0.2"/>
                    </Storyboard>
                </BeginStoryboard>
            </Trigger.EnterActions>
            <Trigger.ExitActions>
                <BeginStoryboard>
                    <Storyboard>
                        <DoubleAnimation Storyboard.TargetProperty="(RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)"
                                         To="1" Duration="0:0:0.5"/>
                        <DoubleAnimation Storyboard.TargetProperty="(RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)"
                                         To="1" Duration="0:0:0.5"/>
                    </Storyboard>
                </BeginStoryboard>
            </Trigger.ExitActions>
        </Trigger>
    </Style.Triggers>
</Style>

...

<Image x:Name="img" 
       Source="{Binding PicPath}"
       ToolTip="{Binding Name}"                           
       Width="{Binding ImageWidth, ElementName=selfUserControl}" 
       Style="{StaticResource MoveInBiggerImageStyle}">
</Image>

posted @ 2021-03-11 21:46  鑫茂  阅读(138)  评论(0编辑  收藏  举报