【flutter】ScrollControllerでスクロール範囲を制限する

スクロールできる画面で、画面の半分までスクロールできるようにし、それより下には行けないようにする、というような処理を実装する方法について説明します。(需要があるかはわかりませんが)

実装自体はかなり簡単で、

  • ScrollControllerを用意し、スクロール可能なウィジェットに設定する
  • スクロールされた時に呼ばれる関数を用意し、一定のoffset(スクロール範囲の最上部からの距離を示す値のこと)を超えたらスクロール位置を限界値まで戻す処理を書く。
  • initStateで上で用意した関数をScrollControllerのイベントとして追加する

といったところでしょうか。サンプルコードを用意しました。

なんの変哲もないStatefulWidgetです。

scrollLimitOffsetという変数に300を設定しています。つまり、スクロール範囲の最上部から300pxまでをスクロール範囲として設定しています。

_scrollListener()では、スクロールイベントが発火するたびに次に移動するoffsetを取得し、それが300を超えるようなら強制的に300地点に戻す、というやり方をしています。

class Page extends StatefulWidget {
  const Page({Key? key}) : super(key: key);

  @override
  State<Page> createState() => _PageState();
}

class _PageState extends State<Page> {
  //スクロールを制限する位置
  final double scrollLimitOffset = 300;
  //スクロールコントローラー
  final ScrollController _controller = ScrollController();

  @override
  void initState() {
    super.initState();
    //スクロールコントローラーにイベントを設定
    _controller.addListener(_scrollListener);
  }

  // スクロールを検知したときに呼ばれるイベント
  void _scrollListener() {
    print("スクロールを試みたオフセット${_controller.offset}");

    if (_controller.offset > scrollLimitOffset) {
      //スクロール範囲を強制的に戻す
      _controller.jumpTo(scrollLimitOffset);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Page'),
      ),
      body: ListView.builder(
          controller: _controller,
          padding: const EdgeInsets.all(8),
          itemCount: 100,
          itemBuilder: (BuildContext context, int index) {
            return Center(child: Text(index.toString()));
          }),
    );
  }
}

以下、全コードです。DartPadなどにコピペして動かしてみてください。

import 'package:flutter/material.dart';

const Color darkBlue = Color.fromARGB(255, 18, 32, 47);

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark().copyWith(
        scaffoldBackgroundColor: darkBlue,
      ),
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Center(
          child: Page(),
        ),
      ),
    );
  }
}

class Page extends StatefulWidget {
  const Page({Key? key}) : super(key: key);

  @override
  State<Page> createState() => _PageState();
}

class _PageState extends State<Page> {
  //スクロールを制限する位置
  final double scrollLimitOffset = 300;
  //スクロールコントローラー
  final ScrollController _controller = ScrollController();

  @override
  void initState() {
    super.initState();
    //スクロールコントローラーにイベントを設定
    _controller.addListener(_scrollListener);
  }

  // スクロールを検知したときに呼ばれるイベント
  void _scrollListener() {
    print("スクロールを試みたオフセット${_controller.offset}");

    if (_controller.offset > scrollLimitOffset) {
      //スクロール範囲を強制的に戻す
      _controller.jumpTo(scrollLimitOffset);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Page'),
      ),
      body: ListView.builder(
          controller: _controller,
          padding: const EdgeInsets.all(8),
          itemCount: 100,
          itemBuilder: (BuildContext context, int index) {
            return Center(child: Text(index.toString()));
          }),
    );
  }
}

コメント

タイトルとURLをコピーしました