本篇来学习WPF的动画。什么是动画?动画就是一系列帧。在WPF中,动画就是在一段时间内修改依赖属性值的行为,它是基于时间线Timeline的。有人会说,要动画干嘛,华而不实,而且添加了额外的资源消耗而影响性能。尽管如此,适当的使用动画却可以使你的程序富有更好的表现力和交互性。更加可喜的是,WPF提供了丰富的动画支持,大部分的动画都可以直接通过XAML来呈现而不用去写繁琐的cs代码。在System.Windows.Media.Animation命名空间中,我们发现了许多的类,大体可归类为这么三种:基于线性内插算法动画(简单动画)、基于KeyFrame动画和基于路径动画。下面来分别介绍这三种动画:

首先以图来说明类的继承层次:

这张粗糙的图只是以Double类型的动画为例,因为它是比较全的,全面提到的三种动画类它都具备。支持KeyFrame的类型最多,其次是线性内插,最后是路径,只有三种,除了Double外,还有Matrix和Point。

1.基于线性内插算法动画(简单动画)

基于线性内插动画是在一个开始值到一个结束值之间以逐步增加的方式来改变属性值。有这么几个重要的属性:

From:开始值,当忽略该属性时,动画默认从其使用者属性值开始,所以其使用者属性在执行动画前要被赋值

To:结束值,当忽略该属性时,动画默认从其使用者属性值结束,所以其使用者属性在执行动画前要被赋值

By:递增值,和To一样和From搭配使用,当然可以用To来代替它

Duration:时间间隔,动画的执行时间,可以将一个TimeSpan值直接给它赋值,会自动隐式转换

AutoReverse:在到达结束值后,是否以相反的顺序回到From值

RepeatBehavior:重复行为,默认不重复,可设置其为Forever来重复动画,也可以通过构造器来设置次数和Timespan

FillBehavior:在活动周期结束后的行为方式,有HoldEnd(默认值)和Stop两个值,HoldEnd表示在动画结束后保持该属性值,而Stop则表示在动画结束后回到原来值(不是From值)

例子如下:

