【WPF学习】第四十九章 基本动画

  在前一章已经学习过WPF动画的第一条规则——每一个动画依赖于一个依赖项属性。然而,还有另外一个限制。为了实现属性的动态化(换句话说,使用基于时间的方式改变属性的值),须要有支持相应数据类型的动画类。例如,Button.Width属性使用双精度数据类型。为实现属性的动态化,须要使用DoubleAnimation类。但Button.Paddin属性使用的是Thickness结构,因此须要使用ThicknessAnimation类。html

  该要求不像WPF动画的第一条规则那么绝对,第一条规则将动画局限于依赖项属性。这是由于对于没有相应动画类的依赖项属性,为了为该属性应用动画,能够针对相应的数据类型建立本身的动画类。System.Windows.Media.Animation名称空间已经为但愿使用的大多数数据类型提供了动画类。函数

  由于许多数据类型实际上不使用动画,因此没有相应的动画类。一个明显的例子是枚举类型。例如,可以使用HorizontalAlignment属性控制如何在布局面板中放置元素,该属性使用的是HorizontalAlignment枚举值。然而,HorizontalAlignment枚举只容许从4个值中选择一个(Left、Right、Center和Stretch),这极大地限制了它在动画中的使用。尽管可在某个方向或其余方向之间进行交换,但不能将元素从一种对齐方式平滑过渡到另外一中对齐方式。因此,没有为HorizontalAlignment数据类型提供动画类。能够本身为HorizontalAlignment数据类型构建动画类,但仍要受到4个枚举数值的限制。工具

  引用类型一般不能应用动画,但它们的子属性能够。例如,全部内容控件都支持Background属性,从而能够设置Brush对象用来绘制背景。使用动画从一个画刷切换到另外一个画刷的效率一般不高,但可使用动画改变画刷的属性。例如,可改变SolidColorBrush画刷的Color属性(使用ColorAnimation类),或改变LinearGradientBrush画刷中GradientStop对象的Offset属性(使用DoubleAnimation类)。这扩展了WPF动画的应用范围,容许用户为元素外观的特定方面应用动画。布局

1、Animation类学习

   根据目前为止提到的动画类型——DoubleAnimation和ColorAnimation——可能会任务全部的动画类都是以“类型名+Animation”方式命名。这种观点很接近实际状况,但不是很是准确。动画

  实际上有两种类型的动画——在开始值和结束值之间以逐步增长的方式(被称为线性插值过程)改变属性的动画,以及从一个值忽然变成另外一个值得动画。DoubleAnimation和ColorAnimation属于第一种动画类型,他们使用插值平滑地改变值。然而,当改变特定的数据时,如String和引用类型的对象,插值就没有意义的。不是使用插值,这些数据类型使用一种称为“关键帧动画”的技术在特定时刻从一个值忽然改变到另外一个值。全部关键帧动画类都使用“类型名+AnimationUsingKeyFrames”的形式进行命名,好比StringAnimationUsingKeyFrames和ObjectAnimationUsingKeyFrames。this

  某些数据类型有关键帧动画类,但没有插值动画类。例如,可以使用关键帧为字符串应用动画,但不能使用插值为字符串应用动画。然而,全部数据类型都支持关键帧动画,除非它们根本不支持动画。换句话说,全部具备(使用插值的)常规动画类(例如DoubleAnimation和ColorAnimation)的数据类型,也都有相应的用于关键帧动画的动画类型(如DoubleAnimationUsingKeyFrames和ColorAnimationUsingKeyFrames)。编码

  实际上,还有一种动画类型。这种类型称为基于路径的动画,并且它们比使用插值或关键帧的动画更加专业。基于路径的动画修改数值使其符合由PathGeometry对象描述的形状,而且主要用于艳路径移动元素。基于路径的动画类使用“类型名+AnimationUsingPath”的形式进行命名,如DoubleAnimationUsingPath和PointAnimationUsingPath。spa

  总之,在System.Windows.Media.Animation名称空间中间发现如下内容:设计

  •   17个“类型名+Animation”类,这些类使用插值。
  •   22个“类型名+AnimationUsingKeyFrames”类,这些类使用关键帧动画
  •   3个“类型名+AnimationUsingPath”类,这些类使用基于路径的动画

  全部这些动画类都继承自抽象的“类型名+AnimationBase”类,这些基类实现了一些基本功能,从而为建立自定义动画类提供了快捷方式。若是某个数据类型支持多种类型的动画,那么全部的动画类都继承自抽象的动画基类。例如,DoubleAnimation和DoubleAnimationUsingKeyFrames都继承自DoubleAnimationBase基类。

  可经过查看这42个类快速决定哪些数据类型为动画提供了本地支持。下面是这42个类的完整列表:

BooleanAnimationUsingKeyFrames ByteAnimation
ByteAnimationUsingKeyFrames CharAnimationUsingKeyFrames
ColorAnimation ColorAnimationUsingKeyFrames
DecimalAnimation DecimalAnimationUsingKeyFrames
DoubleAnimation DoubleAnimationUsingKeyFrames
DoubleAnimationUsingPath Int16Animation
Int16AnimationUsingKeyFrames Int32Animation
Int32AnimationUsingKeyFrames Int64Animation
Int64AnimationUsingKeyFrames MatrixAnimationUsingKeyFrames
MatrixAnimationUsingPath ObjectAnimationUsingKeyFrames
PointAnimation PointAnimationUsingKeyFrames
PointAnimationUsingPath Point3DAnimation
Point3DAnimationUsingKeyFrames QuarternionAnimation
QuarternionAnimationUsingKeyFrames RectAnimation
RectAnimationUsingKeyFrames Rotation3DAnimation
Rotation3DAnimationUsingKeyFrames SingleAnimation
SingleAnimationUsingKeyFrames SizeAnimation
SizeAnimationUsingKeyFrames StringAnimationUsingKeyFrames
ThicknessAnimation ThicknessAnimationUsingKeyFrames
VectorAnimation VectorAnimationUsingKeyFrames
Vector3DAnimation Vector3DAnimationUsingKeyFrames

  其中许多类型的含义不言自明。例如,一旦掌握DoubleAnimation类,就不在须要再分析SingleAnimation、Int16Animation、Int32Animation以及其余全部用于简单数值类型的动画类,它们都以相同的方式工做。除这些用于数值类型的动画类外,还会发现一些使用其余基本数据类型(如byte、bool、string以及char)的动画类,以及更多的用于处理二维和三维Drawing图元(Point、Size、Rect和Vector等)的动画类,用于全部元素的Margin和Padding属性的动画类(ThicknessAnimation)、用于颜色的动画类(ColorAnimation)以及用于任意引用类型对象的动画类(ObjectAnimationUsingKeyFrames)。

2、使用代码建立动画

  最经常使用的动画技术是线性插值动画,这种技术平滑地从起点到终点修改属性值。例如,若是将开始数值设置为1,而且将结束数值设置为10,属性可能从1快速地变为1.一、1.二、1.3等,知道数值达到10.

  WPF使用它所需的步长以确保在当前配置的帧率下获得平滑的动画。标准的帧率是60帧/秒。换句话说,WPF每隔1/60秒就会计算全部应用了动画的数值,并更新相应的属性。

  使用动画的最简单方法是实例化在前面列出的其中一个动画类,配置该实例,而后使用但愿修改的元素的BeginAnimation()方法。全部WPF元素,从UIElement基类开始,都继承了BeginAnimation()方法,该方法是IAnimatable接口的一部分。其余实现了IAnimatable接口的类包括ContentElement(文档流内容的基类)和Visual3D(3D可视化对象的基类)。

  下图显示了一个非简单的、增长了按钮宽度的动画。当单击按钮时,WPF平滑地扩展按钮的两个侧边直到充满窗口。

   为建立这种效果,使用动画修改按钮的Width属性。当单击按钮时,下面的代码建立并启用这个动画:

