はじめに
スマートフォン上でもTTabControl
のFullSize
をFalse
にして使いたいなあ、という場面があったのですが、ジェスチャでページ送りするとタブが隠れて辛い!となったので、センタリングするようにしてみました。
(こんな感じ)
Delphi10.3 Community Editionにて確認しています。
記事内のコードの命名の微妙さについては・・ご指摘頂ければと思います。
やり方
移動量の計算
TTabControlのボタン部分はスクロール可能な領域になっており、スクロール位置はTabContentPosition
で取得・変更可能です。やりたいのは中央寄せなので、目標のタブボタンの中心と、タブコントロールの中心の距離を計算します。(下図参照)
function CalcTabCenteringDistance(targetTabIndex:Integer;tabControl:TTabControl):Single;
var
i,n:Integer;
targetTabCenter:Single;
begin
n := Min(targetTabIndex, tabControl.TabCount-1);
targetTabCenter := tabControl.TabContentPosition;
for i := 0 to n do
begin
if i < n then
targetTabCenter := targetTabCenter + tabControl.Tabs[i].Width
else
targetTabCenter := targetTabCenter + tabControl.Tabs[i].Width / 2;
end;
result := tabControl.Width / 2 - targetTabCenter;
end;
※TabItem
にMargin
を設定して使用している場合は、上記の計算では考慮されていないので、targetTabCenter
に足すようコードを修正してください。
アニメーションさせる
実際にアクティブタブを中央寄せするメソッドは以下です。TabContentPosition
の値の範囲は(Width-TabContentSize.Width) ~ 0
なので、一応それも考慮します(内部で超えないようにしてくれているは思う)。
FloatAnimation1
は、TabControl1 の子として配置し、PropatyName
をTabContentPosition
、StartFromCurrent
をTrue
に設定しておきます。
procedure TForm1.AlignActiveTabWithCenter;
var
newPosition:Single;
begin
newPosition := TabControl1.TabContentPosition +
CalcTabCenteringDistance(TabControl1.TabIndex, TabControl1);
if newPosition > 0 then
newPosition := 0
else if newPosition < TabControl1.Width - TabControl1.TabContentSize.Width then
newPosition := TabControl1.Width - TabControl1.TabContentSize.Width;
FloatAnimation1.StopValue := newPosition;
FloatAnimation1.Start;
end;
上手くいくと思ったのに!
さっそく上記のAlignActiveTabWithCenter
メソッドをTabControl.OnChange
イベントで呼び出して・・。と、やってみると、中央寄せはされますが、アニメーションはスキップされてしまいます。なぜだ。
代替案
色々試してみると、どうもタブの遷移アニメーション中は、ボタン部分のアニメーションが動かないようです。
よって、タブ遷移完了後にAlignActiveTabWithCenter
を呼ぶ必要があります。
その辺りを考慮して次のように実装してみました。
var FAligningActiveTab : Boolean;
procedure TForm1.PrevButtonClick(Sender: TObject);
var
newIndex:Integer;
begin
newIndex := TabControl1.FindVisibleTab(TTabControl.TFindKind.Back);
if newIndex > -1 then
begin
FAligningActiveTab := True;
TabControl1.SetActiveTabWithTransitionAsync(
TabControl1.Tabs[newIndex],
TTabTransition.Slide,
TTabTransitionDirection.Reversed,
AlignActiveTabWithCenter );
end;
end;
procedure TForm1.NextButtonClick(Sender: TObject);
var
newIndex:Integer;
begin
newIndex := TabControl1.FindVisibleTab(TTabControl.TFindKind.Next);
if newIndex > -1 then
begin
FAligningActiveTab := True;
TabControl1.SetActiveTabWithTransitionAsync(
TabControl1.Tabs[newIndex],
TTabTransition.Slide,
TTabTransitionDirection.Normal,
AlignActiveTabWithCenter );
end;
end;
procedure TForm1.FloatAnimation1Finish(Sender: TObject);
begin
FAligningActiveTab := False;
end;
procedure TForm1.TabControl1Change(Sender: TObject);
begin
if not FAligningActiveTab then
begin
AlignActiveTabWithCenter;
end;
end;
遷移アニメーションが発生しないタブ切り替えの場合、OnChange
イベントで中央寄せを行います。
遷移アニメーションがある(したい)場合は、FAligningActiveTab
フラグを立て、OnChangeイベント側をスルーするようにしておいて、SetActiveTabWithTransitionAsync
のAOnFinish
引数にメソッドを渡すことで中央寄せを行います。
以上です!
こぼれ話
TTabControl
のFullSize
をFalse
にした状態でTabItem内にTPanel
を配置すると、Panel上でPANジェスチャした際にタブボタンがスクロールする、という現象が発生します(Android上でのみ。iOSは未確認です)。
対処としては、PanelプロパティにてInteractiveGesture.Pan = True
とした上で、同PanelのOnGestrue
イベント内でHandled:=True
とすると回避できました。