【Flutter】ShellRoute/StatefulShellRouteを使用中にiOSのステータスバータップを検出する

2024/06/02に公開

1. はじめに

iOSにおいて、ステータスバーをタップしたときにスクロールをトップへ戻す動きがあると思います。iOSユーザは、頻繁にステータスバーをタップしてScroll To Topさせることを行うと思いますので、UXとしても重要な点なのかなと思っています。

例えば単純な以下のコードは、実装上特に何も考慮せずともiOSのステータスバーをタップするとスクロールがトップへ戻る挙動になります。

https://github.com/motucraft/tap_status_bar/blob/main/lib/ok_without_go_router.dart

これはScaffoldがPrimaryScrollControllerを使用して機能させていると考えれば良さそうです。

Inheriting this ScrollController can provide default behavior for scroll views in a subtree. For example, the Scaffold uses this mechanism to implement the scroll-to-top gesture on iOS.

では、以下のようにgo_routerShellRouteStatefulShellRouteを利用した場合はどうでしょうか。

  • ShellRouteを使用したサンプル

https://github.com/motucraft/tap_status_bar/blob/main/lib/ng_with_shell_route.dart

  • StatefulShellRouteを使用したサンプル

https://github.com/motucraft/tap_status_bar/blob/main/lib/ng_with_stateful_shell_route.dart

上記コードのようにShellRoute/StatefulShellRouteをすると、ステータスバーをタップしてもスクロールが動きません。
そこで、issueを起票しました。ステータスバーのタップを検出する方法があるのでしょうか?

https://github.com/flutter/flutter/issues/149484

(おそらく既存のissueも存在しそうで重複だと言われてクローズされるかもしれませんが)

2. 正しいのかどうか自信の無いソリューション

PrimaryScrollControllerは、以下のようにInheritedWidgetであり、
Creates a widget that associates a [ScrollController] with a subtree.ということのようです。

class PrimaryScrollController extends InheritedWidget {
  /// Creates a widget that associates a [ScrollController] with a subtree.
  const PrimaryScrollController({

それならば、ScrollControllerのattachメソッドを使ってなんとかできないものかと模索しました。

attachメソッドの引数にはScrollPositionが必要です。
ScrollPositionWithSingleContextを継承したダミーのScrollPositionを渡しておけば、ステータスバータップ時にanimateToが呼び出されるのではないのかと。
animateToが呼び出されてさえくれれば、あとはそこをコールバックで処理してなんとかなりそうだ、と考えて模索したのが下記のコードです。

  • 作成したTapStatusBarNotifierクラス

https://github.com/motucraft/tap_status_bar/blob/main/lib/tap_status_bar_notifier/tap_status_bar_notifier.dart

以下は、TapStatusBarNotifierを使用したコードです。onTapStatusBarコールバック内で自前でスクロールトップさせています。

  • ShellRoute

https://github.com/motucraft/tap_status_bar/blob/main/lib/tap_status_bar_notifier/ok_shell_route.dart

  • StatefulShellRoute

https://github.com/motucraft/tap_status_bar/blob/main/lib/tap_status_bar_notifier/ok_stateful_shell_route.dart

matchedLocationを確認して、現在表示中の画面であればスクロールをトップに戻しています。この確認を忘れてしまうと、StatefulShellRouteの場合は他の画面も一緒にスクロールトップされてしまうことになります。(逆に言えば、そんなこともできるのか!)

onTapStatusBar: () {
  if (GoRouter.of(context).routerDelegate.currentConfiguration.last.matchedLocation == '/home') {
    _controller.animateTo(0, duration: const Duration(milliseconds: 300), curve: Curves.linear);
  }
},

以下は、StatefulShellRouteの挙動です。Like画面のステータスバータップにてスクロールトップしても、Home画面には影響を与えていないことを確認できます。

3. おわりに

一応やりたいことはできたのですが、これが正しい対応なのか最善なのか自信が持てません。issueへのコメントを待ちたいと思います。どなたかよろしくお願い致します🙇‍♂

https://github.com/flutter/flutter/issues/149484

GitHubで編集を提案

Discussion