WPF:(四)常见的动画及案例

一. 基本概念

WPF的一个特点就是支持动画,我们可以非常容易的实现漂亮大方的界面。首先,我们来复习一下动画的基本概念。计算机中的动画一般是定格动画,也称之为逐帧动画,它通过每帧不同的图像连续播放,从而欺骗眼和脑产生动画效果。

二. WPF的动画的实现

(1)简洁

这个是非常明显的,WPF的动画的代码非常容易理解,Timer的版本则要难懂得多。当然,我们也可以通过封装,使得用Timer也能用类似的API实现动画。但动画的API并不是仅仅这么一点,要把整个动画框架的API都封装也没有那么容易。

(2)和XAML无缝集成

这个就是WPF的独有技术了,得益于XAML强大的表述能力,我们可以写出非常强大且容易维护的动画。这点WinFom的Timer版本是无法做到的。

(3)流畅性

如果将这两种实现方式一起跑起来比较一下就会发现,Timer实现的版本明显要卡顿,并且并没有精准的按照我们设计的那样运动。具体原因为:

(4)Timer精度的问题:
由于是改UI控件的属性(按钮的宽度),因此必须在UI线程上进行,因此DispatcherTimer操作与其他操作一样需要放置到Dispatcher队列中,它并不保证恰好在改时间间隔中。它并不适合动画这种间隔很短的计时。

(5)帧率的问题:
逐帧动画的流畅性一般取决于每秒更新的帧数,也就是常说的帧率。人眼睛上限是70帧,而我这里代码中的Timer的固定了为20帧,因此是能明显感觉到卡顿的。而WPF的动画则不然,从它的API中可以看到,它是没有帧率的设置的。实际上,它是根据计算机的性能和当前进程的繁忙程度尽可能增大帧率的,因此WPF的动画是远大于20帧的,因此要流畅得多。

那么,是否只要修改参数,加大Timer的版本的帧率,也可以实现同样流畅的动画呢? 试了一下,就算修改参数,也是无法达到WPF版本的流畅程度的。我认为原因主要有如下两点,

DispatcherTimer精度不够,无法实现大帧率下准确刷新。

通过简单的设置参数很难像WPF那样帧率根据计算机的性能和当前进程的繁忙程度智能匹配帧率。帧率设置过低,动画不流畅,设置过大,处理不过来仍然不流畅。并且UI线程的忙碌程度是会动态变化的,帧率也需要相应调整,这些都无法通过Timer来简单的处理。

三. From/To/By 动画

这种过渡动画一般成为From/To/By 动画,是因为它们是通过From、To、By三个属性来决定了目标属性的起始值和结束值。首先我们来看下这三个属性代表的意义:

(1). From: 起始值,在动画开始的时候将目标属性设置为该值
(2). To: 结束值,动画结束是目标属性为改值
(3). By: 偏移值:动画结束的时候目标属性为"初始值+偏移值"

很明显,To和By的效果是有可能冲突的。实际上,这三个属性都是可选设置的,并且在设置了To和By的时候,是会忽略By属性的。下面我再通过一些简单的场景介绍一下这三个属性如何组合使用。

属性具有动画功能的要求

(1). 它必须是依赖项属性。
(2). 它必须属于继承自 DependencyObject 并实现 IAnimatable 接口的类。
(3). 必须存在可用的兼容动画类型.

四. 简单的动画实现(使元素从显示到透明)

(1) 先添加一个元素

 
        
    

(2)创建 DoubleAnimation(浮点动画)

使元素逐渐进入视野并逐渐从视野中消失的一种方法是对其 Opacity 属性进行动画处理。由于 Opacity 属性的类型是 Double,因此需要一个产生双精度值的动画。DoubleAnimation 就是这样的一个动画。DoubleAnimation 创建两个双精度值之间的过渡。若要指定其起始值,可设置其From属性。若要指定其终止值,可设置其To属性。

不透明度值1.0使对象完全不透明,不透明度值0.0使对象完全不可见。若要使动画的不透明度值从1.0过渡为 0.0,可以将其From属性设置为1.0,将其To属性设置为0.0。

(3)创建演示图板

若要向对象应用动画,请创建Storyboard并使用TargetName和TargetProperty附加属性指定要进行动画处理的对象和属性。

创建 Storyboard 并将动画添加为其子项。

