［小ネタ］色々使えるVertScrollBox

#はじめに
前回に続いてFireMonkey初心者による初心者向け記事となります。

Delphiでスマートフォンアプリが作れる！と意気込んでみたものの、いざ作り出すとスマートフォンっぽいUIってどんなだっけ？となりまして。まずは既存のものを真似をするのがいいかなと思い至りました。
そんなわけで（？）、TVertScrollBoxを利用した作例を二つ、紹介したいと思います。

##例１：Twitterの上部エリアを再現する
Twitterを眺めているとチラチラ視界に入るアイツ
![01s.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33684/37565c4d-2830-abfe-2f77-6b5e95d0a32d.png)


###やり方
![02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33684/b1242df6-6ebd-cc73-46a7-afdb7a4686e2.png)
図のような親子構造でコントロールを配置し、Formの<code>OnCreate</code>イベントで<code>ListView</code>コントロールの高さを親コントロールの高さ（今回はForm1.ClientHeight）に設定するだけです。

![04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33684/07d1f7be-318a-8fb5-4dee-e195e718fe65.png)
（ListViewの仮データとしてPrototypeBindSourceを使用しています。詳細は[こちら](http://docwiki.embarcadero.com/RADStudio/Rio/ja/%E3%83%A2%E3%83%90%E3%82%A4%E3%83%AB_%E3%83%81%E3%83%A5%E3%83%BC%E3%83%88%E3%83%AA%E3%82%A2%E3%83%AB%EF%BC%9A%E3%83%A2%E3%83%90%E3%82%A4%E3%83%AB_%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%A7_LiveBinding_%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%97%E3%81%A6%E3%83%AA%E3%82%B9%E3%83%88_%E3%83%9C%E3%83%83%E3%82%AF%E3%82%B9%E3%81%AB%E5%80%A4%E3%82%92%E8%A8%AD%E5%AE%9A%E3%81%99%E3%82%8B%EF%BC%88iOS_%E3%81%8A%E3%82%88%E3%81%B3_Android%EF%BC%89 "モバイル チュートリアル：モバイル アプリケーションで LiveBinding を使用してリスト ボックスに値を設定する")）

※親コントロールに<code>Margin</code>、<code>Padding</code>の設定をしている場合、その考慮も必要です。
※TabControlが親の場合、高さの計算は<code>TabControl.Height-TabControl.TabHeight</code>になります。

###挙動を似せる
そのままでも結構それっぽいのですが、以下の機能が再現できていません。
・上部パネルが半分以上表示されていると、途中でスワイプを止めても全て表示される
・上部パネルが半分以上隠れていると、途中でスワイプを止めても全て隠れる

<code>TVertScrollBox</code>の<code>OnViewportPositionChange</code>イベントに以下のようなコードを書いたところ、近い動きになりました。

```pascal

uses System.Math;

procedure TForm1.VertScrollBox1ViewportPositionChange(Sender: TObject;
  const OldViewportPosition, NewViewportPosition: TPointF;
  const ContentSizeChanged: Boolean);
begin

  if ABS(VertScrollBox1.AniCalculations.CurrentVelocity.Y) < 100 then //スクロール速度が一定以下だったら
  begin
    if VertScrollBox1.ViewportPosition.Y < Panel1.Height * 0.5 then
    begin
      VertScrollBox1.AniCalculations.MouseWheel(0,-5); //マウスホイール操作を疑似発生する（上スクロール）
    end
    else if VertScrollBox1.ViewportPosition.Y >= Panel1.Height * 0.5 then 
    begin
      VertScrollBox1.AniCalculations.MouseWheel(0,6);  //下スクロール
    end;
  end;
end;
```

※マジックナンバー箇所は目分量で調整しました
※どの動作環境でも同じ速さでスクロールするかわかりません

わかり辛いのですが、こんな動きになります。（Android実機動作例）
![twitterLike.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33684/f49f7dda-3aa9-4f78-aab2-7ff19e74fc10.gif)




##例２：入力欄をタップするとスクロールする（パンする）UI
Delphiで作成したスマートフォンアプリは、ソフトウェアキーボード表示の際に自動でパンしません。入力欄が隠れないようなUIデザインをまずは考える必要があります。

どうしても画面の下側にも入力欄を設けたい場合、入力欄の土台となるコントロールの高さを余分にとるか、画面下部にソフトウェアキーボードの高さ分のパネルを配置し、TVertScrollBoxでスクロールできるようにする、などの方法があります。
今回は後者をやってみます。
（TVertScrollBoxを使わずに、土台の位置を直接ずらすことも可能です）

![41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33684/f711df0e-b52d-27dc-4854-1aa2a76326e9.png)

Panel_VK（画面下部パネル）のHeightについては、スクリーン高さの4割ぐらいを設定しておきます。AlignはどちらもTopです。

ソフトウェアキーボードの高さは<code>Form.OnVirtualKeyboardShown</code>イベントにて、引数のBoundsから取得できますが、簡略化のため、キーボードの高さがPanel_VKの高さを越えないものとして作成します。

起動時にパネルの高さを設定し、入力コントロールのOnEnterイベントにてViewportPositionの値を変更します。

```pascal

procedure TForm1.FormCreate(Sender: TObject);
begin
  Panel1.Height := ClientHeight;
  Panel_VK.Height := ClientHeight * 0.4;
end;

procedure TForm1.EditEnter(Sender: TObject);
var
  bottom:single;
  overlapped:single;
begin
  bottom := TControl(sender).Position.Y + TControl(sender).Height;

  overlapped := Panel1.Height - Panel_VK.Height - bottom;
  if overlapped < 0 then
    VertScrollBox1.ViewportPosition := TPointF.Create(0,-overlapped);
end;
```

（動作例）
![VirtualKeyScroll1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33684/018ab9bd-09e8-1d18-7d6c-574f3afb920c.gif)



###なめらかスクロール
せっかくなのでアニメーションさせたい！　しかし<code>ViewportPosition</code>は<code>TPointF</code>型なので<code>FloatAnimation</code>が適用できません。困った・・。
と思っていたら作例がありました。 → [TPointAnimation](https://stackoverflow.com/questions/37116120/delphi-fmx-tanimator-how-to-animate-a-tpointf)

このコードを拝借して、コンポーネント化して配置するとこのようになります。（参考：[コンポーネントの新規作成](http://docwiki.embarcadero.com/RADStudio/Rio/ja/%EF%BC%BB%E3%82%B3%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88%E3%81%AE%E6%96%B0%E8%A6%8F%E4%BD%9C%E6%88%90%EF%BC%BD)）

![42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33684/d846dd5d-0b67-00da-e8dc-d82f36d182ec.png)（VertScrollBox1の子になるように配置してください）

EditEnterイベント内のコードを一部変更します。

```pascal

  if overlapped < 0 then
    VertScrollBox1.ViewportPosition := TPointF.Create(0,-overlapped);
```

↓

```pascal

  if overlapped < 0 then
  begin
    PointAnimation1.StopValue := TPointF.Create(0,-overlapped);
    PointAnimation1.Start;
  end;
```

![VirtualKeyScroll2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33684/c740c248-2dec-292e-9f27-277ff36344f8.gif)

（Android実機動作）
![VirtualKeyScroll3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33684/ea00ca7d-e21b-1f68-12fb-b6027cfc9caa.gif)



###OnVirtualKeyboardHiddenは使わないの？
当初はShown時にPanel_VKの高さを設定して、Hidden時にゼロに戻したらいいんや！と思ったのですが、[環境によってはOnVirtualKeyboardHiddenイベントが発生しない](https://support.embarcadero.com/jp/article/44666)とのことです（BlackberryKey2もだめでした！）。気をつけましょう！

#おわりに
取り留めのない記事になってしまいましたが、（Delphi未経験の方には）こんなこともできるんだな～　程度に見てもらえれば幸いです。