[FMX] Delphi 中 TAnimation 设计的几点值得商榷的地方

在 FMX 中,动画是很好用的一个东西,但是 FMX 动画的基类 TAnimation 的设计,在我看来有一点值得商榷,我们来看 Start 函数的实现:

procedure TAnimation.Start;
var
  Control: IControl;
  SaveDuration: Single;
begin
  if not FLoop then
    FTickCount := 0;
  if Supports(Parent, IControl, Control) and (not Control.Visible) then
    Exit;
  if AutoReverse then
  begin
    if Running then
      FInverse := FSavedInverse
    else
      FSavedInverse := FInverse;
  end;
  if (Abs(FDuration) < 0.001) or (Root = nil) or (csDesigning in ComponentState) then
  begin
    { immediate animation }
    SaveDuration := FDuration;
    try
      FDelayTime := 0;
      FDuration := 1;
      if FInverse then
        FTime := 0
      else
        FTime := FDuration;
      FRunning := True;
      ProcessAnimation;
      DoProcess;
      FRunning := False;
      FTime := 0;
      DoFinish;
    finally
      FDuration := SaveDuration;
    end;
  end
  else
  begin
    FDelayTime := FDelay;
    FRunning := True;
    if FInverse then
      FTime := FDuration
    else
      FTime := 0;
    if FDelay = 0 then
    begin
      FirstFrame;
      ProcessAnimation;
      DoProcess;
    end;

    if AniThread = nil then
      FAniThread := TAniThread.Create;

    TAniThread(AniThread).AddAnimation(Self);
    if not AniThread.Enabled then
      Stop
    else
      FEnabled := True;
  end;
end;

注意两个地方:

第一个地方:

if Supports(Parent, IControl, Control) and (not Control.Visible) then
    Exit;

个人认为动画对象的基类并不适合与控件做必需的关联,因为我们有可能只是要让其一个动画的启动器而已,自己去做一些额外的工作实现动画,显示,加上这两个判断并不合适。这两个判断更适应放在下一级的子类中实现,如叫 TControlAnimation,然后才去检查这些东西。

第二个地方:

if (Abs(FDuration) < 0.001) or (Root = nil) or (csDesigning in ComponentState) then

同样的,这里检查 Root 我觉得也没啥大必要,基类不应该管这事。

这两点地方,是我觉得官方设计过度的地方,毕竟做一个动画的调度基类,更多的事还是应该交给子类来做。

分享一段代码:

uses FMX.utils,FMX.Ani;
type
TCustomFloatAnimation = class(TAnimation)
  private
    procedure SetCurrentValue(const Value: Single);
  protected
    FStartValue, FStopValue, FCurrentValue: Single;
    FStartFromCurrent: Boolean;
    procedure ProcessAnimation; override;
  published
    property StartValue: Single read FStartValue write FStartValue nodefault;
    property StartFromCurrent: Boolean read FStartFromCurrent
      write FStartFromCurrent default False;
    property StopValue: Single read FStopValue write FStopValue
      stored True nodefault;
  public
    property CurrentValue: Single read FCurrentValue write SetCurrentValue;
  end;

{ TCustomFloatAnimation }

procedure TCustomFloatAnimation.ProcessAnimation;
begin
  inherited;
  FCurrentValue := InterpolateSingle(StartValue, StopValue, NormalizedTime);
end;

procedure TCustomFloatAnimation.SetCurrentValue(const Value: Single);
var
  AMin, AMax: Single;
begin
  if FCurrentValue <> Value then
  begin
    if StartValue < StopValue then
    begin
      AMin := StartValue;
      AMax := StopValue;
    end
    else
    begin
      AMin := StopValue;
      AMax := StartValue;
    end;
    if Value < AMin then
      FCurrentValue := AMin
    else if Value > AMax then
      FCurrentValue := AMax
    else
      FCurrentValue := Value;
  end;
end;

这个是TFloatAnimation的非必需绑定属性的版本,在 OnProcess 事件中处理动画效果。但由于上面所说的权限,你创建的这个子类实现,必需指定它的 Root 才能实现非一次性动画。

分享到: