本篇来学习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 = ;
widthAnimation.To = ;
//widthAnimation.AutoReverse = true;
widthAnimation.Duration = TimeSpan.FromSeconds();
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 ...
}
我们再来通过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>
效果如下:
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>
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>
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>
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>
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 = ;
private int maxStartingSpeed = ;
private double speedRatio = 0.1;
private int minEllipses = ;
private int maxEllipses = ;
private int ellipseRadius = ; private void RenderFrame(object sender, EventArgs e)
{
if (ellipses.Count == )
{
// Animation just started. Create the ellipses.
int halfCanvasWidth = (int)canvas.ActualWidth / ; Random rand = new Random();
int ellipseCount = rand.Next(minEllipses, maxEllipses + );
for (int i = ; 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, );
canvas.Children.Add(ellipse); EllipseInfo info = new EllipseInfo(ellipse, speedRatio * rand.Next(minStartingSpeed, maxStartingSpeed));
ellipses.Add(info);
}
}
else
{
for (int i = ellipses.Count - ; i >= ; i--)
{
EllipseInfo info = ellipses[i];
double top = Canvas.GetTop(info.Ellipse);
Canvas.SetTop(info.Ellipse, top + * info.VelocityY); if (top >= (canvas.ActualHeight - ellipseRadius * - ))
{
// This circle has reached the bottom.
// Stop animating it.
ellipses.Remove(info);
}
else
{
// Increase the velocity.
info.VelocityY += accelerationY;
} if (ellipses.Count == )
{
// 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;
}
}
}
WPF学习(12)动画的更多相关文章
-
WPF学习12:基于MVVM Light 制作图形编辑工具(3)
本文是WPF学习11:基于MVVM Light 制作图形编辑工具(2)的后续 这一次的目标是完成 两个任务. 本节完成后的效果: 本文分为三个部分: 1.对之前代码不合理的地方重新设计. 2.图形可选 ...
-
【WPF学习】第五十三章 动画类型回顾
创建动画面临的第一个挑战是为动画选择正确的属性.期望的结果(例如,在窗口中移动元素)与需要使用的属性(在这种情况下是Canvas.Left和Canvas.Top属性)之间的关系并不总是很直观.下面是一 ...
-
【WPF学习】第五十四章 关键帧动画
到目前为止,看到的所有动画都使用线性插值从起点到终点.但如果需要创建具有多个分段的动画和不规则移动的动画.例如,可能希望创建一个动画,快速地将一个元素滑入到视图中,然后慢慢地将它移到正确位置.可通过创 ...
-
WPF学习之绘图和动画
如今的软件市场,竞争已经进入白热化阶段,功能强.运算快.界面友好.Bug少.价格低都已经成为了必备条件.这还不算完,随着计算机的多媒体功能越来越强,软件的界面是否色彩亮丽.是否能通过动画.3D等效果是 ...
-
WPF学习之绘图和动画--DarrenF
Blend作为专门的设计工具让WPF如虎添翼,即能够帮助不了解编程的设计师快速上手,又能够帮助资深开发者快速建立图形或者动画的原型. 1.1 WPF绘图 与传统的.net开发使用GDI+进行绘图不 ...
-
【WPF学习笔记】[转]周银辉之WPF中的动画 &;&; 晓风影天之wpf动画——new PropertyPath属性链
(一)WPF中的动画 动画无疑是WPF中最吸引人的特色之一,其可以像Flash一样平滑地播放并与程序逻辑进行很好的交互.这里我们讨论一下故事板. 在WPF中我们采用Storyboard(故事板)的方式 ...
-
【WPF学习笔记】之如何把数据库里的值读取出来然后显示在页面上:动画系列之(六)(评论处有学习资料及源码)
(应博友们的需要,在文章评论处有源码链接地址,以及WPF学习资料.工具等,希望对大家有所帮助) ...... 承接系列五 上一节讲了,已经把数据保存到数据库并且删除数据,本讲是把已经存在的数据从数据库 ...
-
【WPF学习】第五十七章 使用代码创建故事板
在“[WPF学习]第五十章 故事板”中讨论了如何使用代码创建简单动画,以及如何使用XAML标记构建更复杂的故事板——具有多个动画以及播放控制功能.但有时采用更复杂的故事板例程,并在代码中实现全部复杂功 ...
-
WPF学习之路初识
WPF学习之路初识 WPF 介绍 .NET Framework 4 .NET Framework 3.5 .NET Framework 3.0 Windows Presentation Found ...
随机推荐
-
github改local用户名和email
github改local用户名和email 进入cd ~/.ssh 修改git config --global user.name “用户名” config --global user.email 电 ...
-
padding与margin的区别
padding 是控件的内容相对控件的边缘的边距. margin 是控件边缘相对父空间的边距. android:gravity 属性是对该view 内容的限定.比如一个button 上 ...
-
Java [leetcode 6] ZigZag Conversion
问题描述: The string "PAYPALISHIRING" is written in a zigzag pattern on a given number of rows ...
-
vim配置python编程环境及YouCompleteMe的安装教程
python号称人工智能语言,现在可算大热,这篇博客将介绍如何用vim打造一款自己专属的python编程环境. step1 由于安装YouCompleteMe需要vim8.0及以上版本,所以得安装使用 ...
-
CSS 关于权重的另类解说
众所周知,对于CSS中权重的顺序,从大到小依次如下: !important id class 标签 在html标签中写入行内样式style,又大于link引入.相同类型的样式标记,在数量上多的大于数量 ...
-
[未解决:快速滑动collectionveiw请求数据崩溃]:unable to allocate 6553600 bytes for bitmap data
崩溃:unable to allocate 6553600 bytes for bitmap data
-
关于网站中Logo部分的写法
由于SEO对网页产生的影响,我们在写html的时候要注意写好三大要素:1.keywords 2.describtion 3.title 我们在写像h1 h2 h3 这种标题的时候尽量要带有网站的名字 ...
-
大数据入门第十九天——推荐系统与mahout(一)入门与概述
一.推荐系统概述 为了解决信息过载和用户无明确需求的问题,找到用户感兴趣的物品,才有了个性化推荐系统.其实,解决信息过载的问题,代表性的解决方案是分类目录和搜索引擎,如hao123,电商首页的分类目录 ...
-
详解PHP实现定时任务的五种方法
这几天需要用PHP写一个定时抓取网页的服务器应用. 在网上搜了一下解决办法, 找到几种解决办法,现总结如下. 定时运行任务对于一个网站来说,是一个比较重要的任务,比如定时发布文档,定时清理垃圾信息等, ...
-
20145314郑凯杰 《Java程序设计》第10周学习总结
20145314郑凯杰 <Java程序设计>第10周学习总结 代码托管: 学习内容总结 网络编程 会打手机吗? 第一个问题:会打手机吗?很多人可能说肯定会啊,不就是按按电话号码,拨打电话嘛 ...