Flutter でスクロールするとヘッダー背景がいい感じになるやつ

Flutter には SliverAppBar というものがあり、これを使うとヘッダーの AppBar をいい感じに表示することができる。

Twitter iOS アプリのプロフィールページなどのように、PullToRefresh するとヘッダー画像が blur していい感じに表示される、というのも SliverAppBar で簡単に実装できる。

なのだが、ちょっと複雑なことをしようとするとうまくいかず、自力で ScrollController の offset を取得してヘッダー背景がいい感じになるような Widget を作った。やってることは難しくなくてスクロールの offset をみて画像の位置をいじったり AppBar の opacity を変化させている。(iOS アプリ開発の時によくやっていたので、モバイルアプリとしてはあるあるな実装だと思う)

これで一応動いているのだが、スクロールのたびに Widget の更新が走るため、パフォーマンスは不安である。

こんな感じで動く。

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

  @override
  HeaderScrollPageState createState() => HeaderScrollPageState();
}

class HeaderScrollPageState extends State<HeaderScrollPage> {
  double _appBarOpacity = 1.0;
  double _transform = 0.0;
  double _backgroundHeight = 200;
  final _scrollController = ScrollController();
  final _topMargin = 80;

  @override
  void initState() {
    super.initState();
    _scrollController.addListener(
      () {
        final offset = _scrollController.offset;
        setState(() {
          _transform = offset < 0 ? 0 : -offset;
          _backgroundHeight = offset < 0 ? 200 - offset : 200;
          _appBarOpacity = max(((_topMargin - -_transform)), 0) / _topMargin;
        });
      },
    );
  }

  @override
  void dispose() {
    _scrollController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      extendBodyBehindAppBar: true,
      appBar: _appBarOpacity == 0.0
          ? null
          : AppBar(
              foregroundColor: Colors.black.withOpacity(_appBarOpacity),
              backgroundColor: Colors.transparent,
              title: const Text('AppBar'),
            ),
      body: Stack(
        children: [
          Container(
            transform: Matrix4.translationValues(0.0, _transform, 0.0),
            height: _backgroundHeight,
            width: double.infinity,
            child: Image.network(
              // https://min-chi.material.jp/fm/bg_c/facility2/ をお借りしています
              'https://min-chi.material.jp/mc/materials/background-c/facility2/_facility2_1.jpg',
              fit: BoxFit.cover,
            ),
          ),
          Center(
            child: SingleChildScrollView(
              controller: _scrollController,
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.center,
                children: [
                  SizedBox(
                    height: _topMargin + 20,
                  ),
                  const SizedBox(
                    width: 200,
                    height: 200,
                    child: CircleAvatar(child: Text('Avatar')),
                  ),
                  const SizedBox(height: 40),
                  Column(
                    children:
                        List.generate(100, (index) => Text("Text $index")),
                  )
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }
}

ChatGPT に Flutter の scrollcontroller の使い方を教えて って聞いたらただ動くだけじゃなく dispose ちゃんとしろよって教えてくれた。flutter で 100この Text widget を生成して って聞いたらちゃんと出力してくれて便利。