private void Button_Click(object sender, RoutedEventArgs e){Button button = (Button)sender;DoubleAnimation widthAnimation = new DoubleAnimation();widthAnimation.From = 200;widthAnimation.To = 100;//widthAnimation.AutoReverse = true;widthAnimation.Duration = TimeSpan.FromSeconds(5);widthAnimation.FillBehavior = FillBehavior.Stop;//RepeatBehavior = RepeatBehavior.Forever;widthAnimation.Completed += widthAnimation_Completed;button.BeginAnimation(Button.WidthProperty, widthAnimation);}void widthAnimation_Completed(object sender, EventArgs e){//to do ...}

View Code

我们再来通过Trigger在Xaml页面举个例子:

<Window x:Class="AnimationDemo.Window1"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="Window1" Height="300" Width="300"><Window.Resources><Style TargetType="Grid" x:Key="gridKey"><Style.Triggers><EventTrigger RoutedEvent="Grid.MouseEnter"><BeginStoryboard><Storyboard><DoubleAnimation Storyboard.TargetProperty="RenderTransform.Children[0].ScaleX"From="0" To="1" Duration="0:0:2" AccelerationRatio="1" /><DoubleAnimation Storyboard.TargetProperty="RenderTransform.Children[0].ScaleY"From="0" To="1" Duration="0:0:2" AccelerationRatio="1" /><DoubleAnimation Storyboard.TargetProperty="RenderTransform.Children[1].Angle"From="70" To="0" Duration="0:0:2" /></Storyboard></BeginStoryboard></EventTrigger></Style.Triggers></Style></Window.Resources><Grid><Grid x:Name="grid" Background="Coral" Style="{StaticResource gridKey}"><Grid.RenderTransform><TransformGroup><ScaleTransform /><RotateTransform /></TransformGroup></Grid.RenderTransform><TextBlock TextWrapping="Wrap">烽火绵延,战场泣杀,谁留锋刃惹人嘲。弑红双眸,血刃乱划,拼得素衣染泪颊。阴风低吼,暴雨怒下,徒留艳红干不涸。只盼苟活,还能归家,再不问誓死拼杀。此生惟愿与你共活,难得执手画青丝沧桑,不管红尘凡事俗争,只此一生为你描眉点唇。任苍天笑我懦弱,我依旧充耳不顾,为你甘愿卸甲耕种,只盼安稳走完一生。如若我此去再无音信,愿你重披嫁衣,让他代我此生疼你若宝。如若我此去再无归期,愿你冲洗记忆,让他重新入你心底。我只不甘,再不见你如斯容颜。我只不愿,再见你眼眸带泪颜。我只不叹,再不见你怀中展颜。我只不再,再见盼来世重逢颜。风烟轻擦,我已杳无牵挂,独余你,是我心底最不愿触及的殇。</TextBlock></Grid></Grid>
</Window>

View Code

效果如下:

2.基于KeyFrame动画

基于KeyFrame动画可以很方便地实现多个分段和不规则移动的动画。虽然前面的线性内插动画也可通过BeginTime构建多个连续动画来实现,但是显得很复杂。基于KeyFrame动画是由多个段构成的,每一段表示动画的开始值和最终值或者中间值。主要有一个×××KeyFrameCollection类型的KeyFrames属性,它是一个×××KeyFrame的集合。×××KeyFrame有一个KeyTime类型的KeyTime属性和一个Object类型的Value属性,后者表示关键帧的目标值,前者是到达关键帧的目标值的时间。拿Double类型来说吧,DoubleAnimationUsingKeyFrames有个DoubleKeyFrameCollection类型的KeyFrames属性,是DoubleKeyFrame

抽象类型的集合,DoubleKeyFrame类型又有这么几个子类:

LinearDoubleKeyFrame:通过使用线性内插(线性关键帧),可以在前一个关键帧的Double值及其自己的Value值之间进行动画处理

DiscreteDoubleKeyFrame:通过使用离散内插(离散关键帧),可以在前一个关键帧的Double值及其自己的Value值之间进行动画处理

SplineDoubleKeyFrame:通过使用样条内插(样条关键帧),有KeySpline类型的KeySpline属性,它表示关键帧进度的三次方贝塞尔曲线

EasingDoubleKeyFrame:通过使用缓动关键帧,将EasingFunction和关键帧动画关联,有IEasingFunction类型的EasingFunction属性

例子如下:

<Window x:Class="AnimationDemo.Window2"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="Window2" Height="300" Width="300"><Window.Resources><Style TargetType="Grid" x:Key="gridKey"><Style.Triggers><EventTrigger RoutedEvent="Grid.MouseEnter"><BeginStoryboard><Storyboard><DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="RenderTransform.Children[0].ScaleX"AccelerationRatio="1"><DoubleAnimationUsingKeyFrames.KeyFrames><DoubleKeyFrameCollection><LinearDoubleKeyFrame KeyTime="0:0:0" Value="0" /><LinearDoubleKeyFrame KeyTime="0:0:2" Value="1" /></DoubleKeyFrameCollection></DoubleAnimationUsingKeyFrames.KeyFrames></DoubleAnimationUsingKeyFrames><DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="RenderTransform.Children[0].ScaleY"AccelerationRatio="1"><DoubleKeyFrameCollection><LinearDoubleKeyFrame KeyTime="0:0:0" Value="0" /><LinearDoubleKeyFrame KeyTime="0:0:2" Value="1" /></DoubleKeyFrameCollection></DoubleAnimationUsingKeyFrames><DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="RenderTransform.Children[1].Angle"AccelerationRatio="1"><DoubleKeyFrameCollection><LinearDoubleKeyFrame KeyTime="0:0:0" Value="70" /><LinearDoubleKeyFrame KeyTime="0:0:2" Value="50" /><SplineDoubleKeyFrame KeyTime="0:0:4" Value="30"><SplineDoubleKeyFrame.KeySpline><KeySpline ControlPoint1="0.3,0.7" ControlPoint2="0.7,0.3" /></SplineDoubleKeyFrame.KeySpline></SplineDoubleKeyFrame><EasingDoubleKeyFrame KeyTime="0:0:6" Value="0"><EasingDoubleKeyFrame.EasingFunction><BounceEase /></EasingDoubleKeyFrame.EasingFunction></EasingDoubleKeyFrame></DoubleKeyFrameCollection></DoubleAnimationUsingKeyFrames></Storyboard></BeginStoryboard></EventTrigger></Style.Triggers></Style></Window.Resources><Grid><Grid x:Name="grid" Background="Coral" Style="{StaticResource gridKey}"><Grid.RenderTransform><TransformGroup><ScaleTransform /><RotateTransform /></TransformGroup></Grid.RenderTransform><TextBlock TextWrapping="Wrap">烽火绵延,战场泣杀,谁留锋刃惹人嘲。弑红双眸,血刃乱划,拼得素衣染泪颊。阴风低吼,暴雨怒下,徒留艳红干不涸。只盼苟活,还能归家,再不问誓死拼杀。此生惟愿与你共活,难得执手画青丝沧桑,不管红尘凡事俗争,只此一生为你描眉点唇。任苍天笑我懦弱,我依旧充耳不顾,为你甘愿卸甲耕种,只盼安稳走完一生。如若我此去再无音信,愿你重披嫁衣,让他代我此生疼你若宝。如若我此去再无归期,愿你冲洗记忆,让他重新入你心底。我只不甘,再不见你如斯容颜。我只不愿,再见你眼眸带泪颜。我只不叹,再不见你怀中展颜。我只不再,再见盼来世重逢颜。风烟轻擦,我已杳无牵挂,独余你,是我心底最不愿触及的殇。</TextBlock></Grid></Grid>
</Window>

View Code

3.基于Path的动画

基于Path的动画是一种很灵活的执行动画的方式,它一般被用于沿着一条路径来移动可视对象。它主要有一个PathGeometry类型的PathGeometry属性和PathAnimationSource类型的Source属性,前者表示指定用于生成此动画输出值的几何图形,后者表示指定PathGeometry属性的方位,默认值为PathAnimationSource.X,表示指定在前进过程中沿动画序列路径的 X 坐标偏移量。

<Window x:Class="AnimationDemo.Window3"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="Window3" Height="500" Width="600"><Window.Resources><PathGeometry x:Key="pathKey"><PathGeometry.Figures><PathFigure IsClosed="True"><PathFigure.Segments><LineSegment Point="300,2" IsStroked="True" IsSmoothJoin="True"/><LineSegment Point="300,80" IsStroked="True" IsSmoothJoin="True"/><LineSegment Point="2,80" IsStroked="True" IsSmoothJoin="True"/></PathFigure.Segments></PathFigure></PathGeometry.Figures></PathGeometry><Style x:Key="ImgKey" TargetType="Image"><Style.Triggers><EventTrigger RoutedEvent="Loaded"><BeginStoryboard><Storyboard><DoubleAnimationUsingPath Storyboard.TargetProperty="(Canvas.Left)"PathGeometry="{StaticResource pathKey}" Duration="0:0:5" RepeatBehavior="Forever" Source="X"></DoubleAnimationUsingPath><DoubleAnimationUsingPath Storyboard.TargetProperty="(Canvas.Top)"PathGeometry="{StaticResource pathKey}" Duration="0:0:5" RepeatBehavior="Forever" Source="Y"></DoubleAnimationUsingPath></Storyboard></BeginStoryboard></EventTrigger></Style.Triggers></Style></Window.Resources><Canvas><Path Stroke="Red" StrokeThickness="1" Data="{StaticResource pathKey}" Canvas.Left="10" Canvas.Top="10" /><Image x:Name="img" Canvas.Left="15" Canvas.Top="15" Style="{StaticResource ImgKey}"><Image.Source><DrawingImage><DrawingImage.Drawing><GeometryDrawing Brush="LightSteelBlue"><GeometryDrawing.Pen><Pen Brush="Black" Thickness="1" /></GeometryDrawing.Pen><GeometryDrawing.Geometry><EllipseGeometry Center="10,10" RadiusX="3" RadiusY="3" /></GeometryDrawing.Geometry></GeometryDrawing></DrawingImage.Drawing></DrawingImage></Image.Source></Image></Canvas>
</Window>

View Code

4.动画在XAML中的载体--Storyboard

正如开始所说,大部分基于属性的动画都是可以直接在XAML中表达的,但是它需要一个载体,或者叫一个容器,它就是Storyboard,它是动画和希望应用动画的属性的桥梁。Storyboard经常被BeginStoryboard包装作为EventTrigger的TriggerAction,这里BeginStoryboard是一个密封类,而不是FrameworkElement里面的那个方法,它表示一个触发器操作,该操作可启动Storyboard 并将其动画分发给动画的目标对象和属性。Storyboard继承自ParallelTimeline,意味着它可以并行运行多个子时间线,而且它具有控制动画播放的能力,例如Pause、Resume、Skip和Stop等。它用TargetName附加属性表示动画的作用者,用TargetProperty附加属性来指定TargetName的希望改变的属性。

<Window x:Class="AnimationDemo.Window4"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="Window4" Height="300" Width="300"><Window.Resources><Style TargetType="ListBoxItem"><Style.Triggers><EventTrigger RoutedEvent="MouseEnter"><BeginStoryboard HandoffBehavior="Compose"><Storyboard><DoubleAnimation Storyboard.TargetProperty="FontSize" BeginTime="0:0:0.5" Duration="0:0:0.2" To="18"/></Storyboard></BeginStoryboard></EventTrigger><EventTrigger RoutedEvent="MouseLeave"><BeginStoryboard HandoffBehavior="Compose"><Storyboard><DoubleAnimation Storyboard.TargetProperty="FontSize" BeginTime="0:0:0.5" Duration="0:0:0.2" /></Storyboard></BeginStoryboard></EventTrigger></Style.Triggers></Style></Window.Resources><Grid><ListBox><ListBoxItem>Jello</ListBoxItem><ListBoxItem>Taffy</ListBoxItem><ListBoxItem>Jim</ListBoxItem><ListBoxItem>Lily</ListBoxItem></ListBox></Grid>
</Window>

View Code

5.基于Frame的动画

前面介绍的都是基于属性的动画,还有一种比较的动画,既是基于帧的动画,只需要订阅CompositionTarget的静态Rendering事件即可,它在呈现组合树中的对象之前发生,从而为每帧获取内容。

Xaml代码:

<Window x:Class="AnimationDemo.Window7"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="Window7" Height="300" Width="300"><Grid Margin="3"><Grid.RowDefinitions><RowDefinition Height="Auto"></RowDefinition><RowDefinition></RowDefinition></Grid.RowDefinitions><StackPanel Orientation="Horizontal"><Button Margin="3" Padding="3" Click="cmdStart_Clicked">Start</Button><Button Margin="3" Padding="3" Click="cmdStop_Clicked">Stop</Button></StackPanel><Canvas Name="canvas" Grid.Row="1" Margin="3"></Canvas></Grid>
</Window>

View Code

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.Imaging;
using System.Windows.Shapes;namespace AnimationDemo
{/// <summary>/// Window7.xaml 的交互逻辑/// </summary>public partial class Window7 : Window{public Window7(){InitializeComponent();}private bool rendering = false;private void cmdStart_Clicked(object sender, RoutedEventArgs e){if (!rendering){ellipses.Clear();canvas.Children.Clear();CompositionTarget.Rendering += RenderFrame;rendering = true;}}private void cmdStop_Clicked(object sender, RoutedEventArgs e){StopRendering();}private void StopRendering(){CompositionTarget.Rendering -= RenderFrame;rendering = false;}private List<EllipseInfo> ellipses = new List<EllipseInfo>();private double accelerationY = 0.1;private int minStartingSpeed = 1;private int maxStartingSpeed = 50;private double speedRatio = 0.1;private int minEllipses = 20;private int maxEllipses = 100;private int ellipseRadius = 10;private void RenderFrame(object sender, EventArgs e){if (ellipses.Count == 0){// Animation just started. Create the ellipses.int halfCanvasWidth = (int)canvas.ActualWidth / 2;Random rand = new Random();int ellipseCount = rand.Next(minEllipses, maxEllipses + 1);for (int i = 0; i < ellipseCount; i++){Ellipse ellipse = new Ellipse();ellipse.Fill = Brushes.LimeGreen;ellipse.Width = ellipseRadius;ellipse.Height = ellipseRadius;Canvas.SetLeft(ellipse, halfCanvasWidth + rand.Next(-halfCanvasWidth, halfCanvasWidth));Canvas.SetTop(ellipse, 0);canvas.Children.Add(ellipse);EllipseInfo info = new EllipseInfo(ellipse, speedRatio * rand.Next(minStartingSpeed, maxStartingSpeed));ellipses.Add(info);}}else{for (int i = ellipses.Count - 1; i >= 0; i--){EllipseInfo info = ellipses[i];double top = Canvas.GetTop(info.Ellipse);Canvas.SetTop(info.Ellipse, top + 1 * info.VelocityY);if (top >= (canvas.ActualHeight - ellipseRadius * 2 - 10)){// This circle has reached the bottom.// Stop animating it.
                        ellipses.Remove(info);}else{// Increase the velocity.info.VelocityY += accelerationY;}if (ellipses.Count == 0){// End the animation.// There's no reason to keep calling this method// if it has no work to do.
                        StopRendering();}}}}}public class EllipseInfo{public Ellipse Ellipse{get;set;}public double VelocityY{get;set;}public EllipseInfo(Ellipse ellipse, double velocityY){VelocityY = velocityY;Ellipse = ellipse;}}
}

