Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

FireMonkey controls do not animate smoothly

Background

I've created a GUI using some FireMonkey controls.

  • Some controls are animated and their appearance updates automatically.
  • Some controls only update in response to user interaction (sliders etc).

Problem

Interaction with the user controls prevents updates to the animated controls, resulting in jerky discontinuous animation.

Video of glitchy animation

The animated control in the video above is driven by a TTimer component. The problem persists when using FireMonkey's animation components.

Investigation

The slider controls call Repaint() when adjusted. Smoothly adjusting a slider will generate a dense stream of Repaint() calls which block other controls from being updated.

What To Do?

Freezing animations while one control is continuously updated is not appropriate for my application. My first thought is to swap the Repaint() calls for something similar to the VCL Invalidate() method, but FireMonkey doesn't have anything comparable AFAIK.

Is there a good workaround for this problem?

like image 905
Shannon Matthews Avatar asked Dec 07 '11 06:12

Shannon Matthews


1 Answers

I've created a timer based repaint method as Arnaud Bouchez suggested in the comments above. So far it seems to work.

Code

unit FmxInvalidateHack;

interface

uses
  Fmx.Types;

procedure InvalidateControl(aControl : TControl);


implementation

uses
  Contnrs;

type
  TInvalidator = class
  private
  protected
    Timer : TTimer;
    List  : TObjectList;
    procedure Step(Sender : TObject);
  public
    constructor Create;
    destructor Destroy; override;

    procedure AddToQueue(aControl : TControl);
  end;

var
  GlobalInvalidator : TInvalidator;

procedure InvalidateControl(aControl : TControl);
begin
  if not assigned(GlobalInvalidator) then
  begin
    GlobalInvalidator := TInvalidator.Create;
  end;
  GlobalInvalidator.AddToQueue(aControl);
end;


{ TInvalidator }

constructor TInvalidator.Create;
const
  FrameRate = 30;
begin
  List  := TObjectList.Create;
  List.OwnsObjects := false;

  Timer := TTimer.Create(nil);
  Timer.OnTimer  := Step;
  Timer.Interval := round(1000 / FrameRate);
  Timer.Enabled  := true;
end;

destructor TInvalidator.Destroy;
begin
  Timer.Free;
  List.Free;
  inherited;
end;

procedure TInvalidator.AddToQueue(aControl: TControl);
begin
  if List.IndexOf(aControl) = -1 then
  begin
    List.Add(aControl);
  end;
end;

procedure TInvalidator.Step(Sender: TObject);
var
  c1: Integer;
begin
  for c1 := 0 to List.Count-1 do
  begin
    (List[c1] as TControl).Repaint;
  end;
  List.Clear;
end;


initialization

finalization
  if assigned(GlobalInvalidator) then GlobalInvalidator.Free;

end.

==

Usage

A control can be repainted by calling:

InvalidateControl(MyControl);

The InvalidateControl() procedure doesn't repaint the control immediately. Instead it adds the control to a list. A global timer later checks the list, calls Repaint() and removes the control from the list. Using this method, a control can be invalidated as needed but will not block other controls from being updated, as rapid Repaint() calls do.

like image 53
Shannon Matthews Avatar answered Sep 19 '22 05:09

Shannon Matthews