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三个属性来决定了目标属性的起始值和结束值。首先我们来看下这三个属性代表的意义:
(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实现动画过程
注意
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代码如下:
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");
}
}
}