C# WPF 时钟动画(2/2)

模拟实现时钟效果,学习WPF动画好例子:实现了Xaml及C#代码两种方式添加动画的代码,方便对比学习。

微信公众号:Dotnet9,网站:Dotnet9,问题或建议:请网站留言, 如果对您有所帮助:欢迎赞赏

C# WPF 时钟动画(2/2)

内容目录

  1. 实现效果
  2. 业务场景
  3. 编码实现
  4. 本文参考
  5. 源码下载

1.实现效果

C# WPF时钟

时钟实时展示系统本机时间 

2.业务场景

模拟时钟

3.编码实现

使用 .Net Core 3.1 创建名为 “Clock” 的WPF解决方案,解决方案中需要添加时钟背景图片,图片如下:https://github.com/Abel13/Clock/blob/master/Clock/Assets/clock.png

3.1 主窗体 MainWindow.xaml

使用3个Border布局时钟的时针、分针、秒针,并给3个指针添加动画,动画说明如下:

  1. 时针每12个小时循环一圈(360°),每个小时旋转30°(30°*12=360°);
  2. 分针每60分钟循环一圈(360°),每分钟旋转6°(6°*60=360°);
  3. 秒针每60秒循环一圈(360°),每秒钟旋转6°(6°*60=360°),并且秒针旋转6°有个轻微的摆动动画。