DoubleAnimation widthAnimation = new DoubleAnimation(); widthAnimation.From = 121; widthAnimation.To = this.Width - 30; widthAnimation.Duration = TimeSpan.FromSeconds(5); btnGrow.BeginAnimation(Button.WidthProperty, widthAnimation);

  任何使用线性插值的动画最少须要三个细节:开始值(From)、结束值(To)和整个动画执行的时间(Duration)。在这个示例中,结束值基于包含按钮的窗口的当前宽度。使用插值的全部动画类都提供了这三个属性。

  From、To和Duration属性看似简单,但应注意他们的几个重要细节。接下来将更深刻地分析这些属性。

  1.From属性

  From值是Width属性的开始值。若是屡次单击按钮,每次单击时,都会将Width属性从新设置为121,而且从新开始运行动画。即便当动画已在运行时单击按钮也一样如此。

  在许多状况下,可能不但愿动画从最初的From值开始。有以下两个常见的缘由:

  •   建立可以被触发屡次,并逐次累加效果的动画。例如,可能但愿建立每次单击时都增大一点的按钮。
  •   建立可能相互重叠的动画。例如,可以使用MouseEnter事件触发扩展按钮的动画,并使用MouseLeave事件触发将按钮缩小为原尺寸的互补动画(这一般称为“鱼眼”效果)。若是连续快速地讲鼠标屡次移动到这种按钮上并移开,每一个新动画就会打算上一个动画,致使按钮“跳”回到由From属性设置的尺寸。

  当前示例属于第二种状况。若是当按钮正在增大时单击按钮,按钮的宽度就会被从新设置为121像素——这可能会出现抖动效果。为了纠正这个效果,只须要忽略设置From属性的代码语句便可:

DoubleAnimation widthAnimation = new DoubleAnimation(); widthAnimation.To = this.Width - 30; widthAnimation.Duration = TimeSpan.FromSeconds(5); btnGrow.BeginAnimation(Button.WidthProperty, widthAnimation);

  如今有一个问题。为使用这种技术,应用动画的属性必须有预先设置的值。在这个示例中,这意味着按钮必须有硬编码的宽度(无论是在按钮标签中直接定义的,仍是经过样式设置器应用的)。问题是在许多布局容器中,一般不指定宽度而且让容器根据元素的对齐属性控制宽度。对于这种状况,元素使用默认宽度,也就是特殊的Double.NaN值(这里的NaN表明"不是数字(not a number)")。不能为具备这种值得属性使用线性插值应用动画。

  那么,解决方法是什么呢?在许多状况下,答案是硬编码按钮的宽度。正如看你到的,动画常常更精确地控制元素的尺寸和位置。实际上,对于能应用动画的内容,最经常使用的布局容器是Canvas面板,由于Canvas 面板容许更方便地移动内容(可能相互重叠)以及改变内容的尺寸。Canvas面板仍是量级最轻的布局容器,由于当诸如Width的属性发生变化时不须要额外的布局工做。

  在当前示例中,还有一种选择。可以使用ActualWidth属性检索按钮的当前值,该属性给出的是按钮当前渲染的宽度。不能为ActualWidth属性应用动画(该属性是只读的)。但能够用该属性设置动画的From属性:

widthAnimation.From = btnGrow.ActualWidth;

  这种技术既可用于基于代码的动画(如当前示例),也可用于将后面介绍的声明式动画(这时须要使用绑定表达式来得到ActualWidth属性的值)。

  须要弄清的另外一个问题是,当使用当前值做为动画的起点时——可能改变更画的运行速度。这时由于未调整动画的持续时间,是动画可以考虑到在初始化和最终值之间的宽度变小了。例如,假设建立的按钮不是使用From值而是从当前位置开始动画。若是当几乎达到最大宽度值时单击按钮,新的动画就开始了。尽管只有几个像素的空间可供使用,但这个动画仍呗配置为持续5秒(经过Duration属性)。因此,按钮的增速看起来变慢了。

  只有当从新启动解决完成的动画时才会出现这种效果。尽管有些奇怪,可是大多数开发人员不会尝试为解决该问题而编写许多代码。相反,这被认为具备能够接受的问题。

  2.To属性

  就像可省略From属性同样,也可省略To属性。实际上,可同时省略From属性和To属性,像下面这样建立动画:

