1

所以,我什至不知道如何写正确的标题。

我想要做的是动画让我们说一个进度条的位置。

可以讨论如何使用计时器和循环等来做到这一点。

但是,我希望能够做这样的事情:

  1. ProgressBar1.Position:=Animate(ToValue); 或者
  2. 动画(ProgressBar1.Position,ToValue);

这可能吗?

创建从整数继承的组件不起作用。

我使用指针尝试了 2 号并进行了此过程

procedure TForm1.Animate(ToValue: integer;  var Dest: Integer);
begin    
  Dest:=ToValue;
end;

它确实改变了进度条内部的位置值,但进度条在视觉上没有改变。

如果有人知道如何做到这一点,那就太好了。

谢谢!

4

4 回答 4

2

如果你有一个相对较新的 Delphi 版本,这是一个围绕TTimerusing的动画包装器anonymous methods

type
  Animate = class
    private
      class var fTimer : TTimer;
      class var fStartValue : Integer;
      class var fEndValue : Integer;
      class var fProc : TProc<Integer>;
      class Constructor Create;
      class Destructor Destroy;
      class procedure OnTimer(Sender : TObject);
    public
      class procedure Run( aProc : TProc<Integer>; 
                           fromValue, ToValue, AnimationDelay : Integer);
  end;

class constructor Animate.Create;
begin
  fTimer := TTimer.Create(nil);
  fTimer.Enabled := false;
  fTimer.OnTimer := Animate.OnTimer;
end;

class destructor Animate.Destroy;
begin
  fTimer.Free;
end;

class procedure Animate.OnTimer(Sender: TObject);
begin
  if Assigned(fProc) then
  begin
    if (fStartValue <= fEndValue) then
    begin
      fProc(fStartValue);
      Inc(fStartValue);
    end
    else
      fTimer.Enabled := false;
  end;
end;

class procedure Animate.Run( aProc: TProc<Integer>; 
                             fromValue, ToValue, AnimationDelay: Integer);
begin
  fTimer.Interval := AnimationDelay;
  fStartValue := fromValue;
  fEndValue := ToValue;
  fProc := aProc;
  fTimer.Enabled := (fStartValue <= fEndValue);
end;

该类Animate在应用程序启动/停止时自初始化和自毁。只能激活一个动画进程。

以这种方式使用它:

Animate.Run(
  procedure( aValue : Integer)
  begin 
    ProgressBar1.Position := aValue;
    ProgressBar1.Update;
  end,
  1,100,5
);

正如评论中所讨论的,上面的代码使用类变量和类函数。缺点是只有一个动画可以激活。

这是一个更完整的动画类,您可以在其中实例化任意数量的动画。扩展功能,可以停止/继续,准备就绪时添加事件,以及更多属性。

unit AnimatePlatform;

interface

uses
  System.Classes,System.SysUtils,Vcl.ExtCtrls;

type
  TAnimate = class
    private
      fTimer : TTimer;
      fLoopIx : Integer;
      fEndIx : Integer;
      fProc : TProc<Integer>;
      fOnReady : TProc<TObject>;
      procedure OnTimer(Sender : TObject);
      function GetRunning : boolean;
      procedure SetReady;
    public
      Constructor Create;
      Destructor Destroy; override;
      procedure Run( aProc : TProc<Integer>;
                     FromValue,ToValue,AnimationDelay : Integer); overload;
      procedure Run( aProc : TProc<Integer>;
                     FromValue,ToValue,AnimationDelay : Integer;
                     AReadyEvent : TNotifyEvent); overload;
      procedure Run( aProc : TProc<Integer>;
                     FromValue,ToValue,AnimationDelay : Integer;
                     AReadyEvent: TProc<TObject>); overload;
      procedure Stop;
      procedure Proceed;
      property ActualLoopIx : Integer read fLoopIx write fLoopIx;
      property Running : boolean read GetRunning;
      property OnReady : TProc<TObject> read fOnReady write fOnReady;
  end;

implementation

constructor TAnimate.Create;
begin
  Inherited;
  fTimer := TTimer.Create(nil);
  fTimer.Enabled := false;
  fTimer.OnTimer := Self.OnTimer;
  fOnReady := nil;
