8
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

[Delphi] Transition の使い方

Posted at

Transition

なぜか、Transition について書いた記事があまりないので、Transition の紹介。
まずは、↓これを(サイズ大きめなので表示までに時間かかるかも&実機ではもっと綺麗に表示されてます)。

Transition を使えば、どこかで見たことのあるエフェクトも(比較的)簡単に作れちゃうもんね!!

Transition とは

日本語では推移効果といい、一般的には2つのシーンを効果付きで切り替える機能です。
Delphi FireMonkey における Transition も同様で、2枚の画像を効果付きで切り替えます。
たとえば、フェードしながら切り替えたり、波紋様に変形しながら切り替えたりできます。

■Fade と Ripple Transition

(上記の画像は Sample ディレクトリに入っている "ShaderFilters" からキャプチャしました。標準のインストール先はC:\Users\Public\Documents\Embarcadero\Studio\22.0\Samples\Object Pascal\Multi-Device Samples\User Interface\ShaderFilters\です)

なお冒頭の Transition は、TBlurTransition を使っています。

実用的な Transition の使い方

2枚の画像を切り替えるだけ、という用途もあまりないと思うので、Transition を利用した画面転換方法を紹介します。

0. 画面&遷移後の画面を作る

■元の画面 (以降 First と呼びます)

■遷移後の画面 (以降 Second と呼びます)

取りあえず適当にコントロールを乗せてこんな感じの画面を作りました。
Second が TPanel に構築されているのは背景をつけるためで、別に TLayout でも構いません。
ここは本来必要な UI ができているハズなので順番は 0 です。

1. Transition の設定

First に Transition をくっつけます。
Transition なら何でもいいのですが、ここではぐにょぐにょするエフェクトをかけてくれる TWiggleTransitionEffect を付けました。

2. FloatAnimation の設定

1 で付けた Transition に TFloatAnimation をくっつけます。

そして、プロパティを以下のように設定します。

プロパティ 意味
Duration 3 アニメーション時間[sec]
ここでは判りやすくするために 3[sec]にしましたが、本来はもっと短く 0.2~0.5 ぐらいが良いと思います
PropertyName Progress 変更する親コントロールのプロパティの名前
StopValue 100 アニメーション終了時の値
Progress プロパティは 0~100% を指定するものなので終了値は 100 です
OnFinish FloatAnimation1Finish アニメーションが終了すると呼ばれるイベント

3. コード

ScreenShot

まず、Second である pnlSecond の OnApplyStyleLookup で Transition にターゲット画像を設定します。
このターゲット画像を pnlSecond のスクリーンショットにすると画面が遷移したように見えます。
スクリーンショットを撮った後はとりあえず必用無いので Visible を False にして非表示にしています。

procedure TfrmSample.pnlSecondApplyStyleLookup(Sender: TObject);
begin
  var Bmp := pnlSecond.MakeScreenshot;
  try
    WiggleTransitionEffect1.Target.Assign(Bmp);
  finally
    Bmp.Free;
  end;

  pnlSecond.Visible := False;
end;

MakeScreenshot はコントロールが Visible で実際に画面上にレンダリング(OnApplyStyleLookup 前には取得できません)されていないと正しく取得できません!

Target を指定しないと Android で意図通り動作しません(黒と混合されます)
Target を指定したくないときは

var Bmp := TBitmap.Create(1, 1);
try
  Bmp.Clear(0);
  WiggleTransitionEffect1.Target.Assign(Bmp);
finally
  Bmp.Free;
end;

として、透明 Bitmap を指定します。

開始

Button1 の OnClick でアニメーションをスタートします。
WiggleTransitionEffect1.Enabled := True;として Transition を有効化しています。

procedure TfrmSample.Button1Click(Sender: TObject);
begin
  WiggleTransitionEffect1.Enabled := True;
  FloatAnimation1.Start;
end;

終了

アニメーションが終了するとここが呼ばれます。
まず、WiggleTransitionEffect1.Enabled := False;で Transition を無効化します。
次に、layoutFirst.Visible := False;で First を非表示にします。
最後に、pnlSecond.Visible := True;で Second を表示します。

procedure TfrmSample.FloatAnimation1Finish(Sender: TObject);
begin
  WiggleTransitionEffect1.Enabled := False;
  layoutFirst.Visible := False;
  pnlSecond.Visible := True;
