在 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 才能实现非一次性动画。