DoubleAnimation widthAnimation = new DoubleAnimation(); widthAnimation.Duration = TimeSpan.FromSeconds(5); btnGrow.BeginAnimation(Button.WidthProperty, widthAnimation);

  乍一看,这个动画好像根本没有执行任何操做。这样想是符合逻辑的,由于To属性和From属性都被忽略了,他们将使用相同的值。但他们之间存在一点微妙且重要的区别。

  当省略From属性时,动画使用当前值,并将动画归入考虑范围。例如,若是按钮位于某个增加操做的中间,From值会使用扩展后的宽度。然而,当忽略To值时,动画使用不考虑动画的当前值。本质上,这意味着To值变为原数值——最后一次在代码中、元素标签中或经过样式设置的值.

  在按钮示例中,这意味着若是开始了一个增加动画,而后使用上面的动画打断该动画,按钮将会从已经增加了以后的尺寸进行缩小,直到达到在XAML标记中设置的原始宽度。另外一方面,若是在没有其它动画正在进行的状况下进行这段代码,不会发生任何事情,这是由于From值(动画后的宽度)和To(原始宽度)相等。

  3.By属性

  即便不使用To属性,也可使用By属性,By属性用于建立按钮设置的数值改变值得动画而不是按给定目标改变值。例如,可建立一个动画,增大按钮的尺寸,使得比当前尺寸大10个单位,以下所示:

DoubleAnimation widthAnimation = new DoubleAnimation(); widthAnimation.By = 10; widthAnimation.Duration = TimeSpan.FromSeconds(5); btnGrow.BeginAnimation(Button.WidthProperty, widthAnimation);

  在按钮示例中,这种方法不是必需的,由于可以使用简单的计算设置To属性来实现相同的的效果,以下所示:

widthAnimation.To = btnGrow.Width + 10;

  然而当使用XAML定义动画时,使用By值就变得更加合理了,由于XAML没有提供执行简单计算的方法。

  大部分使用插值的动画类一般都提供了By属性,但并不是所有如此。例如,对于非数值数据类型来讲,By属性是没有意义的,好比ColorAnimation类使用的Color结构。

  另有一种方法可获得相似的行为,而不须要使用By属性——可经过设置IsAdditive属性建立增长数值的动画。当建立这种动画时,当前值被自动添加到From值和To值。例如,分析下面这个动画:

DoubleAnimation widthAnimation = new DoubleAnimation(); widthAnimation.From = 0; widthAnimation.To = -10; widthAnimation.Duration = TimeSpan.FromSeconds(0.5); widthAnimation.IsAdditive = true;

  这个动画是从当前值开始的,当达到比当前值少10个单位的值时完成。另外一方面,若是使用下面的动画:

DoubleAnimation widthAnimation = new DoubleAnimation(); widthAnimation.From = 10; widthAnimation.To = 50; widthAnimation.Duration = TimeSpan.FromSeconds(0.5); widthAnimation.IsAdditive = true;

  属性值跳到新值(比当前值大10个单位的值),而后增长值,直到达到最后的值,最后的值比动画开始前得的当前值大50个单位。

  4.Duration属性

  Duration属性很简单——是在动画开始时刻和结束时刻之间的时间间隔(时间间隔单位是毫秒、分钟、小时或喜欢使用的其余任何单位)。尽管在上一个示例中,动画的持续时间是使用TimeSpan对象设置的,但Duration结构定义了一种隐式转换,能偶根据须要将System.TimeSpan转换为System.Windows.Duration。这正是为何下面的代码彻底合理的缘由:

widthAnimation.Duration = TimeSpan.FromSeconds(5);

  那么,为何使用全新的数据类型呢?由于Duration类型还提供了两个不能经过TimeSpan对象表示的特殊值——Duration.Automatic和Duration.Forever。在当前示例中,这两个值都没有用处(Automatic值只将动画设置为1秒得持续时间,而Forever值使动画具备无限的持续时间,这会防止动画具备任何效果)。然而,当建立更复杂的动画时,这些值就有用处了。

3、同时发生的动画

  可以使用BeginAnimation()方法同时启动多个动画。BeginAnimation()方法几乎老是当即返回,从而可使用相似下面的代码同时为两个属性应用动画:

DoubleAnimation widthAnimation = new DoubleAnimation(); widthAnimation.From = 219; widthAnimation.To = this.Width - 30; widthAnimation.Duration = TimeSpan.FromSeconds(5); DoubleAnimation heightAnimation = new DoubleAnimation(); heightAnimation.From = 99; heightAnimation.To = this.Height - 50; heightAnimation.Duration = TimeSpan.FromSeconds(5); btnGrow.BeginAnimation(Button.WidthProperty, widthAnimation); btnGrow.BeginAnimation(Button.HeightProperty, heightAnimation);

  在这个示例中,两个动画没有被同步,这意味着宽度和高速不会准确地再相同时间间隔内增加(一般,将看到按钮先增长宽度,紧接着增大高速)。可经过建立绑定到同一个时间县的动画,突破这一限制。

4、动画的生命周期

  从技术角度看,WPF动画是暂时的,这意味着它们不能真正改变基本属性的值。当动画处于活动状态时,只是覆盖属性值。这是由依赖项属性的工做方式形成的,而且这是一个常常被忽视的细节,该细节会给用户带来极大的困惑。

  单向动画(如增加按钮的动画)在运行结束后保持处于活动状态,这是由于动画须要将按钮的宽度保持为新值。这会致使以下不常见的问题——若是尝试使用代码在动画完成后修改属性值,代码将不起做用。由于代码只是为属性指定了一个新的本地值,但仍会优先使用动画以后的属性值。

  根据准备完成的工做,可经过以下几种方法解决这个问题:

  •   建立将元素从新设置为原始状态的动画。可经过建立不设置To属性的动画达到该目的。例如,将按钮的宽度减小到最后设置的尺寸的按钮缩小动画,以后就可能使用代码改变该属性了。
  •   建立可翻转的动画。经过将AutoReverse属性设置为true来建立可翻转的动画。例如,当按钮增加动画不在增长按钮的宽度时,将反向播放动画,返回到原始宽度。动画的总持续事件也将翻倍。
  •   改变FillBehavior属性。一般,FillBehavior属性被设置为HoldEnd,这意味着当动画结束时,会继续为目标元素应用最后的值。若是将FillBehavior属性改成Stop,只要动画结束,属性就会恢复为原来的值。
  •   当动画完成时经过处理动画对象的Completed事件删除动画对象。

  前三种方法改变了动画的行为。无论使用哪一种方法,他们都将动画后的属性设置为原来的数值。若是这并不是所但愿的,那就须要使用最后一种方法。

  首先,在启动动画钱,关联事件处理程序以响应动画完成事件:

widthAnimation.Completed += widthAnimation_Completed;

  当引起Completed事件时,可经过调用BeginAnimation()方法来渲染不活动的动画。为此,只须要指定属性,并为动画对象传递null引用:

btnGrow.BeginAnimation(Button.WidthProperty, null);

  当调用BeginAnimation()方法时,属性返回为动画开始以前的原始只。若是这并不是所但愿的结果,可记下动画应用的当前值,删除动画,而后手动为属性设置新值,以下所示:

double currentWidth = this.btnGrow.Width; btnGrow.BeginAnimation(Button.WidthProperty, null); btnGrow.Width = currentWidth;

  须要注意的是,如今改变了属性的本地值。这可能影响其余动画的运行。例如,若是为按钮使用未指定From属性的动画,该动画就会使用这个新应用的属性值做为起点。大多数状况下,这正是所但愿的行为。

5、Timeline类

  每一个动画须要使用几个重要属性,咱们已经分析了其中几个属性:From和To属性(使用插值的动画类提供了这两个属性),以及Duration和FillBehavior属性(全部动画类都提供了这两个属性)。在继续学习以前,有必要深刻分析必须使用的属性。

  下图显示了WPF动画类的继承层次结构。该图包含了全部基类,但省略了所有42个动画类以及相应的TypeNameAnimationBase类:

  图 动画类的继承层次结构

  上图显示的层次结构包含了继承自Timeline抽象类的三个主要分支。当播放音频或视频文件时使用MediaTimeline类。AnimationTimeline分支用于到目前为止分析过的基于属性的动画系统。而TimelineGroup分支则容许同步时间线并控制它们的播放。

  Timeline类中前几个有用的成员定义了已经介绍过的Duration属性,还有其余几个属性。下表列出了Timeline类的属性:

表 Timeline类的属性

   尽管BeginTime、Duration、SpeedRatio以及AutoReverse属性都很简单,但其余一些属性须要进一步加以分析。接下来将深刻分析AccelerationRatio、DecelerationRatio以及RepeatBehavior属性。

  1.AccelerationRatio和DecelerationRatio属性

  能够经过AccelerationRatio和DecelerationRatio属性压缩部分时间线,使动画运行得更快。并将拉伸其余时间线进行补偿,使总时间保持不变。

  这两个属性都表示百分百值。例如,将AccelerationRatio属性设置为0.3表示但愿使用动画持续时间中前30%的时间进行加速。例如,在一个持续10秒得动画中,前3秒会加速运行,而剩余的7秒会以恒定不变的速度运行(显然,在最后7秒钟得速度比没有加速的动画快,由于须要补偿前3秒中的缓慢启动)。若是将AccelerationRatio属性设置为0.3,并将DecelerationRatio属性也设置为0.3,那么前3秒会加速,在中间4秒保持固定的最大速度,在最后3秒减速。分析一下这种方式,显然,AccelerationRatio和DecelerationRatio属性值之和不能超过1,不然就须要超过100%的可用时间来执行所需的加速和减速。固然,可将AccelerationRatio属性设置为1(对于这种状况,动画速度从开始到结束一直在增长),或将DecelerationRatio属性设置为1(对于这种状况,动画速度从开始到结束一直在下降)。

  加速和减速的动画经常使用于提供更趋天然的外观。然而,AccelerationRatio和DecelerationRatio属性只提供了相对简单的控制。例如,它们不能改变加速速度或者将其设置为指定的值。若是但愿获得使用可变加速度的动画,须要定义一系列动画,逐个进行播放,而且为每一个动画设置AccelerationRatio和DecelerationRatio属性,或者须要使用具备关键样条曲线帧动画。尽管这种技术提供了很大的灵活性,但一直跟踪全部细节是一件使人头疼的事情,而且对于构建动画来讲,完美的状况是使用设计工具。

  2.RepeatBehavior属性

  使用RepeatBehavior属性可控制如何重复运行动画。若是但愿重复固定次数,应为RepeatBehavior构造函数传递合适的次数。例如,下面的动画重复次数:

DoubleAnimation widthAnimation = new DoubleAnimation(); widthAnimation.To = this.Width - 30; widthAnimation.Duration = TimeSpan.FromSeconds(5); widthAnimation.RepeatBehavior = new RepeatBehavior(2); btnGrow.BeginAnimation(Button.WidthProperty, widthAnimation);

  当运行这个动画时,按钮会增大尺寸(通过5秒),调回到原来的数值,而后再次增长尺寸(通过5秒),在按钮的宽度为整个窗口的宽度时结束。若是将AutoReverse属性设置为true,行为稍有不一样——整个动画完成向前和向后运行(意味着先展开按钮,而后收缩),以后再重复一次。

  除可使用RepeatBehavior属性设置重复次数外,还能够用该属性设置重复的时间间隔。为此,只须要为RepeatBehavior对象的构造函数传递一个TimeSpan对象。例如,下面的动画重复13秒:

DoubleAnimation widthAnimation = new DoubleAnimation(); widthAnimation.To = this.Width - 30; widthAnimation.Duration = TimeSpan.FromSeconds(5); widthAnimation.RepeatBehavior = new RepeatBehavior(TimeSpan.FromSeconds(13)); btnGrow.BeginAnimation(Button.WidthProperty, widthAnimation);

  在该例中,Duration属性指定整个动画历经5秒。所以,将RepeatBehavior属性设置为13秒将会引发两次重复,而后经过第三次重复动画,使按钮的宽度处于中间位置(在3秒得位置)。

  最后,也可以使用RepeatBehavior.Forever值使动画不断地重复自身:

widthAnimation.RepeatBehavior = RepeatBehavior.Forever;

 

原文出处:https://www.cnblogs.com/Peter-Luo/p/12364590.html

相关文章
相关标签/搜索