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

Flutter如何实现带按钮控制的不可滚动滑动切换列表

Flutter 实现按钮控制的无手动滚动分组滑动列表

Flutter 没有提供完全匹配该交互的开箱即用专属widget,但基于官方滚动组件做极少量定制就能实现完全符合要求的效果,核心是禁用列表手动滚动手势、通过控制器触发编程式平滑滚动。

实现方案选型

如果你的每组widget刚好占满整个视口高度(和提供的示例效果一致),优先选PageView实现,代码量最少,默认的滑动动效就是当前内容滑出、下一组内容滑入的转场,完全不需要额外写动画逻辑。
如果你的分组高度小于视口高度、每次滑动固定距离展示部分后续内容,选ListView搭配ScrollController实现即可。


整视口分组实现代码(匹配示例效果)

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

  @override
  State<ScrollControlledList> createState() => _ScrollControlledListState();
}

class _ScrollControlledListState extends State<ScrollControlledList> {
  // 页面控制器,整组切换时viewportFraction设为1即可
  final PageController _pageController = PageController(viewportFraction: 1);
  int _currentGroupIndex = 0;
  // 替换成你自己的分组widget列表
  final List<Widget> _contentGroups = List.generate(
    6,
    (index) => Container(
      color: Colors.primaries[index % Colors.primaries.length],
      alignment: Alignment.center,
      child: Text('第${index + 1}组内容', style: const TextStyle(fontSize: 24)),
    ),
  );

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        alignment: Alignment.centerRight,
        children: [
          // 核心滑动区域
          PageView.builder(
            scrollDirection: Axis.vertical,
            // 关键配置:禁用手动滚动,用户无法通过拖拽滑动列表
            physics: const NeverScrollableScrollPhysics(),
            controller: _pageController,
            itemCount: _contentGroups.length,
            onPageChanged: (index) => _currentGroupIndex = index,
            itemBuilder: (context, index) => _contentGroups[index],
          ),
          // 右侧控制按钮组
          Padding(
            padding: const EdgeInsets.only(right: 16),
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                // 上一组按钮
                IconButton(
                  style: IconButton.styleFrom(
                    backgroundColor: Colors.white.withOpacity(0.8),
                  ),
                  onPressed: () {
                    if (_currentGroupIndex > 0) {
                      _pageController.previousPage(
                        duration: const Duration(milliseconds: 300),
                        curve: Curves.easeInOut,
                      );
                    }
                  },
                  icon: const Icon(Icons.keyboard_arrow_up, size: 32),
                ),
                const SizedBox(height: 8),
                // 下一组按钮(对应示例图右侧箭头)
                IconButton(
                  style: IconButton.styleFrom(
                    backgroundColor: Colors.white.withOpacity(0.8),
                  ),
                  onPressed: () {
                    if (_currentGroupIndex < _contentGroups.length - 1) {
                      _pageController.nextPage(
                        duration: const Duration(milliseconds: 300),
                        curve: Curves.easeInOut,
                      );
                    }
                  },
                  icon: const Icon(Icons.keyboard_arrow_down, size: 32),
                ),
              ],
            ),
          )
        ],
      ),
    );
  }
}

固定高度分组(非整视口)实现要点

如果不需要整页切换,只需要每次点击按钮滑动固定高度,替换核心组件为ListView即可:

  • 初始化ScrollController,给每个列表项设置统一高度
  • 同样给ListView配置physics: const NeverScrollableScrollPhysics()禁用手动滚动
  • 按钮点击时调用控制器的animateTo方法,传入目标偏移量(当前索引*单组高度),配置和上面一致的动画时长和曲线即可

注意:不要使用TabBarView实现该效果,它的生命周期和逻辑强绑定顶部tab标签,不适合这种纯按钮控制的独立滑动场景。如果需要循环滑动效果,只需要在索引到最后一组时,将偏移量重置为0并触发滚动动画即可。

目标效果示例

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 23:30:52