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