【Flutter】Marqueeを使ってMusicアプリのトラック名みたいな見た目を作る
はじめに
ハイサイ!沖縄出身、今年の4月から東京でフリーランスをしているkajaです。今参画している案件はFlutterで音楽アプリを開発しています。曲名が長い時は改行せずに上の電光掲示板の画像のように流れていくのをよく見ます。今回はこの文が電光掲示板のように流れていく機能(Marquee(マーキーと読みます))を実装しました。技術ブログはあまり書いたことないのですが、本タイトルのような記事は見つからなかったので初めて書いてみました。
ソースコードはこちら
対象読者
- Marqueeの使い方を知りたい方
- 音楽アプリの曲名のように流れてくる見た目をFlutterで作りたい方
Marqueeを使うときの注意点
Marqueeを表示するためのコードを先に見たい方は下のアコーディオンからどうぞ
Marqueeを表示するためのコード
// 高さを決めるため追加
SizedBox(
height: 30,
// Expandedの親Widgetにする必要がある
child: Row(
children: [
// ビルドエラー解消のため追加
Expanded(
child: Marquee(
text: 'This is Sample Marquee.',
style: TextStyle(fontSize: 20),
scrollAxis: Axis.horizontal,
crossAxisAlignment: CrossAxisAlignment.start,
// 2つ目の曲名との間隔
blankSpace: 90.0,
// 何秒後にスクロールを開始するか
startAfter: const Duration(seconds: 1),
// 何秒間一時停止するか
pauseAfterRound: const Duration(seconds: 1),
),
),
],
),
);
まず、Marqueeを表示した際に発生したエラーとその解決方法について説明します。
- 遭遇するエラー1:
Horizontal viewport was given unbounded width.
それではMarqueeを表示させるため、エラーを修正していきます。
まずMarqueeのExampleの通りにtextだけ指定してみました。
Marquee(
text: 'There once was a boy who told this story about a boy: "',
)
するとビルドエラーになります。また以下のようなエラー文がDEBUG CONSOLEに表示されます。
════════ Exception caught by rendering library ═════════════════════════════════
The following assertion was thrown during performResize():
Horizontal viewport was given unbounded width.
Flutter公式に修正方法がありました。公式ではVertical viewport...
のエラーの場合の解決方法を書かれていますが、Horizontal viewport...
のエラーでも同じ対応で解決できそうです。Expanded
でラップする必要があり、高さなどを決めたい場合はSizedBox
も使います。
Expanded
とSizedBox
を追加してみます。
+ SizedBox(
+ height: 30,
+ child: Expanded(
+ child: Marquee(
text: 'This is Sample Marquee.',
style: TextStyle(fontSize: 20),
scrollAxis: Axis.horizontal,
crossAxisAlignment: CrossAxisAlignment.start,
blankSpace: 90.0,
startAfter: const Duration(seconds: 1),
pauseAfterRound: const Duration(seconds: 1),
),
+ ),
+ );
- 遭遇するエラー2:
Incorrect use of ParentDataWidget.
Expanded
とSizedBox
を追加してビルドできるようになったのですが、またDEBUG CONSOLE
にエラーが出ます
════════ Exception caught by widgets library ═══════════════════════════════════
The following assertion was thrown while applying parent data.:
Incorrect use of ParentDataWidget.
このエラーもFlutterの公式にエラー解決方法が載ってました。
Expanded
を使った場合、該当する親Widgetが必要のようです。Expanded
の場合、Row()
,Column(),Flex()
のどれかを親Widgetに持つ必要があります。ということで私はRow()
を追加してみました。これでエラーもなくなりました。
SizedBox(
height: 30,
+ child: Row(
+ children: [
Expanded(
child: Marquee(
text: 'This is Sample Marquee.',
style: TextStyle(fontSize: 20),
scrollAxis: Axis.horizontal,
crossAxisAlignment: CrossAxisAlignment.start,
blankSpace: 90.0,
startAfter: const Duration(seconds: 1),
pauseAfterRound: const Duration(seconds: 1),
),
),
+ ],
+ ),
);
Marqueeを表示できるようになったら共通化した方が良さそうです。私はBasicMarqueeという名前でカスタムWidgetを作りました。
曲名が長い時はMarquee,短い時はTextにしたい
ここからが本題です。
音楽アプリは短い曲名の時はスクロールせず停止、長い曲名の時はスクロールするような動作になっていることが多いです。曲名が2行に改行されて表示しているのはほとんど見たことがないですね。Marqueeの使い方がわかったところで今度は実際に音楽アプリっぽく表示の切り分けをできるようにしていきましょう。
文字列を当てはめたText Widgetが決められた幅からはみ出すかどうかをtextPainter.didExceedMaxLines
を使って判定することができます。
この判定方法はMarqueeだけじゃなくて下の参考記事のように、長いテキスト等を表示する際に、最初は一部だけ表示しておいて、ボタン等を押すと全文を表示するようなUIを作る時にも使えます。
またStackOverflowにも参考になりそうな回答をしてくれている方がいたのでこちらを参考にしました。
実際にGithubのソースコードには以下のメソッドに書いています。
ここまでの実装で下のような動作になります
フローティングアクションボタンをタップすると次の曲名リストに進めるようにしています。
曲名を変えた時にMarqueeの動きがおかしい
実はまだおかしなところがあります。
それは曲名を変更した時に、スクロールが止まってくれないことです。
これは曲名の変更を検知してwidgetを再構築するときに、同じMarquee Widgetを使用していることが原因みたいです。曲名を変更してMarqueeを表示するときは、新しいMarqueeを表示するようにしてみます。これはMarqueeを表示するときに一意になるようなKeyを発行すると、新しいwidgetとして認識されます。
該当箇所はソースコードの以下のところです。
また何秒後にスクロールを開始するかを決めることができるstartAfter
プロパティに以下のように指定します。
// 1秒後にスクロールを開始する
startAfter: const Duration(seconds: 1),
修正後の動作は以下のようになります。
まとめ
今回は音楽アプリでよく見る曲名が長い時のMarquee表示をしてみました。初めて記事を書いたので拙いと思いますが、誰かのお役に立てると嬉しいです。間違ってるところやこうした方がいいなどアドバイスもらえるとありがたいです🙇♂️
Discussion