<Window x:Class="Clock.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Clock"
        mc:Ignorable="d"
        AllowsTransparency="True" WindowStyle="None" ResizeMode="NoResize"
        Height="520" Width="520" WindowStartupLocation="CenterScreen" Background="{x:Null}">
    <Window.Resources>
        <Storyboard x:Key="sbseconds" RepeatBehavior="Forever">
            <DoubleAnimationUsingKeyFrames Storyboard.TargetName="second" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateTransform.Angle)">
                <EasingDoubleKeyFrame KeyTime="0" Value="-90">
                    <EasingDoubleKeyFrame.EasingFunction>
                        <BackEase EasingMode="EaseOut" Amplitude="0.4"/>
                    </EasingDoubleKeyFrame.EasingFunction>
                </EasingDoubleKeyFrame>
                <EasingDoubleKeyFrame KeyTime="00:00:1" Value="-84">
                    <EasingDoubleKeyFrame.EasingFunction>
                        <BackEase EasingMode="EaseOut" Amplitude="0.4"/>
                    </EasingDoubleKeyFrame.EasingFunction>
                </EasingDoubleKeyFrame>
                <EasingDoubleKeyFrame KeyTime="00:00:2" Value="-78">
                    <EasingDoubleKeyFrame.EasingFunction>
                        <BackEase EasingMode="EaseOut" Amplitude="0.4"/>
                    </EasingDoubleKeyFrame.EasingFunction>
                </EasingDoubleKeyFrame>
                <EasingDoubleKeyFrame KeyTime="00:00:3" Value="-72">
                    <EasingDoubleKeyFrame.EasingFunction>
                        <BackEase EasingMode="EaseOut" Amplitude="0.4"/>
                    </EasingDoubleKeyFrame.EasingFunction>
                </EasingDoubleKeyFrame>
                <EasingDoubleKeyFrame KeyTime="00:00:4" Value="-66">
                    <EasingDoubleKeyFrame.EasingFunction>
                        <BackEase EasingMode="EaseOut" Amplitude="0.4"/>
                    </EasingDoubleKeyFrame.EasingFunction>
                </EasingDoubleKeyFrame>
                <EasingDoubleKeyFrame KeyTime="00:00:5" Value="-60">
                    <EasingDoubleKeyFrame.EasingFunction>
                        <BackEase EasingMode="EaseOut" Amplitude="0.4"/>
                    </EasingDoubleKeyFrame.EasingFunction>
                </EasingDoubleKeyFrame>
                <EasingDoubleKeyFrame KeyTime="00:00:6" Value="-54">
                    <EasingDoubleKeyFrame.EasingFunction>
                        <BackEase EasingMode="EaseOut" Amplitude="0.4"/>
                    </EasingDoubleKeyFrame.EasingFunction>
                </EasingDoubleKeyFrame>
                <EasingDoubleKeyFrame KeyTime="00:00:7" Value="-48">
                    <EasingDoubleKeyFrame.EasingFunction>
                        <BackEase EasingMode="EaseOut" Amplitude="0.4"/>
                    </EasingDoubleKeyFrame.EasingFunction>
                </EasingDoubleKeyFrame>
                <EasingDoubleKeyFrame KeyTime="00:00:8" Value="-42">
                    <EasingDoubleKeyFrame.EasingFunction>
                        <BackEase EasingMode="EaseOut" Amplitude="0.4"/>
                    </EasingDoubleKeyFrame.EasingFunction>
                </EasingDoubleKeyFrame>
                <EasingDoubleKeyFrame KeyTime="00:00:9" Value="-36">
                    <EasingDoubleKeyFrame.EasingFunction>
                        <BackEase EasingMode="EaseOut" Amplitude="0.4"/>
                    </EasingDoubleKeyFrame.EasingFunction>
                </EasingDoubleKeyFrame>
                <EasingDoubleKeyFrame KeyTime="00:00:10" Value="-30">
                    <EasingDoubleKeyFrame.EasingFunction>
                        <BackEase EasingMode="EaseOut" Amplitude="0.4"/>
                    </EasingDoubleKeyFrame.EasingFunction>
                </EasingDoubleKeyFrame>
                <EasingDoubleKeyFrame KeyTime="00:00:11" Value="-24">
                    <EasingDoubleKeyFrame.EasingFunction>
                        <BackEase EasingMode="EaseOut" Amplitude="0.4"/>
                    </EasingDoubleKeyFrame.EasingFunction>
                </EasingDoubleKeyFrame>
                <EasingDoubleKeyFrame KeyTime="00:00:12" Value="-18">
                    <EasingDoubleKeyFrame.EasingFunction>
                        <BackEase EasingMode="EaseOut" Amplitude="0.4"/>
                    </EasingDoubleKeyFrame.EasingFunction>
                </EasingDoubleKeyFrame>
                <EasingDoubleKeyFrame KeyTime="00:00:13" Value="-12">
                    <EasingDoubleKeyFrame.EasingFunction>
                        <BackEase EasingMode="EaseOut" Amplitude="0.4"/>
                    </EasingDoubleKeyFrame.EasingFunction>
                </EasingDoubleKeyFrame>
                <EasingDoubleKeyFrame KeyTime="00:00:14" Value="-6">
                    <EasingDoubleKeyFrame.EasingFunction>
                        <BackEase EasingMode="EaseOut" Amplitude="0.4"/>
                    </EasingDoubleKeyFrame.EasingFunction>
                </EasingDoubleKeyFrame>
                <EasingDoubleKeyFrame KeyTime="00:00:15" Value="0">
                    <EasingDoubleKeyFrame.EasingFunction>
                        <BackEase EasingMode="EaseOut" Amplitude="0.4"/>
                    </EasingDoubleKeyFrame.EasingFunction>
                </EasingDoubleKeyFrame>
                <EasingDoubleKeyFrame KeyTime="00:00:16" Value="6">
                    <EasingDoubleKeyFrame.EasingFunction>
                        <BackEase EasingMode="EaseOut" Amplitude="0.4"/>
                    </EasingDoubleKeyFrame.EasingFunction>
                </EasingDoubleKeyFrame>
                <EasingDoubleKeyFrame KeyTime="00:00:17" Value="12">
                    <EasingDoubleKeyFrame.EasingFunction>
                        <BackEase EasingMode="EaseOut" Amplitude="0.4"/>
                    </EasingDoubleKeyFrame.EasingFunction>
                </EasingDoubleKeyFrame>
                <EasingDoubleKeyFrame KeyTime="00:00:18" Value="18">
                    <EasingDoubleKeyFrame.EasingFunction>
                        <BackEase EasingMode="EaseOut" Amplitude="0.4"/>
                    </EasingDoubleKeyFrame.EasingFunction>
                </EasingDoubleKeyFrame>
                <EasingDoubleKeyFrame KeyTime="00:00:19" Value="24">
                    <EasingDoubleKeyFrame.EasingFunction>
                        <BackEase EasingMode="EaseOut" Amplitude="0.4"/>
                    </EasingDoubleKeyFrame.EasingFunction>
                </EasingDoubleKeyFrame>
                <EasingDoubleKeyFrame KeyTime="00:00:20" Value="30">
                    <EasingDoubleKeyFrame.EasingFunction>
                        <BackEase EasingMode="EaseOut" Amplitude="0.4"/>
                    </EasingDoubleKeyFrame.EasingFunction>
                </EasingDoubleKeyFrame>
                <EasingDoubleKeyFrame KeyTime="00:00:21" Value="36">
                    <EasingDoubleKeyFrame.EasingFunction>
                        <BackEase EasingMode="EaseOut" Amplitude="0.4"/>
                    </EasingDoubleKeyFrame.EasingFunction>
                </EasingDoubleKeyFrame>
                <EasingDoubleKeyFrame KeyTime="00:00:22" Value="42">
                    <EasingDoubleKeyFrame.EasingFunction>
                        <BackEase EasingMode="EaseOut" Amplitude="0.4"/>
                    </EasingDoubleKeyFrame.EasingFunction>
                </EasingDoubleKeyFrame>
                <EasingDoubleKeyFrame KeyTime="00:00:23" Value="48">
                    <EasingDoubleKeyFrame.EasingFunction>
                        <BackEase EasingMode="EaseOut" Amplitude="0.4"/>
                    </EasingDoubleKeyFrame.EasingFunction>
                </EasingDoubleKeyFrame>
                <EasingDoubleKeyFrame KeyTime="00:00:24" Value="54">
                    <EasingDoubleKeyFrame.EasingFunction>
                        <BackEase EasingMode="EaseOut" Amplitude="0.4"/>
                    </EasingDoubleKeyFrame.EasingFunction>
                </EasingDoubleKeyFrame>
                <EasingDoubleKeyFrame KeyTime="00:00:25" Value="60">
                    <EasingDoubleKeyFrame.EasingFunction>
                        <BackEase EasingMode="EaseOut" Amplitude="0.4"/>
                    </EasingDoubleKeyFrame.EasingFunction>
                </EasingDoubleKeyFrame>
                <EasingDoubleKeyFrame KeyTime="00:00:26" Value="66">
                    <EasingDoubleKeyFrame.EasingFunction>
                        <BackEase EasingMode="EaseOut" Amplitude="0.4"/>
                    </EasingDoubleKeyFrame.EasingFunction>
                </EasingDoubleKeyFrame>
                <EasingDoubleKeyFrame KeyTime="00:00:27" Value="72">
                    <EasingDoubleKeyFrame.EasingFunction>
                        <BackEase EasingMode="EaseOut" Amplitude="0.4"/>
                    </EasingDoubleKeyFrame.EasingFunction>
                </EasingDoubleKeyFrame>
                <EasingDoubleKeyFrame KeyTime="00:00:28" Value="78">
                    <EasingDoubleKeyFrame.EasingFunction>
                        <BackEase EasingMode="EaseOut" Amplitude="0.4"/>
                    </EasingDoubleKeyFrame.EasingFunction>
                </EasingDoubleKeyFrame>
                <EasingDoubleKeyFrame KeyTime="00:00:29" Value="84">
                    <EasingDoubleKeyFrame.EasingFunction>
                        <BackEase EasingMode="EaseOut" Amplitude="0.4"/>
                    </EasingDoubleKeyFrame.EasingFunction>
                </EasingDoubleKeyFrame>
                <EasingDoubleKeyFrame KeyTime="00:00:30" Value="90">
                    <EasingDoubleKeyFrame.EasingFunction>
                        <BackEase EasingMode="EaseOut" Amplitude="0.4"/>
                    </EasingDoubleKeyFrame.EasingFunction>
                </EasingDoubleKeyFrame>
            </DoubleAnimationUsingKeyFrames>
        </Storyboard>
    </Window.Resources>
    <Window.Triggers>
        <EventTrigger RoutedEvent="FrameworkElement.Loaded">
            <BeginStoryboard Storyboard="{StaticResource sbseconds}"/>
        </EventTrigger>
    </Window.Triggers>
    <Grid>
        <Border BorderThickness="10" BorderBrush="Black" CornerRadius="300" Width="520" Height="520">
            <Grid Height="500" Width="500" HorizontalAlignment="Center" VerticalAlignment="Center">
                <Border CornerRadius="350">
                    <Border.Background>
                        <ImageBrush ImageSource="Assets/clock.png"/>
                    </Border.Background>
                </Border>

                <Border CornerRadius="350" Background="#778889BB"/>

                <Border x:Name="hour" CornerRadius="0 15 15 0" Height="10" Width="130" BorderThickness="3" BorderBrush="#FF66B01C" Margin="0,245,120,245" HorizontalAlignment="Right" RenderTransformOrigin="0,0.5">
                    <Border.RenderTransform>
                        <TransformGroup>
                            <ScaleTransform/>
                            <SkewTransform/>
                            <RotateTransform Angle="-90"/>
                            <TranslateTransform/>
                        </TransformGroup>
                    </Border.RenderTransform>
                </Border>
                <Border x:Name="minute" CornerRadius="0 15 15 0" Height="5" Width="160" BorderThickness="3" BorderBrush="#FFD15209" Margin="250.5,247,0,248" HorizontalAlignment="Left" RenderTransformOrigin="0,0.5">
                    <Border.RenderTransform>
                        <TransformGroup>
                            <ScaleTransform/>
                            <SkewTransform/>
                            <RotateTransform Angle="-90"/>
                            <TranslateTransform/>
                        </TransformGroup>
                    </Border.RenderTransform>
                </Border>
                <Border x:Name="second" CornerRadius="0 15 15 0" Height="3" Width="220" Background="Red" Margin="0,248,30,248" HorizontalAlignment="Right" RenderTransformOrigin="0,0.5">
                    <Border.RenderTransform>
                        <TransformGroup>
                            <ScaleTransform/>
                            <SkewTransform/>
                            <RotateTransform Angle="-90"/>
                            <TranslateTransform/>
                        </TransformGroup>
                    </Border.RenderTransform>
                </Border>
                <Ellipse Fill="Black" Width="20" Height="20" HorizontalAlignment="Center" VerticalAlignment="Center"/>
            </Grid>
        </Border>
    </Grid>