View Code

转载于:https://www.cnblogs.com/jellochen/p/3592963.html

WPF学习(12)动画相关推荐

  1. WPF学习12:基于MVVM Light 制作图形编辑工具(3)

    本文是WPF学习11:基于MVVM Light 制作图形编辑工具(2)的后续 这一次的目标是完成 两个任务. 本节完成后的效果: 本文分为三个部分: 1.对之前代码不合理的地方重新设计. 2.图形可选 ...

  2. 【WPF学习】第五十四章 关键帧动画

    到目前为止,看到的所有动画都使用线性插值从起点到终点.但如果需要创建具有多个分段的动画和不规则移动的动画.例如,可能希望创建一个动画,快速地将一个元素滑入到视图中,然后慢慢地将它移到正确位置.可通过创 ...

  3. 【WPF学习笔记】[转]周银辉之WPF中的动画 晓风影天之wpf动画——new PropertyPath属性链...

    (一)WPF中的动画 动画无疑是WPF中最吸引人的特色之一,其可以像Flash一样平滑地播放并与程序逻辑进行很好的交互.这里我们讨论一下故事板. 在WPF中我们采用Storyboard(故事板)的方式 ...

  4. .NET-7.WPF学习2. 知识总结

    WPF学习2. 知识总结 前言 一.面试 二.代码片段 三.查看链接 前言 对wpf 的知识总结. 一.面试 1. 跨线程操作(Dispatcher)2. template(模板类型[控件模板.数据模 ...

  5. WPF中的动画——(一)基本概念

    原文:WPF中的动画--(一)基本概念 WPF的一个特点就是支持动画,我们可以非常容易的实现漂亮大方的界面.首先,我们来复习一下动画的基本概念.计算机中的动画一般是定格动画,也称之为逐帧动画,它通过每 ...

  6. WPF学习笔记-第二周【基本笔刷】

    书接上回,这一次,讲的是WPF中的基本笔刷,由于是菜鸟,就不多说了,继续帖示例代码:) 第一部份 代码 第二章 基本笔刷 第一个 示例 VaryTheBackgroud P38  1     #reg ...

  7. 【WPF学习】第五十章 故事板

    正如上一章介绍,WPF动画通过一组动画类(Animation类)表示.使用少数几个熟悉设置相关信息,如开始值.结束值以及持续时间.这显然使得它们非常适合于XAML.不是很清晰的时:如何为特定的事件和属 ...

  8. WPF学习笔记(7):DataGrid中数字自定义格式显示

    WPF学习笔记(7):DataGrid中数字自定义格式显示 原文:WPF学习笔记(7):DataGrid中数字自定义格式显示 DataGrid中数据显示如下图,数据格式比较杂乱.希望达到以下要求:(1 ...

  9. WPF学习:3.Border Brush

    上一章<WPF学习:2.Layout-Panels-Countainers>主要介绍了布局,容器和面板.这一章主要开始介绍Border(边界)和Brush(画刷). 代码地址:http:/ ...

最新文章

  1. 赛门铁克:“高度怀疑”WannaCry的幕后黑手是朝鲜
  2. Java中的微信支付(2):API V3 微信平台证书的获取与刷新
  3. Spring学习笔记15--注解Bean
  4. java开发常用jar包_Java开发中常用jar包整理及使用
  5. 企业内网利用devpi搭建pypi私服
  6. 保障了罗振宇跨年演讲的PTS铂金版正式上线,产品体验全新升级
  7. 加密机工作原理_端子机压力检测装置工作原理
  8. laravel运用redis存储数据和读取的方式
  9. 只有ajax会跨域吗_ajax解决跨域
  10. [Spark] - HashPartitioner RangePartitioner 区别
  11. pandas隔行计算均值方差(相邻行或隔行的均值/方差)
  12. IntelliJ IDEA 创建Java Web项目
  13. 前端开发:模块化 — 高效重构
  14. 生新技能树单细胞GBM数据分析(SignleR以及Seurat 联合分析及细胞簇注释
  15. C++const类型
  16. Excel修改默认分页符(仅仅在特定行后可插入分页符)
  17. oracle增加字段为主键自增_Oracle新增自增一的主键字段和赋值代码
  18. 用opencv将左右眼视角图片转换为红蓝3D图片
  19. Google网络硬盘(GDrive):千呼万唤不出来
  20. 化繁为简,我用”知晓推送”开发微信小程序订阅消息

热门文章

  1. Python 3.7.1 官方文档 总结
  2. Python创作新年祝福语
  3. 带有默认值的参数一定位于参数列表的末尾
  4. vue路由传参的三种方式区别(params,query)
  5. Java ASCII编码
  6. echarts立体中国地图的展示
  7. 再访洪小文:AI,誓不作恶
  8. C++中 对》和《的重载
  9. 【Docker】安装mysql,redis
  10. gem意思_gem是什么意思中文翻译