🎉

【Flutter】Marqueeを使ってMusicアプリのトラック名みたいな見た目を作る

2023/06/05に公開

はじめに

電光掲示板

ハイサイ!沖縄出身、今年の4月から東京でフリーランスをしているkajaです。今参画している案件はFlutterで音楽アプリを開発しています。曲名が長い時は改行せずに上の電光掲示板の画像のように流れていくのをよく見ます。今回はこの文が電光掲示板のように流れていく機能(Marquee(マーキーと読みます))を実装しました。技術ブログはあまり書いたことないのですが、本タイトルのような記事は見つからなかったので初めて書いてみました。

ソースコードはこちら

対象読者

  • Marqueeの使い方を知りたい方
  • 音楽アプリの曲名のように流れてくる見た目をFlutterで作りたい方

https://pub.dev/packages/marquee

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も使います。

https://docs.flutter.dev/testing/common-errors#vertical-viewport-was-given-unbounded-height

ExpandedSizedBoxを追加してみます。

+  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.

ExpandedSizedBoxを追加してビルドできるようになったのですが、またDEBUG CONSOLEにエラーが出ます

════════ Exception caught by widgets library ═══════════════════════════════════
The following assertion was thrown while applying parent data.:
Incorrect use of ParentDataWidget.

このエラーもFlutterの公式にエラー解決方法が載ってました。

https://docs.flutter.dev/testing/common-errors#incorrect-use-of-parentdata-widget

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を作りました。

https://github.com/king-kazu39/SampleMusicTrackWidget/blob/main/lib/main.dart#L138-L174

曲名が長い時はMarquee,短い時はTextにしたい

ここからが本題です。
音楽アプリは短い曲名の時はスクロールせず停止、長い曲名の時はスクロールするような動作になっていることが多いです。曲名が2行に改行されて表示しているのはほとんど見たことがないですね。Marqueeの使い方がわかったところで今度は実際に音楽アプリっぽく表示の切り分けをできるようにしていきましょう。

文字列を当てはめたText Widgetが決められた幅からはみ出すかどうかをtextPainter.didExceedMaxLinesを使って判定することができます。

この判定方法はMarqueeだけじゃなくて下の参考記事のように、長いテキスト等を表示する際に、最初は一部だけ表示しておいて、ボタン等を押すと全文を表示するようなUIを作る時にも使えます。

https://zenn.dev/hayabusabusa/articles/7bf73f007584aa4e0ee8

またStackOverflowにも参考になりそうな回答をしてくれている方がいたのでこちらを参考にしました。

https://stackoverflow.com/questions/70953210/how-can-i-use-marquee-package-conditionally-in-futter

実際にGithubのソースコードには以下のメソッドに書いています。

https://github.com/king-kazu39/SampleMusicTrackWidget/blob/main/lib/main.dart#L102-L119

ここまでの実装で下のような動作になります
フローティングアクションボタンをタップすると次の曲名リストに進めるようにしています。

曲名を変えた時にMarqueeの動きがおかしい

実はまだおかしなところがあります。
それは曲名を変更した時に、スクロールが止まってくれないことです。

これは曲名の変更を検知してwidgetを再構築するときに、同じMarquee Widgetを使用していることが原因みたいです。曲名を変更してMarqueeを表示するときは、新しいMarqueeを表示するようにしてみます。これはMarqueeを表示するときに一意になるようなKeyを発行すると、新しいwidgetとして認識されます。

該当箇所はソースコードの以下のところです。

https://github.com/king-kazu39/SampleMusicTrackWidget/blob/main/lib/main.dart#L159-L160

また何秒後にスクロールを開始するかを決めることができるstartAfterプロパティに以下のように指定します。

// 1秒後にスクロールを開始する
startAfter: const Duration(seconds: 1),

修正後の動作は以下のようになります。

まとめ

今回は音楽アプリでよく見る曲名が長い時のMarquee表示をしてみました。初めて記事を書いたので拙いと思いますが、誰かのお役に立てると嬉しいです。間違ってるところやこうした方がいいなどアドバイスもらえるとありがたいです🙇‍♂️

Discussion