并在动画中指定动画需要的三个必要属性

 
            
            
        

a. From:动画属性的起始值
b. To:动画属性的结束值
c. Duration:动画执行所需的时间
d. TargetName:执行这个动画的对象名称
e. TargetProperty:执行这个动画的对象的属性是哪个属性
可选属性

f. AutoReverse:设置是否往返播放
g. RepeatBehavior:设置播放的次数枚举RepeatBehavior.Forever设置永久重复
在动画中指定故事版需要的两个必要属性

必须知道要在哪里应用动画。使用 Storyboard…::.TargetName 附加属性指定要进行动画处理的对象。在下面的代码中,为 DoubleAnimation 指定了一个目标名称 MyRectangle,这是要进行动画处理的对象的名称。

使用 TargetProperty 附加属性指定要进行动画处理的属性。在下面的代码中,动画被配置为面向 Rectangle 的 Opacity 属性。

 
            
            
        

(4) 将演示图板与触发器关联

在 XAML中应用和启动Storyboard的最简单的方法是使用事件触发器。

创建一个BeginStoryboard对象并将演示图板与其关联。BeginStoryboard 是一种应用和启动 Storyboard 的 TriggerAction。

        
            
                
                    
                        
                            
                            
                        
                    
                
            
        

XAML实现动画过程

  1. 设置动画目标对象
  2. 设置动画执行的触发器:控制动画播放的基本条件
  3. 在触发器中注册路由事件:指定对象的哪个事件激活触发器
  4. 激活触发器条件下开始故事版执行
  5. 故事版中添加动画,指定动画的5个必要属性

注意

  1. 一个故事版中可以放N多个动画
  2. 一个故事版可以给多个对象使用

使用C#实现WPF动画

过程

  1. 先创建故事版
  2. 选择动画并创建动画
  3. 设置动画的5个必要属性
  4. 将动画添加到故事版中
  5. 通过故事版的方法来启动动画
 Storyboard sto = new Storyboard();
            DoubleAnimation da = new DoubleAnimation();
            da.From = 1.0;
            da.To = 0.0;
            //可选属性:是否往返播放
            da.AutoReverse = true;
            //da.RepeatBehavior = RepeatBehavior.Forever;
            da.RepeatBehavior = new RepeatBehavior(2);
            da.Duration = new Duration(TimeSpan.FromSeconds(3));
            Storyboard.SetTarget(da, br);
            Storyboard.SetTargetProperty(da, new PropertyPath("Opacity"));
            sto.Children.Add(da);
            sto.Begin();

例如:设置矩形的若隐若现,改变颜色,大小,移动,旋转
xaml代码如下:


    
        
        

WPF:(四)常见的动画及案例_第1张图片
cs代码如下

namespace c代码实现动画
{
    /// 
    /// MainWindow.xaml 的交互逻辑
    /// 
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
        Border br = new Border();
        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            br.Width = 100;
            br.Height = 100;
            //br.Background = Brushes.Red;
            br.Background = new SolidColorBrush(Colors.Red);
            Canvas.SetLeft(br,100);
            Canvas.SetTop(br,100);
            br.Opacity = 1.0;
            can.Children.Add(br);
        }
        //设置若隐若现
        private void Btnxiao_Click(object sender, RoutedEventArgs e)
        {
            if (btnxiao.Content.ToString() == "消失")
            {
                btnxiao.Content = "显示";
                Storyboard st = new Storyboard();
                DoubleAnimation da = new DoubleAnimation();
                da.From = 1.0;
                da.To = 0.0;
                da.Duration = new Duration(TimeSpan.FromSeconds(3));
                Storyboard.SetTarget(da,br);
                Storyboard.SetTargetProperty(da,new PropertyPath("Opacity"));
                st.Children.Add(da);
                st.Begin();
            }
            else
            {
                btnxiao.Content = "消失";
                Storyboard st = new Storyboard();
                DoubleAnimation da = new DoubleAnimation();
                da.From = 0.0;
                da.To = 1.0;
                da.Duration = new Duration(TimeSpan.FromSeconds(3));
                Storyboard.SetTarget(da, br);
                Storyboard.SetTargetProperty(da, new PropertyPath("Opacity"));
                st.Children.Add(da);
                st.Begin();
            }
        }
