You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter实现基于分类的滚动显示AppBar功能

Flutter 滚动时动态切换顶部固定Toolbar的实现方案

要实现滚动ListView时根据分类切换顶部固定的Toolbar,核心是利用Flutter的Sliver组件体系,通过CustomScrollView结合自定义的SliverPersistentHeaderDelegate实现分类头部的粘性效果,同时监听滚动位置动态更新顶部AppBar内容。以下是具体实现步骤:

1. 定义分类数据结构

先创建包含分类标题和对应内容的数据模型:

class CategorySection {
  final String title;
  final List<String> items;

  CategorySection(this.title, this.items);
}

2. 自定义粘性分类头部Delegate

实现一个SliverPersistentHeaderDelegate,用于渲染每个分类的头部并控制粘性行为:

class CategoryHeaderDelegate extends SliverPersistentHeaderDelegate {
  final String title;
  final double minHeight;
  final double maxHeight;

  CategoryHeaderDelegate({
    required this.title,
    this.minHeight = 50,
    this.maxHeight = 50,
  });

  @override
  Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
    return Container(
      color: Colors.grey[100],
      alignment: Alignment.centerLeft,
      padding: const EdgeInsets.symmetric(horizontal: 16),
      child: Text(
        title,
        style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
      ),
    );
  }

  @override
  double get maxExtent => maxHeight;

  @override
  double get minExtent => minHeight;

  @override
  bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
    return oldDelegate != this;
  }
}

3. 完整实现代码

通过ScrollController监听滚动位置,动态更新AppBar标题,结合CustomScrollView组织布局:

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Dynamic Toolbar Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const DynamicToolbarPage(),
    );
  }
}

class DynamicToolbarPage extends StatefulWidget {
  const DynamicToolbarPage({super.key});

  @override
  State<DynamicToolbarPage> createState() => _DynamicToolbarPageState();
}

class _DynamicToolbarPageState extends State<DynamicToolbarPage> {
  final ScrollController _scrollController = ScrollController();
  final List<CategorySection> _sections = [
    CategorySection('分类A', List.generate(20, (index) => 'A类内容$index')),
    CategorySection('分类B', List.generate(20, (index) => 'B类内容$index')),
    CategorySection('分类C', List.generate(20, (index) => 'C类内容$index')),
    CategorySection('分类D', List.generate(20, (index) => 'D类内容$index')),
  ];
  String _currentToolbarTitle = '分类A';

  @override
  void initState() {
    super.initState();
    _scrollController.addListener(_onScroll);
  }

  void _onScroll() {
    double currentOffset = _scrollController.offset;
    double accumulatedOffset = 0;

    for (var section in _sections) {
      // 计算当前分类总高度:头部高度 + 所有item高度(假设item高度固定为50)
      double sectionHeight = 50 + section.items.length * 50;
      if (currentOffset < accumulatedOffset + sectionHeight) {
        if (_currentToolbarTitle != section.title) {
          setState(() {
            _currentToolbarTitle = section.title;
          });
        }
        break;
      }
      accumulatedOffset += sectionHeight;
    }
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(_currentToolbarTitle),
        centerTitle: true,
      ),
      body: CustomScrollView(
        controller: _scrollController,
        slivers: [
          for (var section in _sections)
            ...[
              SliverPersistentHeader(
                pinned: true, // 开启头部固定效果
                delegate: CategoryHeaderDelegate(title: section.title),
              ),
              SliverList(
                delegate: SliverChildBuilderDelegate(
                  (context, index) {
                    return ListTile(
                      title: Text(section.items[index]),
                      leading: const Icon(Icons.list),
                    );
                  },
                  childCount: section.items.length,
                ),
              ),
            ],
        ],
      ),
    );
  }
}

关键细节说明

  • SliverPersistentHeader的pinned: true属性确保分类头部滚动到顶部时固定,配合滚动监听同步更新AppBar标题。
  • 示例中假设列表项高度固定为50,若你的item高度不固定,可使用GlobalKey获取实际渲染高度,或通过LayoutBuilder动态计算。
  • 若需要更流畅的标题切换效果,可给AppBar标题添加AnimatedSwitcher实现过渡动画。

内容的提问来源于stack exchange,提问作者newcomer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 08:40:46