LoginSignup
4
1

More than 3 years have passed since last update.

[小ネタ]タブコントロールのタブを中央寄せする

Last updated at Posted at 2020-01-15

はじめに

スマートフォン上でもTTabControlFullSizeFalseにして使いたいなあ、という場面があったのですが、ジェスチャでページ送りするとタブが隠れて辛い!となったので、センタリングするようにしてみました。
tabmoveanimetion.gif(こんな感じ)

Delphi10.3 Community Editionにて確認しています。
記事内のコードの命名の微妙さについては・・ご指摘頂ければと思います。

やり方

移動量の計算

TTabControlのボタン部分はスクロール可能な領域になっており、スクロール位置はTabContentPositionで取得・変更可能です。やりたいのは中央寄せなので、目標のタブボタンの中心と、タブコントロールの中心の距離を計算します。(下図参照)
fig1.png

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;

TabItemMarginを設定して使用している場合は、上記の計算では考慮されていないので、targetTabCenter に足すようコードを修正してください。

アニメーションさせる 

実際にアクティブタブを中央寄せするメソッドは以下です。TabContentPositionの値の範囲は(Width-TabContentSize.Width) ~ 0なので、一応それも考慮します(内部で超えないようにしてくれているは思う)。
FloatAnimation1は、TabControl1 の子として配置し、PropatyNameTabContentPositionStartFromCurrentTrueに設定しておきます。

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イベント側をスルーするようにしておいて、SetActiveTabWithTransitionAsyncAOnFinish引数にメソッドを渡すことで中央寄せを行います。

以上です!

こぼれ話

TTabControlFullSizeFalseにした状態でTabItem内にTPanelを配置すると、Panel上でPANジェスチャした際にタブボタンがスクロールする、という現象が発生します(Android上でのみ。iOSは未確認です)。

対処としては、PanelプロパティにてInteractiveGesture.Pan = Trueとした上で、同PanelのOnGestrueイベント内でHandled:=Trueとすると回避できました。

4
1
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
4
1