一步一步学习使用FireMonkey动画(6) 用实例理解动画的运行状态

虽然在过去的几节课中,已经详细的介绍了不少的属性和方法以及事件,本节来看一看动画的执行状态。

回顾一下前面的几节内容,可以看到,每一个动画都具有如下的属性和方法,请仔细阅读下面的属性或事件的作用,在接下来的案例中将会使用到它们:

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已经触发。

总结

当设计多个动画联合时,有时候要获取或设置动画的状态,或者是要获取动画的执行时间来进行下一步的处理,这个时候了解动画的执行状态就特别有必要。

这节通过一个案例,详细介绍了不同的动画属性在不同的状态下的值,相信对于初学者,会有一定的启发作用。

相关推荐
@Demi13 小时前
vsCode或Cursor 使用remote-ssh插件链接远程终端
服务器·ide·vscode·ssh
lincats14 小时前
一步一步学习使用FireMonkey动画(5) 动画图解11种动画插值类型
ide·移动开发·delphi 12.3·firedac·firemonkey
王伯爵14 小时前
Visual Studio Code (VS Code) 工作区配置文件的作用
ide·vscode·状态模式
南风里18 小时前
Android Studio下载gradle文件很慢的捷径之路
android·ide·android studio
lincats20 小时前
一步一步学习使用FireMonkey动画(4) 使用Delphi的基本动画组件类,路径和位图列表动画 弹跳小球和奔跑的小人示例
livebindings·delphi 12.3·firemonkey
后天han1 天前
vscode中launch.json中定义的编译文件名于生成的不一致修改
ide·vscode·编辑器
lincats1 天前
一步一步学习使用FireMonkey动画(3) 使用Delphi的基本动画组件类
ide·delphi·delphi 12.3·firemonkey
在嵌入式里摸爬滚打1 天前
VScode远程连接Ubuntu报错问题分析
ide·vscode·编辑器
funfan05171 天前
IDEA基础配置优化指南(中英双版)
java·ide·intellij-idea