</Window>

3.2 MainWindow.xaml.cs

后台代码开启三个指针动画,并设置各指针动画当前系统时间的实时起始位置,注意Storyboard.Seek方法的调用,时针是由当前小时数+分钟数+秒数设置的起始旋转角度,分针是由当前分钟数+秒数设置的起始旋转角度,秒针是由当前秒数设置的起始角度:

using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media.Animation;

namespace Clock
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            int now_hours = DateTime.Now.Hour > 12 ? (DateTime.Now.Hour - 12) : DateTime.Now.Hour;
            int now_minutes = DateTime.Now.Minute;
            int now_seconds = DateTime.Now.Second;

            Storyboard seconds = (Storyboard)second.FindResource("sbseconds");
            seconds.Begin();
            seconds.Seek(new TimeSpan(0, 0, 0, now_seconds, 0));

            Storyboard minutes = (Storyboard)minute.FindResource("sbminutes");
            minutes.Begin();
            minutes.Seek(new TimeSpan(0, 0, now_minutes, now_seconds, 0));

            Storyboard hours = (Storyboard)hour.FindResource("sbhours");
            hours.Begin();
            hours.Seek(new TimeSpan(0, now_hours, now_minutes, now_seconds, 0));
        }

        private void Window_MouseDown(object sender, MouseButtonEventArgs e)
        {
            DragMove();
        }
    }
}

