0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Flutter】任意の場所まで自動スクロール(後編)

Last updated at Posted at 2021-10-10

概要

Flutterで縦に長いページを作成する場合、一番上まで自動でスクロールさせたいユースケースがあると思います。
例えば、Twitter, Instagram, Facebook, Slackなど、そうそうたるアプリは、AppBarを押すと最上部に遷移する実装がなされています。

本記事では、それらのアプリが実現している【AppBarを押すと画面最上部まで自動でスクロール】の実装内容を整理してみました。
後編のみご覧いただくだけでも問題ありませんが、実現にはいくつかの要素を含むため、
本記事の内容が難解であると感じた場合、まずは前編をご覧いただくことをおすすめします。

前編に引き続き、今回も【scroll_to_index】を利用します。

スクロールについては、前後編で以下の内容で整理しています。

| 内容
---|---
前編|【同一Widget内】で、任意のスクロール場所へ遷移する方法
後編(本記事)|【表示と異なるWidget】で、任意のスクロール場所へ遷移する方法
※具体的にはAppBarを押すとページ最上部に遷移する

実装イメージ

AppBarをタップしたら、画面最上部に遷移するデモとなります。
(1回目は23まで手動でスクロール、その後AppBarタップ)
(2回目は99まで手動でスクロール、その後AppBarタップ)
output.gif

環境

$ flutter --version
Flutter 2.5.1 • channel stable • https://github.com/flutter/flutter.git
Framework • revision ffb2ecea52 (3 weeks ago) • 2021-09-17 15:26:33 -0400
Engine • revision b3af521a05
Tools • Dart 2.14.2

pubspec.yaml

dependencies:
  # ✨✨✨↓追加✨✨✨
  scroll_to_index: ^2.1.0
  hooks_riverpod: ^1.0.0-dev.10

実装

ポイントは、以下の3つです。

  1. スクロールのcontrollerの状態管理
  2. AppBar押下のコールバック取得(+スクロールイベント発火)
  3. スクロールさせたい画面表示

順に説明します。注意してほしい箇所はコメントアウトに記載しているので、その辺りも見ていただければと。
GitHubにも全文公開しているのでご参考まで

##スクロールのcontrollerの状態管理
本記事の状態管理はriverpodを採用しています。
riverpodあるあるとして、runAppのchildに【ProviderScope】をかませることを忘れないように注意してください。
※筆者はよく忘れます。自分への戒めとして明記しました。

import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:scroll_to_index/scroll_to_index.dart';

/// scroll用のproviderを宣言
final scrollControllerProvider = Provider((_) => AutoScrollController());

void main() {
  runApp(
    /// ✨✨✨ProviderScopeは、かなり忘れがちなので注意✨✨✨
    const ProviderScope(
      child: MyApp(),
    ),
  );
}

##AppBar押下のコールバック取得(+スクロールイベント発火)
デフォルトのAppBarは、Tap検出できないため、AppBarをカスタムします。

/// ✨✨✨デフォルトのAppBarは、Tap検出できないため、AppBarをカスタムする(GestureDetectorをかませた)
class ScrollAppBar extends StatelessWidget implements PreferredSizeWidget {
  final VoidCallback onTap;
  final AppBar appBar;

  const ScrollAppBar({Key? key, required this.onTap, required this.appBar})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    return GestureDetector(onTap: onTap, child: appBar);
  }

  @override
  Size get preferredSize => const Size.fromHeight(kToolbarHeight);
}

本クラスの呼び出し方法は、以下をご覧ください。
また、AppBarが押下(onTapがコール)されたら、先ほど宣言したcontrollerに対して、最上部へスクロールさせる実装します。
※ref.watchは必ずbuildメソッド内で実行してください。これも忘れがち。

class MyApp extends HookConsumerWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    /// riverpodからcontrollerを参照
    final controller = ref.watch(scrollControllerProvider);

    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        /// カスタムAppBarの呼び出し
        appBar: ScrollAppBar(
          appBar: AppBar(
            title: const Text('Sample of Scroll to Jump'),
          ),
          onTap: () {
            /// ボタンを押したら先頭(=ListViewの0番目)にジャンプできる。
            /// AutoScrollPosition.beginがListのindexの頭に表示される。他に、middleとendが存在する
            controller.scrollToIndex(
              0,
              preferPosition: AutoScrollPosition.begin,
            );

            /// なお、【scroll_to_index】は、FlutterデフォルトAPIのScrollControllerを継承しているため、
            /// 前編にてジャンプ使用不可と記載しましたが、先頭に遷移させるだけであれば、実はjumpToが簡単に使えます。
            /// TwitterやInstagramは、アニメーション方式を採用している、且つユーザ体験もanimationの方が好ましいため、今回はコメントアウトしますが、お知りおきを。
            // controller.jumpTo(0);
          },
        ),

        /// 呼び出し元のWidget
        body: const ScrollWidget(),
      ),
    );
  }
}

##スクロールさせたい画面表示
こちらは、ほぼ前編と同様の内容になっています。
変更点としては、

  • AppBarでスクロール動作させるようになったので、ListView内のボタンを削除
  • スクロール用のcontrollerをriverpodから参照

となります。
※ref.watchは必ずbuildメソッド内で実行してください。これも忘れがち。

class ScrollWidget extends HookConsumerWidget {
  const ScrollWidget({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    /// riverpodからcontrollerを参照
    final controller = ref.watch(scrollControllerProvider);

    return ListView.builder(
      controller: controller,

      /// とりあえず100個だけ表示するように実装
      itemCount: 100,
      itemBuilder: (context, index) {
        /// AutoScrollTagをかませる
        return AutoScrollTag(
          key: ValueKey(index),
          controller: controller,
          index: index,
          child: Column(
            children: [
              SizedBox(
                width: double.infinity,
                height: 80,

                /// 行頭に遷移されることがわかりやすいようにCardウィジェットを採用しています。適宜変更してください。
                child: Card(
                  child: Text(
                    '$index',
                    style: const TextStyle(fontSize: 24),
                  ),
                ),
              ),
            ],
          ),
        );
      },
    );
  }
}

#おまけ
お気づきの方もいらっしゃるかもしれませんが、画面最上部に持ってくるだけなら、scroll_to_indexのプラグインは正直不要です。
ただし、任意の場所まで遷移させることを考えた時に、拡張性があるという点でscroll_to_index使えば良いという点で採用しています。
※前後編の繋がりも考えて、採用したというのも大いにあったりしますが、、、

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?