end;

destructor TAnimate.Destroy;
begin
  fTimer.Free;
  Inherited;
end;

function TAnimate.GetRunning: boolean;
begin
  Result := fTimer.Enabled;
end;

procedure TAnimate.OnTimer(Sender: TObject);
begin
  if Assigned(fProc) then
  begin
    if (fLoopIx <= fEndIx) then
    begin
      fProc(fLoopIx);
      Inc(fLoopIx);
    end;
    if (fLoopIx > fEndIx) then
      SetReady;
  end
  else SetReady;
end;

procedure TAnimate.Proceed;
begin
  fTimer.Enabled := true;
end;

procedure TAnimate.Run(aProc: TProc<Integer>; FromValue, ToValue,
  AnimationDelay: Integer; AReadyEvent: TNotifyEvent);
begin
  Run(aProc,FromValue,ToValue,AnimationDelay);
  fOnReady := AReadyEvent;
end;

procedure TAnimate.Run(aProc: TProc<Integer>; FromValue, ToValue,
  AnimationDelay: Integer; AReadyEvent: TProc<TObject>);
begin
  Run(aProc,FromValue,ToValue,AnimationDelay);
  fOnReady := AReadyEvent;
end;

procedure TAnimate.Run(aProc: TProc<Integer>; fromValue, ToValue,
  AnimationDelay: Integer);
begin
  fTimer.Interval := AnimationDelay;
  fLoopIx :=         fromValue;
  fEndIx :=          ToValue;
  fProc :=           aProc;
  fTimer.Enabled :=  true;
end;

procedure TAnimate.SetReady;
begin
  Stop;
  if Assigned(fOnReady) then
    fOnReady(Self);
end;

procedure TAnimate.Stop;
begin
  fTimer.Enabled := false;
end;

end.

更新:

这里没有使用基于动画师的版本,而是TTimer使用anonymous thread:

uses
  SyncObjs;

procedure AnimatedThread( aProc: TProc<Integer>;
                          FromValue, ToValue, AnimationDelay: Integer;
                          AReadyEvent: TNotifyEvent);
begin
  TThread.CreateAnonymousThread(
    procedure
    var
      i: Integer;
      w : TSimpleEvent;
    begin
      w := TSimpleEvent.Create(Nil,False,False,'');
      try
        for i := FromValue to ToValue do begin
          TThread.Synchronize(nil,
            procedure
            begin
              aProc(i);
            end
          );
          w.WaitFor(AnimationDelay);
        end;
      finally
        w.Free;
      end;
      if Assigned(AReadyEvent) then
        TThread.Synchronize(nil,
          procedure
          begin
            AReadyEvent(Nil);
          end
        );
    end
  ).Start;
end;

// Example call

AnimateThread(
  procedure(aValue: Integer)
  begin 
    ProgressBar1.Position := aValue;
    ProgressBar1.Update;
  end,
  1,100,5,nil
); 
于 2013-03-10T14:08:05.640 回答
1

您可以使用 RTTI 轻松完成此操作。

您无法避免编写循环,但您可以编写一次并为您想要设置的任何对象/属性调用Animate方法。当然,编写这样的函数仍然很棘手,因为您必须考虑闪烁、UI 阻塞的时间等。

一个非常简单的例子是:

implementation
uses RTTI;


procedure TForm1.Animate(AObj: TObject; APropertyName: string; AValue: Integer);
var
  Context: TRTTIContext;
  OType: TRTTIType;
  Prop: TRTTIProperty;
  StartValue: Integer;
begin
  Context := TRTTIContext.Create;
  OType := context.GetType(AObj.ClassType);
  Prop := OType.GetProperty(APropertyName);
  StartValue := Prop.GetValue(AObj).AsInteger;
  for AValue := StartValue to AValue do
  begin
    Prop.SetValue(AObj, AValue);
    if AObj is TWinControl then
    begin
      TWinControl(AObj).Update;
      Sleep(3);
    end;
  end;
end;


//call it like this:
procedure TForm1.Button1Click(Sender: TObject);
begin
  Animate(ProgressBar1, 'Position', 30);
  Animate(Self, 'Height', 300);