3.2 第二版代码

有博客园园友提出疑问:文章跳转

C# WPF 时钟动画(2/2)

站长再仔细看了代码,并运行,没有问题哈,能比较准确的显示时针和分针旋转角度,后台代码设置了时针、分针、秒针的Seek时刻,看下图,2点30分17秒左右的截图:

C# WPF 时钟动画(2/2)

下面我用C#代码实现了Xaml中的动画代码,Xaml中动画代码看着比较多,不够清爽,大家可以注释掉资源里面的动画代码,使用下面的C#代码实现:

using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media.Animation;

namespace Clock
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            int nowHours = DateTime.Now.Hour > 12 ? (DateTime.Now.Hour - 12) : DateTime.Now.Hour;
            int nowMinutes = DateTime.Now.Minute;
            int nowSeconds = DateTime.Now.Second;

            //1、根据当前系统时间,设置分针起始位置
            //1.1 根据界面中动画,直接Seek到指定秒
            //Storyboard seconds = (Storyboard)second.FindResource("sbseconds");
            //seconds.Begin();
            //seconds.Seek(new TimeSpan(0, 0, 0, nowSeconds, 0));
            //1.2 界面动画代码比较多,使用代码看着清爽一点
            AddSecondAnimation(nowSeconds);

            //2、根据当前系统时间,设置分针起始位置
            //2.1 根据界面中动画,直接Seek到指定的分钟数及当前秒数
            //Storyboard minutes = (Storyboard)minute.FindResource("sbminutes");
            //minutes.Begin();
            //minutes.Seek(new TimeSpan(0, 0, nowMinutes, nowSeconds, 0));
            //2.2 界面动画代码不多,统一使用代码实现动画试试
            AddMinuteAnimation(nowMinutes, nowSeconds);

            //3、根据当前系统时间,设置时针起始位置
            //3.1 根据界面中动画,直接Seek到指定的小时数、分钟数及秒数
            //Storyboard hours = (Storyboard)hour.FindResource("sbhours");
            //hours.Begin();
            //hours.Seek(new TimeSpan(0, nowHours, nowMinutes, nowSeconds, 0));
            //3.2 界面动画代码不多,统一使用代码实现动画试试
            AddHourAnimation(nowHours, nowMinutes, nowSeconds);
        }

        /// <summary>
        /// 添加秒针动画,MainWindow.xaml中
        /// </summary>
        /// <param name="nowSeconds">实时秒数</param>
        private void AddSecondAnimation(int nowSeconds)
        {
            Storyboard st = new Storyboard();
            st.RepeatBehavior = RepeatBehavior.Forever;

            DoubleAnimationUsingKeyFrames da = new DoubleAnimationUsingKeyFrames();
            st.Children.Add(da);

            Storyboard.SetTarget(st, second);
            Storyboard.SetTargetProperty(st, new PropertyPath("(UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateTransform.Angle)"));
            for (int i = 0; i < 60; i++)
            {
                EasingDoubleKeyFrame ed = new EasingDoubleKeyFrame();
                da.KeyFrames.Add(ed);

                ed.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(i));     //每秒旋转
                ed.Value = -90 + 6 * i;                                         //秒针旋转一圈60秒,每秒旋转6度,-90为12点钟角度
                ed.EasingFunction = new BackEase
                {
                    EasingMode = EasingMode.EaseOut,
                    Amplitude = 0.4
                };
            }

            st.Begin();
            st.Seek(new TimeSpan(0, 0, 0, nowSeconds, 0));
        }

        /// <summary>
        /// 添加分针动画
        /// </summary>
        /// <param name="nowMinutes">实时分钟数</param>
        /// <param name="nowSeconds">实时秒数</param>
        private void AddMinuteAnimation(int nowMinutes, int nowSeconds)
        {
            Storyboard st = new Storyboard();
            st.RepeatBehavior = RepeatBehavior.Forever;

            DoubleAnimationUsingKeyFrames da = new DoubleAnimationUsingKeyFrames();
            st.Children.Add(da);

            Storyboard.SetTarget(st, minute);
            Storyboard.SetTargetProperty(st, new PropertyPath("(UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateTransform.Angle)"));
            for (int i = 0; i < 60; i++)
            {
                EasingDoubleKeyFrame ed = new EasingDoubleKeyFrame();
                da.KeyFrames.Add(ed);

                ed.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMinutes(i));     //每分旋转
                ed.Value = -90 + 6 * i;                                         //分针旋转一圈60分钟,每分钟旋转6度,-90为12点钟角度
                ed.EasingFunction = new BackEase
                {
                    EasingMode = EasingMode.EaseOut,
                    Amplitude = 0.4
                };
            }

            st.Begin();
            st.Seek(new TimeSpan(0, 0, nowMinutes, nowSeconds, 0));
        }

        /// <summary>
        /// 添加时针动画
        /// </summary>
        /// <param name="nowHours">实时小时数</param>
        /// <param name="nowMinutes">实时分钟数</param>
        /// <param name="nowSeconds">实时秒数</param>
        private void AddHourAnimation(int nowHours, int nowMinutes, int nowSeconds)
        {
            Storyboard st = new Storyboard();
            st.RepeatBehavior = RepeatBehavior.Forever;

            DoubleAnimationUsingKeyFrames da = new DoubleAnimationUsingKeyFrames();
            st.Children.Add(da);

            Storyboard.SetTarget(st, hour);
            Storyboard.SetTargetProperty(st, new PropertyPath("(UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateTransform.Angle)"));
            for (int i = 0; i <= 12; i++)
            {
                EasingDoubleKeyFrame ed = new EasingDoubleKeyFrame();
                da.KeyFrames.Add(ed);

                ed.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromHours(i));       //每小时旋转
                ed.Value = -90 + 30 * i;                                        //时针旋转一圈12个小时,每小时旋转30度,-90为12点钟角度
                ed.EasingFunction = new BackEase
                {
                    EasingMode = EasingMode.EaseOut,
                    Amplitude = 0.4
                };
            }

            st.Begin();
            st.Seek(new TimeSpan(0, nowHours, nowMinutes, nowSeconds, 0));
        }

        private void Window_MouseDown(object sender, MouseButtonEventArgs e)
        {
            DragMove();
        }
    }
}

4.本文参考

Design com WPF 大神的学习视频:

1/2 – Creating a clock with Animations

2/2 – Creating a clock with Animations

5.代码下载

源码中实现了全部效果,大伙可以直接下载编译运行;建议看大神视频手敲一遍,加深学习印象;视频中使用Blend布局时钟、设置动画,类似PS(PhotoShop),设计界面很是方便的。

Github源码下载:Clock

除非注明,文章均由 Dotnet9 整理发布,欢迎转载。

转载请注明:
作者:Dotnet9
链接:https://dotnet9.com/6858.html
来源:Dotnet9
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

发表评论

登录后才能评论

评论列表(1条)

  • 站长-Dotnet9
    Dotnet9 2020年1月11日 12:16

    后台代码重写了动画,看着代码更多的样子,不过对于大家学习是很有帮助的。