［小ネタ］クラスヘルパーでタブコントロールのアニメーションを早める

#はじめに
Firemonkeyの<code>TTabControl</code> はスマートフォンアプリ作成においてページの切り替えに使用したりと応用範囲が広いのですが、如何せん切り替え速度が0.3秒固定は使いづらいっ・・と誰もが感じていると思います。
なんとかしたい！
![tabQuickAnimetion.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33684/4109dca5-0db5-6455-6b5d-13ca490cf196.gif)　下が改良版（0.2秒）

##やること
<code>TTabControl.SetActiveTabWithTransitionAsync</code>メソッドにて、遷移アニメーション時間に固定値が指定されていることが問題なので、このメソッドをまるっとコピーし、当該の固定値をメソッドの引数に置き換えます。

<code>TTabControl</code> の<code>Private</code>フィールド・メソッドを呼ぶ箇所が多いので、with文で回避します。
（参考：[【Delphi】クラスヘルパによる private メンバアクセスの件を with 文で回避できると聞いて](https://qiita.com/ht_deko/items/0a6588819c5102646a79) ）

途中、<code>TTabItem.Content</code>のスコープが<code>Protected</code>の為、こちらもクラスヘルパーで読めるようにします。

```pascal
//宣言部
type
  TTabItemHelper = class helper for TTabItem
    function ReadContent:TContent;
  end;

  TTabTransitionEx = (None, Slide, Overlap);

  TTabControlHelper = class helper for TTabControl
    procedure DoTransition(
      const ATab: TTabItem;
      const ATransition: TTabTransitionEx;
      const ADirection: TTabTransitionDirection;
      const ADuration:Single;
      const AOnFinish: TProc);
  end;
```

```pascal
//実装部
{ TTabItemHelper }
function TTabItemHelper.ReadContent: TContent;
begin
  result := Self.Content;
end;

{ TTabControlHelper }
procedure TTabControlHelper.DoTransition(
  const ATab: TTabItem; const ATransition: TTabTransitionEx;
  const ADirection: TTabTransitionDirection; const ADuration: Single;
  const AOnFinish: TProc);

  procedure AnimateControlPositionX(AParent: TFmxObject; 
    const NewValue: Integer; const AProgressHandler: TNotifyEvent;
    const AFinishHandler: TNotifyEvent);
  var
    A: TIntAnimation;
  begin
    TAnimator.StopPropertyAnimation(AParent, 'Position.X');

    A := TIntAnimation.Create(AParent);
    A.Parent := AParent;
    A.AnimationType := TAnimationType.In;
    A.Interpolation := TInterpolationType.Linear;
    A.OnFinish := AFinishHandler;
    A.OnProcess := AProgressHandler;
    A.Duration := ADuration; //変更箇所
    A.PropertyName := 'Position.X';
    A.StartFromCurrent := True;
    A.StopValue := NewValue;
    A.Start;
  end;

var
  PreviousTab, NewTab: TTabItem;
  PreviousTabContent, NewTabContent: TControl;
  LayoutRect: TRectF;
  P, LayoutPos: TPointF;
begin
  if ATab = ActiveTab then
    Exit;

  FinishCurrentTabTransition;

  if ATransition = TTabTransitionEx.None then
  begin
    ActiveTab := ATab;
    Exit;
  end;

  with Self do
  begin
    FTransitionRunning := True;
    ClipChildren := True;
    FTransitionFinishedCallback := AOnFinish;

    LayoutPos := ActiveTab.ReadContent.LocalToAbsolute(TPointF.Zero);
    LayoutRect := ActiveTab.ReadContent.BoundsRect;

    PreviousTab := ActiveTab;
    PreviousTabContent := ActiveTab.ReadContent;
    ActiveTab := ATab;
    NewTab := ATab;
    NewTabContent := ActiveTab.ReadContent;
    FTransitionTabs := [PreviousTab, NewTab];

    DisableDisappear(PreviousTab);

    PreviousTabContent.Visible := True;
    NewTabContent.Visible := True;
    if not NewTab.DisableDisappear then
    begin
      DisableDisappear(NewTab);
      PreloadContent(NewTabContent);
    end;

    P := PreviousTab.AbsoluteToLocal(LayoutPos);
    PreviousTabContent.SetBounds(P.X, P.Y, LayoutRect.Width, LayoutRect.Height);

    if ADirection = TTabTransitionDirection.Normal then
    begin
      if ATransition = TTabTransitionEx.Slide then
        AnimateControlPositionX(PreviousTabContent, Round(P.X - LayoutRect.Width), nil, nil);

      P := NewTab.AbsoluteToLocal(LayoutPos);
      NewTabContent.SetBounds(P.X + LayoutRect.Width, P.Y, LayoutRect.Width, LayoutRect.Height);
      AnimateControlPositionX(NewTabContent, Round(P.X), WebBrowserRealign, AnimationFinished);
    end
    else
    begin
      if ATransition = TTabTransitionEx.Slide then
        AnimateControlPositionX(PreviousTabContent, Round(P.X + LayoutRect.Width), nil, nil);

      P := NewTab.AbsoluteToLocal(LayoutPos);
      NewTabContent.SetBounds(P.X - LayoutRect.Width, P.Y, LayoutRect.Width, LayoutRect.Height);
      AnimateControlPositionX(NewTabContent, Round(P.X), WebBrowserRealign, AnimationFinished);
    end;
  end;
end;
```
　
<code>TTabTransitionEx</code>については遊び心というか・・Overlapを指定すると、ページが被さる様なアニメーションにできます。
![tabOverlapAnimetion.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33684/b4a4106a-8689-95a0-16f6-b9a37de9870c.gif)
工夫すれば色々な切り替えアニメーションが作れそうですが、<code>FinishCurrentTabTransition</code>の中で停止させているアニメーションが<code>Position.X</code>のみなので、あまり無茶はできないかもしれません（未検証）。

#おわりに
クラスヘルパーのwith文によるPriveteメンバアクセスが今後も可能かわからないので、そういったリスクを抱えることになることはご留意ください。
個人的にはTTabControlが改良されてアニメーション時間だけでも設定できるようになると良いなあ、と思っています。

また、TGridLayout等を使ってタブコントロールを自作する、という手もあるそうですので、そういった沼も面白いと思います！