//设置改变背景颜色
        private void Btncolor_Click(object sender, RoutedEventArgs e)
        {
            if (btncolor.Content.ToString()=="改变颜色")
            {
                btncolor.Content = "原来颜色";
                Storyboard st = new Storyboard();
                ColorAnimation color = new ColorAnimation(Colors.Red, Colors.Green, new Duration(TimeSpan.FromSeconds(3)));
                Storyboard.SetTarget(color, br);
                Storyboard.SetTargetProperty(color, new PropertyPath("(Border.Background).(SolidColorBrush.Color)"));
                st.Children.Add(color);
                st.Begin();
            }
            else
            {
                btncolor.Content = "改变颜色";
                Storyboard st = new Storyboard();
                ColorAnimation color = new ColorAnimation(Colors.Green, Colors.Red, new Duration(TimeSpan.FromSeconds(3)));
                Storyboard.SetTarget(color, br);
                Storyboard.SetTargetProperty(color, new PropertyPath("(Border.Background).(SolidColorBrush.Color)"));
                st.Children.Add(color);
                st.Begin();
            }
           
        }
//设置大小的改变
        private void Btnsize_Click(object sender, RoutedEventArgs e)
        {
            if (btnsize.Content.ToString() == "改变大小")
            {
                btnsize.Content = "原来大小";
                Storyboard st = new Storyboard();
                DoubleAnimation dou = new DoubleAnimation(100, 300, new Duration(TimeSpan.FromSeconds(3)));
                Storyboard.SetTarget(dou, br);
                Storyboard.SetTargetProperty(dou, new PropertyPath("Width"));
                st.Children.Add(dou);
                DoubleAnimation dou1 = new DoubleAnimation(100, 300, new Duration(TimeSpan.FromSeconds(3)));
                Storyboard.SetTarget(dou1, br);
                Storyboard.SetTargetProperty(dou1, new PropertyPath("Height"));
                st.Children.Add(dou1);
                st.Begin();
            }
            else
            {
                btnsize.Content = "改变大小";
                Storyboard st = new Storyboard();
                DoubleAnimation dou = new DoubleAnimation(300, 100, new Duration(TimeSpan.FromSeconds(3)));
                Storyboard.SetTarget(dou, br);
                Storyboard.SetTargetProperty(dou, new PropertyPath("Width"));
                st.Children.Add(dou);
                DoubleAnimation dou1 = new DoubleAnimation(300, 100, new Duration(TimeSpan.FromSeconds(3)));
                Storyboard.SetTarget(dou1, br);
                Storyboard.SetTargetProperty(dou1, new PropertyPath("Height"));
                st.Children.Add(dou1);
                st.Begin();
            }
        }
//设置移动
        private void Btnyidong_Click(object sender, RoutedEventArgs e)
        {
            if (btnyidong.Content.ToString()=="移动")
            {
                btnyidong.Content = "原来位置";
                Storyboard st = new Storyboard();
                DoubleAnimation dos = new DoubleAnimation(Canvas.GetLeft(br), (Canvas.GetLeft(br)+300),new Duration(TimeSpan.FromSeconds(3)));
                Storyboard.SetTarget(dos,br);
                Storyboard.SetTargetProperty(dos,new PropertyPath("(Canvas.Left)"));
                st.Children.Add(dos);
                DoubleAnimation dos2 = new DoubleAnimation(Canvas.GetTop(br), (Canvas.GetTop(br) + 300), new Duration(TimeSpan.FromSeconds(3)));
                Storyboard.SetTarget(dos2, br);
                Storyboard.SetTargetProperty(dos2, new PropertyPath("(Canvas.Top)"));
                st.Children.Add(dos2);
                st.Completed += St_Completed;
                st.Begin();
            }
            else
            {
                btnyidong.Content = "移动";
                Storyboard st = new Storyboard();
                DoubleAnimation dos = new DoubleAnimation((Canvas.GetLeft(br)), (Canvas.GetLeft(br)-300), new Duration(TimeSpan.FromSeconds(3)));
                Storyboard.SetTarget(dos, br);
                Storyboard.SetTargetProperty(dos, new PropertyPath("(Canvas.Left)"));
                st.Children.Add(dos);
                DoubleAnimation dos2 = new DoubleAnimation((Canvas.GetTop(br)), (Canvas.GetTop(br)-300), new Duration(TimeSpan.FromSeconds(3)));
                Storyboard.SetTarget(dos2, br);
                Storyboard.SetTargetProperty(dos2, new PropertyPath("(Canvas.Top)"));
                st.Children.Add(dos2);
                //完成,当St故事板播放完成后自动执行这个事件,这个事件必须在st开启之前添加
                st.Completed += St_Completed1;
                st.Begin();
            }
        }
        private void St_Completed(object sender, EventArgs e)
        {
            sto.Begin();
        }
        private void St_Completed1(object sender, EventArgs e)
        {
            sto.Begin();
        }

       