end;

実行

ここまでを実行すると、ぐにょおおおおと画面が変わります。

冒頭の Transition について

まずこんな画面を作ります。

↓のコードでやっていることは上記のコードと基本的には同じです。
ただ、GridLayout からまるで外に飛び出したように見せるための座標計算、飛び出した画像の大きさを変更するための TFloatAnimation.OnProcess などが書いてあります。
また、TBlurTransitionEffect は画面外に飛び出したように見えるため Target は透明画像を指定しています。

Transition の種類によっては Target 画像を指定しない方が上手く見える事も良くあります。
MakeScreenshot をしなくて良いので使える場合は、こちらの方が楽です)

コード全文
unit Unit1;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Objects,
  FMX.Layouts, FMX.Effects, FMX.Filter.Effects, FMX.Controls.Presentation,
  FMX.StdCtrls, FMX.Ani;

type
  TForm1 = class(TForm)
    layoutGallery: TGridLayout;
    Image1: TImage;
    Image2: TImage;
    Image24: TImage;
    Image3: TImage;
    Image4: TImage;
    Image5: TImage;
    Image6: TImage;
    Image7: TImage;
    Image8: TImage;
    Image9: TImage;
    Image10: TImage;
    Image11: TImage;
    Image12: TImage;
    Image13: TImage;
    Image14: TImage;
    Image15: TImage;
    Image16: TImage;
    Image17: TImage;
    Image18: TImage;
    Image19: TImage;
    Image20: TImage;
    Image21: TImage;
    Image22: TImage;
    Image23: TImage;
    StyleBook1: TStyleBook;
    BlurTransitionEffect1: TBlurTransitionEffect;
    FloatAnimation1: TFloatAnimation;
    layoutOneImageBase: TLayout;
    imgOneImage: TImage;
    imgTarget: TImage;
    layoutRoot: TLayout;
    procedure FloatAnimation1Finish(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FloatAnimation1Process(Sender: TObject);
    procedure ImageClick(Sender: TObject);
    procedure imgOneImageClick(Sender: TObject);
    procedure layoutGalleryResized(Sender: TObject);
    procedure FormKeyDown(Sender: TObject; var Key: Word; var KeyChar: Char;
      Shift: TShiftState);
  private var
    FTarget: TImage;
    FStartPos: TPointF;
    FStartSize: TSizeF;
    FDeltaPos: TPointF;
    FDeltaSize: TSizeF;
  private
    procedure StartTransition(const AInverse: Boolean);
  public
  end;

var
  Form1: TForm1;

implementation

{$R *.fmx}

{$IFDEF ANDROID}
uses
  Androidapi.Helpers;
{$ENDIF}

procedure TForm1.FloatAnimation1Finish(Sender: TObject);
begin
  // アニメーションの終了処理
  // Transition 無効化
  BlurTransitionEffect1.Enabled := False;

  // 可視状態変更
  imgTarget.Visible := False;
  FTarget.Visible := True;

  if FloatAnimation1.Inverse then
  begin
    layoutOneImageBase.Visible := False;
    imgOneImage.Bitmap.Assign(nil);
  end
  else
  begin
    layoutOneImageBase.Opacity := 1;
    layoutOneImageBase.Visible := True;
    layoutGallery.Visible := False;
  end;
end;

procedure TForm1.FloatAnimation1Process(Sender: TObject);
begin
  // アニメーションの段階毎に呼ばれる
  // 進捗パーセンテージを計算
  var P := BlurTransitionEffect1.Progress / FloatAnimation1.StopValue;
  if FloatAnimation1.Inverse then
    P := 1 - P;

  // ターゲットの位置と透明度を設定
  imgTarget.Opacity := P;
  imgTarget.SetBounds(
    FStartPos.X + FDeltaPos.X * P,
    FStartPos.Y + FDeltaPos.Y * P,
    FStartSize.cx + FDeltaSize.cx * P,
    FStartSize.cy + FDeltaSize.cy * P);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  // Transition 用透明画像をセット
  var Bmp := TBitmap.Create(1, 1);
  try
    BlurTransitionEffect1.Target.Assign(Bmp);
  finally
    Bmp.Free;
  end;

  imgTarget.Visible := False;
  layoutOneImageBase.Visible := False;
end;

procedure TForm1.FormKeyDown(
  Sender: TObject;
  var Key: Word;
  var KeyChar: Char;
  Shift: TShiftState);
begin
  {$IFDEF ANDROID}
  // Back Key の制御
  if Key = vkHardwareBack then
  begin
    Key := 0;

    if layoutOneImageBase.Visible then
      imgOneImageClick(nil)
    else
      TAndroidHelper.Activity.moveTaskToBack(True);
  end;
  {$ENDIF}
end;

procedure TForm1.ImageClick(Sender: TObject);
begin
  if not (Sender is TImage) then
    Exit;

  // ターゲット設定
  FTarget := TImage(Sender);
  imgOneImage.Bitmap.Assign(FTarget.Bitmap);

  // アニメーション
  StartTransition(False);
end;

procedure TForm1.imgOneImageClick(Sender: TObject);
begin
  // 逆順アニメーション
  StartTransition(True);
end;

procedure TForm1.layoutGalleryResized(Sender: TObject);
begin
  // 画面内が4列になるように計算
  var Size := layoutGallery.Width;
  if Size > layoutGallery.Height then
    Size := layoutGallery.Height;

  Size := (Size - layoutGallery.Padding.Left * 2) / 4;

  layoutGallery.ItemWidth := Size;
  layoutGallery.ItemHeight := Size;
end;

procedure TForm1.StartTransition(const AInverse: Boolean);
const
  ONE_IMAGE_MARGIN = 16 * 2;
begin
  if
    (FTarget = nil) or
    (FTarget.Parent = nil) or
    (imgOneImage.Parent = nil) or
    (not (FTarget.Parent is TControl)) or
    (not (imgOneImage.Parent is TControl))
  then
    Exit;

  // 綺麗に見せるための表示調整
  FTarget.Visible := False;
  layoutGallery.Visible := True;

  layoutOneImageBase.Opacity := 0;
  layoutOneImageBase.Visible := True;

  layoutOneImageBase.BeginUpdate;
  try
    var Size := layoutOneImageBase.Width;
    if Size > layoutOneImageBase.Height then
      Size := layoutOneImageBase.Height;

    Size := Size - ONE_IMAGE_MARGIN;

    imgOneImage.SetBounds(0, 0, Size, Size);
  finally
    layoutOneImageBase.EndUpdate;
  end;

  var StartImg, EndImg: TImage;

  // 座標計算
  if AInverse then
  begin
    StartImg := imgOneImage;
    EndImg := FTarget;
  end
  else
  begin
    StartImg := FTarget;
    EndImg := imgOneImage;
  end;

  // 開始位置・終了位置計算
  FStartPos :=
    TControl(StartImg.Parent).LocalToAbsolute(StartImg.Position.Point);
  var EndPos :=
    TControl(EndImg.Parent).LocalToAbsolute(EndImg.Position.Point);

  FStartPos := layoutRoot.AbsoluteToLocal(FStartPos);
  EndPos := layoutRoot.AbsoluteToLocal(EndPos);

  // 終了位置と開始位置の差
  FDeltaPos.X := EndPos.X - FStartPos.X;
  FDeltaPos.Y := EndPos.Y - FStartPos.Y;

  // 開始状態の大きさ
  FStartSize.cx := StartImg.Width;
  FStartSize.cy := StartImg.Height;

  // 開始と終了の大きさの差
  FDeltaSize.cx := EndImg.Width - StartImg.Width;
  FDeltaSize.cy := EndImg.Height - StartImg.Height;

  // Target Image 設定
  imgTarget.Bitmap.Assign(EndImg.Bitmap);
  imgTarget.Visible := True;

  // スタート
  BlurTransitionEffect1.Enabled := True;

  FloatAnimation1.Inverse := AInverse;
  FloatAnimation1.Start;
end;

end.

まとめ

Transition の使い方をまとめると

  1. Transition を元画面に付ける
  2. TFloatAnimation を Transition に付ける
  3. Transition.Target に遷移先画面の Bitmap か、透明 Bitmap を指定する
  4. 遷移先画面を非表示にする
  5. FloatAnimation.Start でアニメーションをスタートする
  6. アニメーションが終了したら、元画面を非表示にし、遷移先画面を表示する

となります。
言葉で説明すると長いですが、コードは上に上げたたったこれだけです。

Delphi で作られたアプリケーションがもっと華やかになりますように!

8
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?