虽然在过去的几节课中,已经详细的介绍了不少的属性和方法以及事件,本节来看一看动画的执行状态。
回顾一下前面的几节内容,可以看到,每一个动画都具有如下的属性和方法,请仔细阅读下面的属性或事件的作用,在接下来的案例中将会使用到它们:
1. 动画的属性和事件
-
Duration:该属性表示动画的持续时间(以秒为单位)。它是一个浮点值,因此你可以指定动画播放的任何时间长度,而不仅仅是整数值。
-
Enabled:严格来说,该属性决定动画是否可以启动。然而,该属性的一般理解与其在设计时使用相关。如果你希望动画在加载后自动启动,可以将 Enabled 设置为 True ;如果你在运行时将此属性从 True 更改为 False ,这也将导致动画在该时刻停止。
注意:请不要将 Enabled 与Running属性(Running属性只具有Public可见性,而 Enabled 是一个Publish属性,可以在属性编辑器上进行修改)混淆。Enabled决定了动画是否可以通过Start进行启动。
-
Running: 这是Public的,并不是Published的属性,因此未列在对象检查器窗口中。这个只读属性可用于判断动画是否正在运行。当动画开始时,此属性的值将为 True ,并保持此值直到动画结束(这可能意味着整个持续时间,或者由于某种原因动画在完成前被停止)。完成的动画将设置 Running 为 False 。
-
Delay: 此属性设置动画执行前等待的时间(以秒为单位)。如果你将此属性设置为 1 (秒)并调用 Start 方法,动画将在 1 秒后执行。
-
Interpolation:此属性将允许你选择应用于此特定动画的插值修正函数。
-
AnimationType:此属性与插值相关,你可以将其视为其修饰符。在大多数情况下,你可以将其视为在起始值和最终值之间选择插值强调的位置。此属性的可用值为 In 、 Out 和 InOut 。
-
Inverse:你可以将此属性设置为 True 来反转动画方向;具体来说,你实际上反转的是动画中的时间流(你可以通过在动画的 OnProcess 事件处理程序中打印 NormalizedTime 值来轻松证明这一点)。这也意味着所有动画都是可逆的。
-
Loop: 这个布尔属性决定动画是否应该在其开始后循环播放。将此属性设置为 True 将导致动画在从初始值到最终值的过渡完成后(或 Duration 到期时)不会结束。通常,设置此属性也会对列表中的下一个属性------即 AutoReverse ------产生一些影响,因为您可能不希望盲目地从相同的初始点重复到目标值的相同过渡。当将 Loop 设置为 True 时,默认情况下就会发生这种情况。
-
AutoReverse:正如我们所见,所有动画都可以反转,并且可以配置为循环。 AutoReverse 属性将动画设置为在第一次运行完成后自动反转(通过切换 Inverse 属性)。因此,如果你正在将值从 A 过渡到 B,一旦达到 B,相同的动画(此时 Inverse 设置为 True )将反向运行;也就是说,从 B 过渡到 A。最终持续时间将遵循(最终的) Delay + Duration (A 到 B)+ Duration (B 到 A)的方案。请注意,在第二次迭代之前不会应用任何 Delay ;此功能通常与将 Loop 设置为 True 相关联(尽管它与它并不严格相关,但仍然可以独立工作)。
-
CurrentTime:这是一个只读属性,它将提供动画的当前执行时间;如果动画未运行,它将是 0。你可以将其视为一个在实际过渡开始时启动的秒表(因此 Delay 永远不会包含在内)。当 Inverse 为 False 时,会看到这个值增加,如果它为 True ,这个值会减小(当这是由 AutoReverse 属性的值引起时,也会发生这种情况)。
-
NormalizedTime:这是用于插值的时间, NormalizedTime 属性让您可以访问当前时间的值,包括插值。如果在动画运行时比较 CurrentTime 和 NormalizedTime ,将看到实际时间与用于插值的时间(使用线性插值时,将看到重合的值)之间的差异。
-
Pause:Pause 属性可以在其值为 True 时暂停动画的执行;可以在动画运行时(或之前)设置此属性。
-
OnProcess事件:可以设置 OnProcess 事件的处理器,以便每次动画将要计算新步骤时收到通知;在正常情况下,事件会频繁的触发。动画的默认帧率为 60,全局动画计时器的间隔也相应设置,因此可以预期每秒调用此事件 60 次。这是一个监控动画的 CurrentTime 或 NormalizedTime 属性值并使用它们进行某些附加目的(同步、日志记录等)的好地方。
-
OnFinish事件:当动画完成所有中间步骤,并且当前过渡值与动画设置的最终期望值一致时, OnFinish 事件就会被触发。
请记住, OnFinish 事件只会发生一次。这意味着如果在同一执行过程中暂停动画------即使多次暂停------ OnFinish 事件也不会被触发。同样适用于 AutoInverse 属性的使用,当其设置为 True 时,基本上会导致动画运行两次,但不会导致 OnFinish 事件触发两次。
2. 使用示例观察动画的执行状态
在这一节中,创建了一个非常简单的例子,它包含一个使小球旋转的TFloatAnimation组件,它的属性设置如下所示:
- AutoReverse为True,表示动画会反向进行一次。
- Enabled为False,表示动画不会在启动后立即执行。
- Deploy为1,表示延迟1秒,Duration为5表示5秒跑完。
整个用户界面布局如下所示:
在主窗体上添加了5个按钮,一个TMemo和一个TProgressBar控件,添加了一个TTimer控件用来计时。
我们分别为TFloatAnimation的OnProcess和OnFinish添加了事件,并且在TTimer的OnTimer事件中进行全局的计时,代码如下:
Pascal
// FloatAnimation1动画完成事件处理程序
procedure TfrmMain.FloatAnimation1Finish(Sender: TObject);
begin
// 在Memo1控件中添加一行日志,记录动画完成的时间
Memo1.Lines.Add(TimeToStr(Now) + ': ' + '动画已完成');
// 重置开始时间标记,0通常表示未开始或已结束
FStartedAt:=0;
end;
// FloatAnimation1动画处理过程事件(每帧触发)
procedure TfrmMain.FloatAnimation1Process(Sender: TObject);
// 声明局部变量
var
// t: 用于存储标准化时间进度(0.0到1.0)
t,
// Scale: 声明但未使用的变量,可能用于缩放计算
Scale: Single;
begin
// 在Label3上显示动画的当前时间和标准化时间
// FormatFloat将浮点数格式化为指定格式的字符串(保留3位小数)
Label3.Text :=
// 显示动画从开始到现在经过的时间(秒)
'当前时间: ' + FormatFloat('0.000', FloatAnimation1.CurrentTime) +
// 显示动画的标准化进度(0.0到1.0)
'插值时间: ' + FormatFloat('0.000', FloatAnimation1.NormalizedTime);
// 获取当前的标准化时间进度(0.0表示开始,1.0表示结束)
t := FloatAnimation1.NormalizedTime;
// 在Label1上显示动画进度百分比
// Format函数将浮点数格式化为百分比字符串(保留1位小数)
Label1.Text := Format('进度: %.1f%%', [t * 100]);
// 设置进度条的值,将标准化时间转换为百分比(0-100)
ProgressBar1.Value:=t*100;
end;
// 定时器Timer1的定时事件处理程序(按Interval间隔定期触发)
procedure TfrmMain.Timer1Timer(Sender: TObject);
begin
// 声明并初始化一个空字符串,用于构建状态信息
var LStr := '';
// 检查动画是否启用
if FloatAnimation1.Enabled then
// 如果启用,在状态字符串后添加"-Enabled"标记
LStr := LStr + '-Enabled';
// 检查动画是否正在运行
if FloatAnimation1.Running then
// 如果正在运行,在状态字符串后添加"-Running"标记
LStr := LStr + '-Running';
// 检查动画是否处于反向播放模式
if FloatAnimation1.Inverse then
// 如果处于反向模式,在状态字符串后添加"-Inverse"标记
LStr := LStr + '-Inverse';
// 检查动画是否有有效的开始时间(FStartedAt > 0)
if FStartedAt>0 then
// 在Label2上显示动画状态和从开始到现在经过的毫秒数
// MilliSecondsBetween计算两个时间点之间的毫秒数差
// ToString将数值转换为字符串
Label2.Text := LStr + ' ' + MilliSecondsBetween(FStartedAt, Now).ToString + 'ms';
end;
接下来测试按下不同的按钮,会发生什么。
2.1 当单击"启动动画"按钮,
它会调用FloatAnimation.Start,并给FStartedAt赋当前时间值:
Pascal
procedure TfrmMain.btnStartAnimationClick(Sender: TObject);
begin
// 启动FloatAnimation1动画,开始播放
FloatAnimation1.Start;
// 记录动画开始的时间点,Now函数返回当前日期和时间
FStartedAt := Now;
end;
运行时可以很清楚的看到运行状态,直到出现"动画已完成"的提示:
当调用FloatAnimation.Start后,TTimer开始显示FloatAnimation的状态,可以看到Enabled、Running和Inverse三个状态分别为True,并且在延时1秒之后,CurrentTime和NormalizedTime才开始显示值,进度条开始动起来。
FloatAnimation使用的是Quadratic插值算法,可以看到CurrentTime和NormalizedTime的值其实并不重合。
FloatAnimation的OnFinish事件直到反向动画完成,实际上经历了2个5秒,再加上延迟的1秒,因此最终经历了差不多11秒时长。
2.2 如果按下"暂停/启动动画"按钮,会出现什么效果?
在启动动画后,按下暂停键,pause属性设置为True后,动画仍然处于Running状态,所以无法单击"启动动画"按钮让动画重新运作。但是可以单击"停止动画"按钮,让动画处于停止状态,停止之后,CurrentTime和NormalizedTime归0。
虽然动画已经停止,但是Pause仍然为False状态,所以在下次启动动画时,需要将Pause设置为True,动画才能重新启动,这跟很多播放器完全不相同
特别注意:暂停并不会触发OnFinish事件,无论暂停多少次。
按钮的事件处理代码如下所示:
Pascal
// 暂停/继续动画按钮的点击事件处理程序
procedure TfrmMain.btnPauseAnimationClick(Sender: TObject);
begin
// 切换动画的暂停状态:如果当前是暂停则继续,如果是播放则暂停
FloatAnimation1.Pause := not FloatAnimation1.Pause;
// 在Memo1控件中添加一行日志,记录当前时间和暂停状态
// TimeToStr(Now)将当前时间转换为字符串格式
// BoolToStr将布尔值转换为字符串,True参数表示返回'True'/'False'而不是'-1'/'0'
Memo1.Lines.Add(TimeToStr(Now) + ': ' + '暂停状态 = ' + BoolToStr(FloatAnimation1.Pause, True));
end;
2.3 如果按下"启用/禁用动画"按钮,会出现什么效果?
"启用/禁用动画"按钮会设置动画的Enable属性,如果设置Enable属性为True,则如同调用Start方法,动画立即开始运行。如果Enable为False,则动画立即停止,并触发OnFinish事件。
代码如下所示:
Pascal
// 启用/禁用动画按钮的点击事件处理程序
procedure TfrmMain.btnEnableDisableAnimationClick(Sender: TObject);
begin
// 切换动画的启用状态:如果当前启用则禁用,如果禁用则启用
FloatAnimation1.Enabled := not FloatAnimation1.Enabled;
// 在Memo1控件中添加一行日志,记录当前时间和启用状态
Memo1.Lines.Add(TimeToStr(Now) + ': ' + 'Enabled 状态 = ' + BoolToStr(FloatAnimation1.Enabled, True));
// 如果动画被启用
if FloatAnimation1.Enabled then
// 重新记录开始时间
FStartedAt := Now;
end;
效果如下所示:
可以看到,禁用时,OnFinish被触发,CurrentTime和NormalizedTime的时间被重置。
2.4 如果按下"停止动画"按钮,会出现什么效果?
"停止动画"按钮调用Stop方法,代码如下:
Pascal
// 停止动画按钮的点击事件处理程序
procedure TfrmMain.btnStopAnimationClick(Sender: TObject);
begin
// 检查动画是否正在运行中
if FloatAnimation1.Running then
// 如果正在运行,则停止动画
FloatAnimation1.Stop;
end;
运行效果如下所示:
它与调用Enable=False相似,动画立即停止,并重置了显示时间。
2.5 如果按下"在当下位置停止"按钮,会出现什么效果?
这个按钮的事件处理代码如下:
Pascal
// 停止在当前状态按钮的点击事件处理程序
procedure TfrmMain.btnStopAtAnimationClick(Sender: TObject);
begin
// 检查动画是否正在运行中
if FloatAnimation1.Running then
// 如果正在运行,则停止动画并保持当前属性值
FloatAnimation1.StopAtCurrent;
end;
效果如下所示:
可以看到,StopAtCurrent方法,并不会重置时间,它保留了CurrentTime和NormalizedTime的值,但动画确实已经停止,OnFinish已经触发。
总结
当设计多个动画联合时,有时候要获取或设置动画的状态,或者是要获取动画的执行时间来进行下一步的处理,这个时候了解动画的执行状态就特别有必要。
这节通过一个案例,详细介绍了不同的动画属性在不同的状态下的值,相信对于初学者,会有一定的启发作用。