end;
于 2013-03-10T08:43:21.020 回答
1

正如大卫所说,您将需要使用计时器。这是一些演示原理的代码。我建议你接受这个想法并将它们融入你自己的 TProgressbar 后代。

请注意,在 Vista 和 Windows 7 下,TProgressBar 在增加位置时有一些内置动画。使用您自己的动画时,这可能会产生奇怪的效果。

您没有提及您使用的是哪个版本的 Delphi。此示例是使用 XE2 创建的。如果您使用的是早期版本,您可能需要修复uses 子句中的虚线单元名称,例如Winapi.Windows 应该是Windows。

代码:

unit Unit11;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ComCtrls, Vcl.StdCtrls, Vcl.ExtCtrls,
  Vcl.Samples.Spin;

type
  TForm11 = class(TForm)
    ProgressBar1: TProgressBar;
    Timer1: TTimer;
    Button1: TButton;
    Button2: TButton;
    spnIncrement: TSpinEdit;
    Label1: TLabel;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
  private
    FDestPos: Integer;
    FProgInc: Integer;
    procedure AnimateTo(const DestPos, Increment: Integer);
  public
    { Public declarations }
  end;

var
  Form11: TForm11;

implementation

{$R *.dfm}

procedure TForm11.Button1Click(Sender: TObject);
begin
  AnimateTo(10, spnIncrement.Value);
end;

procedure TForm11.Button2Click(Sender: TObject);
begin
  AnimateTo(90, spnIncrement.Value);
end;

procedure TForm11.Timer1Timer(Sender: TObject);
begin
  if ((FProgInc > 0) and (ProgressBar1.Position + FProgInc >= FDestPos)) or
     ((FProgInc < 0) and (ProgressBar1.Position + FProgInc <= FDestPos)) then
  begin
    ProgressBar1.Position := FDestPos;

    Timer1.Enabled := FALSE;
  end
  else
  begin
    ProgressBar1.Position := ProgressBar1.Position + FProgInc;
  end;
end;

procedure TForm11.AnimateTo(const DestPos, Increment: Integer);
begin
  FDestPos := DestPos;

  FProgInc := Increment;

  if FDestPos < ProgressBar1.Position then
    FProgInc := -FProgInc;

  Timer1.Enabled := FProgInc <> 0;
end;

end. 

DFM:

object Form11: TForm11
  Left = 0
  Top = 0
  BorderStyle = bsDialog
  Caption = 'Animated Progressbar'
  ClientHeight = 77
  ClientWidth = 466
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'Tahoma'
  Font.Style = []
  OldCreateOrder = False
  Position = poScreenCenter
  PixelsPerInch = 96
  TextHeight = 13
  object Label1: TLabel
    Left = 309
    Top = 42
    Width = 53
    Height = 13
    Caption = 'Increment:'
  end
  object ProgressBar1: TProgressBar
    Left = 24
    Top = 16
    Width = 417
    Height = 17
    TabOrder = 0
  end
  object Button1: TButton
    Left = 24
    Top = 39
    Width = 75
    Height = 25
    Caption = '10%'
    TabOrder = 1
    OnClick = Button1Click
  end
  object Button2: TButton
    Left = 105
    Top = 39
    Width = 75
    Height = 25
    Caption = '90%'
    TabOrder = 2
    OnClick = Button2Click
  end
  object spnIncrement: TSpinEdit
    Left = 368
    Top = 39
    Width = 73
    Height = 22
    MaxValue = 100
    MinValue = 1
    TabOrder = 3
    Value = 0
  end
  object Timer1: TTimer
    Enabled = False
    Interval = 20
    OnTimer = Timer1Timer
    Left = 240
    Top = 40
  end
end
于 2013-03-10T12:35:09.283 回答
0

您不能将除整数以外的任何内容分配给进度条的位置。因此,如果要使位置从一个值平滑地移动到另一个值,则需要将位置设置为每个单独的值。

没有方便的捷径。没有什么可以像 jQuery 的 animate() 方法那样开箱即用。您提到了计时器和循环。这些是您需要使用的方法。

于 2013-03-10T08:43:55.793 回答