🙋‍♂️

Flutter小ネタ集① 〜「それ、できますよ?」って言いたくなる瞬間〜

に公開

こんにちは@ヒラメです。
エンジニアをしていると、「それ、できますよ?」と言いたくなるような小ネタに出会うことがあります。
この記事では、Flutterを使った開発の中で出会った、そんな小さな実装アイデアや工夫を第一弾としてまとめました。ちょっとした参考やヒントになればうれしいです。

https://shadomas.com/

🎢 ListViewで横スクロールをエッジまで自然に見せる工夫

シャドーイングのレッスン情報をカード形式にして、ListViewで横スクロール表示しています。
ポイントは、ファーストビューやスクロールの終端ではパディングを入れ、スクロール中はカードを画面エッジまで表示することです。

ファーストビュー(初期表示)
最初のカードが画面端に張り付かないよう、左側にパディングを設けています。右側はエッジまでしっかり表示されるため、自然なバランスで「スクロールできる感」が伝わります。

スクロール終端
最後のカードが画面端に張り付かないよう、右側にパディングを追加しています。これにより「ここでスクロールが終わり」という余白が自然に生まれます。

ListView(
  scrollDirection: Axis.horizontal, // 横スクロール
  padding: EdgeInsets.only(right: 16), // 終端までスクロールしたときのパディング
  shrinkWrap: true,
  children: lessonModel.map((lesson) {
    return Padding(
      padding: EdgeInsets.only(left: 16), // ファーストビューの左側パディング
      child: SizedBox(
        width: 234,
        child: BoxMasterLesson(
          location: DisplayLocation.master,
          title: lesson.title,
          imagePath: lesson.imagePath,
          description: lesson.description,
          isLock: lesson.isLock,
          wpm: lesson.wpm,
          progress: lessonProgress[lesson.docId]?.progress ?? 0,
          max: lessonProgress[lesson.docId]?.max ?? 0,
          onPressed: () => onPressed.call(lesson.docId),
        ),
      ),
    );
  }).toList(),
)

ファーストビューの左側パディング
各カードを Padding でラップして左側に余白を入れています。
これにより ListView 自体の横幅はそのままに、最初のカードが画面端に張り付かず自然に表示されます。また、この Padding はカード同士の間隔にも作用しています。

終端までスクロールしたときのパディング
こちらは ListView に直接パディングを指定しています。
リスト全体のサイズを変えることなく、最後のカードの右側だけに余白を確保できます。

🎨 リーセントでの帯のカラー for Android

古くからAndroidとのお付き合いのある方ならわかるこのリーセント。Flutterプロジェクトのデフォルトのままだと紫色のままですが、カラーを指定し変更することができます。

return MaterialApp.router(
  // テーマの設定
  theme: ThemeData(
    primaryColor: Colors.green,
  ),

MaterialApp.routerthemeprimaryColorを指定すると反映されます。
ちょっとした調整ですが、プロジェクトの統一感を出すのに役立ちます。

💬 ダイアログの表示で didPush をゲットする

シャドマスは go_router を使ってルーティングの管理をしています。Firebase Analyticsと連携して遷移情報を分析しているアプリも多いかと思います。ShellRouteobserversNavigatorObserverをセットしておけば大半の画面であれば自ずとdidPushdidPopが検知できますが、ダイアログの表示は少し工夫が必要です。

Future<int?> show() {
  return showDialog<int>(
    context: context,
    barrierDismissible: false,
    useRootNavigator: useRootNavigator,
    routeSettings: RouteSettings(name: name), // ダイアログの名前を設定
    builder: (BuildContext context) => HookBuilder(
      builder: (context) {
        final media = useMediaQuery();

        return AlertDialog(
          insetPadding: EdgeInsets.symmetric(horizontal: 16),
          contentPadding: EdgeInsets.zero,
          shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
          content: SizedBox(
            width: media.size.width,
            child: child,
          ),
        );
      },
    ),
  );
}

showDialog は内部的に Navigator.push を使って DialogRoute を追加しています。そのときに routeSettings を渡すと、その情報が DialogRoute.settings に反映されます。NavigatorObserverNavigator に対して新しいルートが push されたときに必ず通知を受ける仕組みなので、ダイアログが表示された瞬間に didPush が呼ばれ、そこで RouteSettings.name が確認できる、という流れになっています。

📌 まとめ

今回紹介したのは、どれも「大きな機能追加」ではなく、UI を自然に見せたり、アナリティクスの計測精度を少し上げたりといった ちょっとした工夫 でした。こうした小さな改善の積み重ねが、アプリ全体の完成度やユーザー体験を大きく変えてくれます。

今後も Flutter 開発の中で見つけた「それ、できますよ?」なアイデアを紹介していくので、ぜひ参考にしてみてください。

🚀 アプリのダウンロードはこちら

🍎 App Store(iOS):
https://itunes.apple.com/jp/app/id6745143950?mt=8

🤖 Google Play(Android):
https://play.google.com/store/apps/details?id=com.eleven.shadomas

いくつかのシャドーイングは無料で試せるので、ぜひダウンロードして使ってみてください!

@ヒラメ

Discussion