//设置旋转
        Storyboard sto = new Storyboard();
        private void Btnxuan_Click(object sender, RoutedEventArgs e)
        {
            if (btnxuan.Content.ToString()=="旋转")
            {
                btnxuan.Content = "转回去";
                //添加一个旋转引用
               
                RotateTransform rtf = new RotateTransform();
                br.RenderTransform = rtf;
                //添加一个旋转中心
                br.RenderTransformOrigin = new Point(0.5, 0.5);  //0.5表示百分数


                DoubleAnimation da = new DoubleAnimation(0, 360, new Duration(TimeSpan.FromSeconds(3)));
                Storyboard.SetTarget(da, br);
                Storyboard.SetTargetProperty(da, new PropertyPath("RenderTransform.Angle"));
                sto.Children.Add(da);
                sto.Begin();

            }
            else
            {
                btnxuan.Content = "旋转";
                //添加一个旋转引用

                RotateTransform rtf = new RotateTransform();
                br.RenderTransform = rtf;
                //添加一个旋转中心
                br.RenderTransformOrigin = new Point(0.5, 0.5);  //0.5表示百分数


                DoubleAnimation da = new DoubleAnimation(360, 0, new Duration(TimeSpan.FromSeconds(3)));
                Storyboard.SetTarget(da, br);
                Storyboard.SetTargetProperty(da, new PropertyPath("RenderTransform.Angle"));
                sto.Children.Add(da);
                sto.Begin();
            }

        }

   
    }
}

案例:实现一个时钟:时分秒三个指针,时钟启动跟随系统时间
//需求分析:
//(1)先做一个圆形的钟表盘,里面有三个指针,时针比较短,下来是分针,秒针最长
//(2)获取当天的时间信息,
//(3)让表盘里面的每一指针都围绕着圆表盘的中心转动
//(4)每一个指针加上一个旋转动画

xaml代码如下:


    
        
    


cs代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;
namespace 时钟_作业题_
{
    /// 
    /// MainWindow.xaml 的交互逻辑
    /// 
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
           

        }
      
        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            //获取现在的时间
            DateTime time = DateTime.Now;
            int S = time.Second;
            int M = time.Minute;
            int H = time.Hour;


            //添加表盘
            Image br = new Image();
            br.Width = 800;
            br.Height = 800;
            Canvas.SetLeft(br,100);
            Canvas.SetTop(br,100);
            UriKind relative = default;
            br.Source = new BitmapImage(new Uri("img/biao.png", uriKind: relative));
            can.Children.Add(br);

            //图片默认的是以平铺的方式;设置一个可拉伸属性就可填满
            //br.Stretch

            //添加秒针
            Image br1 = new Image();
            br1.Width = 100;
            br1.Height = 200;
            Canvas.SetLeft(br1, 452);
            Canvas.SetTop(br1, 338);
            br1.Source = new BitmapImage(new Uri("img/mz.png", uriKind: relative));
            can.Children.Add(br1);
            Storyboard miao = new Storyboard();
            //添加一个旋转引用
            RotateTransform rtf = new RotateTransform();
            //添加一个旋转中心
            br1.RenderTransform = rtf;
            br1.RenderTransformOrigin = new Point(0.5, 0.7);  //0.5表示百分数
      //秒针走一圈是60格,360度,那么一格就是6度,现在的度数就是
     //获取到的时间乘以6,每次走的位置要比上一次走的位置多一圈

            DoubleAnimation da = new DoubleAnimation(S*6, S * 6+360, new Duration(TimeSpan.FromSeconds(60)));
            Storyboard.SetTarget(da, br1);
            Storyboard.SetTargetProperty(da, new PropertyPath("RenderTransform.Angle"));
            miao.Children.Add(da);
           miao.RepeatBehavior = RepeatBehavior.Forever;
           miao.Begin();


            //添加分针
            Image br2 = new Image();
            br2.Width = 100;
            br2.Height = 150;
            Canvas.SetLeft(br2, 450);
            Canvas.SetTop(br2, 360);
            br2.Source = new BitmapImage(new Uri("img/sz.png", uriKind: relative));
            can.Children.Add(br2);

            Storyboard fen = new Storyboard();
            //添加一个旋转引用
            RotateTransform rtf1 = new RotateTransform();
            br2.RenderTransform = rtf1;
            //添加一个旋转中心
            br2.RenderTransformOrigin = new Point(0.5, 0.8);  //0.5表示百分数
            //一个时钟分针走一圈是60格,每格也就是6度,
            //在判断现在走了几秒,根据走了几秒判断分针是否在增加
            DoubleAnimation da1 = new DoubleAnimation(M*6+(6/60*S), (M * 6 + (6 / 60 * S)+360), new Duration(TimeSpan.FromMinutes(60)));
            Storyboard.SetTarget(da1, br2);
            Storyboard.SetTargetProperty(da1, new PropertyPath("RenderTransform.Angle"));
            fen.Children.Add(da1);
            fen.RepeatBehavior = RepeatBehavior.Forever;   //设置转动几次
            fen.Begin();

            //添加时针
            Image br3 = new Image();
            br3.Width = 100;
            br3.Height = 100;
            Canvas.SetLeft(br3, 452);
            Canvas.SetTop(br3, 395);
            br3.Source = new BitmapImage(new Uri("img/sz.png", uriKind: relative));
            can.Children.Add(br3);
            Storyboard shi = new Storyboard();
            //添加一个旋转引用
            RotateTransform rtf2 = new RotateTransform();
            br3.RenderTransform = rtf2;
            //添加一个旋转中心
            br3.RenderTransformOrigin = new Point(0.5, 0.8);  //0.5表示百分数
             //走一圈360度12个小时,一个小时是30度,分针转一圈,秒针转60圈相当于3600秒
            //先要判断小时然后在判断现在的分钟和秒钟来判断现在时针的位置
            //这有一个问题 为啥分数不行?
            //double类型是小数类型。(1/2)是int类型,0.5是小数类型
            //如何将1/2转化成小数?
            //(1/2)改成(1.0/2)
            DoubleAnimation da2 = new DoubleAnimation((H*30+(1.0/2)*M+(30/3600)*S), ((H * 30 + (1.0/2) * M + (30 / 3600) * S) + 360), new Duration(TimeSpan.FromHours(12)));
            Storyboard.SetTarget(da2, br3);
            Storyboard.SetTargetProperty(da2, new PropertyPath("RenderTransform.Angle"));
            shi.Children.Add(da2);
            shi.RepeatBehavior = RepeatBehavior.Forever;
            shi.Begin();



            //创建一个圆点
            Ellipse si = new Ellipse();
            si.Width = 15;
            si.Height = 15;
            si.Fill = new SolidColorBrush(Colors.Red);
            Canvas.SetLeft(si, 495);
            Canvas.SetTop(si, 476);
            can.Children.Add(si);
            //创建一个定时器
            DispatcherTimer tim = new DispatcherTimer();
            tim.Interval = TimeSpan.FromMilliseconds(1000);
            tim.Tick += Tim_Tick;
            tim.Start();
        }
       
        private void Tim_Tick(object sender, EventArgs e)
        {
            //创建一个label来显示现在的时间
            Label bor = new Label();
            bor.Width = 250;
            bor.Height = 50;
            bor.Background = new SolidColorBrush(Colors.Yellow);
            Canvas.SetLeft(bor, 350);
            Canvas.SetTop(bor, 800);
            can.Children.Add(bor);
            bor.FontSize = 20;
            bor.Foreground = new SolidColorBrush(Colors.Red);  //设置内容的显示颜色
          //  C#中有严格的大小写限定

    //yyyy - MM - dd hh: mm: ss

    //      两者之间是有区别的:

    //yyyy:代表年份
    //MM:  代表月份
    //dd:   代表天
    //HH:   代表小时(24小时制)
    //mm:  代表分钟
    //ss:    代表秒
            bor.Content = DateTime.Now.ToString("现在的时间为:"+"HH:mm:ss");
          
        }
    }
}

你可能感兴趣的:(WPF:(四)常见的动画及案例)