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

Flutter单页Web应用:如何通过导航栏实现页面动态滚动?

Flutter单页网站导航栏滚动到对应区块实现方案

核心思路

给每个目标区块绑定GlobalKey,导航栏点击时通过回调通知主页执行滚动逻辑,利用Scrollable.ensureVisible实现平滑滚动到指定位置。

步骤1:修改HomeScreen,添加定位Key和滚动方法

class HomeScreen extends StatelessWidget {
  // 为每个区块定义唯一的GlobalKey
  final GlobalKey _topKey = GlobalKey();
  final GlobalKey _aboutKey = GlobalKey();
  final GlobalKey _servicesKey = GlobalKey();
  final GlobalKey _testimonialsKey = GlobalKey();
  final GlobalKey _contactKey = GlobalKey();

  // 滚动到指定区块的方法
  void _scrollToSection(GlobalKey key) {
    if (key.currentContext != null) {
      Scrollable.ensureVisible(
        key.currentContext!,
        duration: const Duration(milliseconds: 500), // 平滑滚动时长
        curve: Curves.easeInOut, // 动画曲线
        // 若导航栏固定在顶部,添加padding避免区块被遮挡
        padding: const EdgeInsets.only(top: 100),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SingleChildScrollView(
        child: Column(
          children: [
            TopSection(key: _topKey), // 绑定key到对应区块
            SizedBox(height: kDefaultPadding * 2),
            AboutSection(key: _aboutKey),
            ServiceSection(key: _servicesKey),
            TestimonialSection(key: _testimonialsKey),
            SizedBox(height: kDefaultPadding),
            ContactSection(key: _contactKey),
            const SizedBox(height: 50)
          ],
        ),
      ),
      // 将滚动方法通过回调传递给导航栏
      bottomNavigationBar: Menu(
        onMenuItemTap: (index) {
          switch(index) {
            case 0:
              _scrollToSection(_topKey);
              break;
            case 1:
              _scrollToSection(_aboutKey);
              break;
            case 2:
              _scrollToSection(_servicesKey);
              break;
            case 3:
              _scrollToSection(_testimonialsKey);
              break;
            case 4:
              _scrollToSection(_contactKey);
              break;
          }
        },
      ),
    );
  }
}

步骤2:修改导航栏组件,添加回调触发滚动

// 先修改Menu组件的Stateful定义,添加回调参数
class Menu extends StatefulWidget {
  final Function(int index) onMenuItemTap;

  const Menu({super.key, required this.onMenuItemTap});

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

class _MenuState extends State<Menu> {
  int selectedIndex = 0;
  int hoverIndex = -1;
  List<String> menuItems = [
    "Home",
    "About",
    "Services",
    "Testimonials",
    "Contact"
  ];

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.symmetric(horizontal: kDefaultPadding * 2.5),
      constraints: const BoxConstraints(maxWidth: 1110),
      height: 100,
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: const BorderRadius.only(
          topLeft: Radius.circular(10),
          topRight: Radius.circular(10),
        ),
        boxShadow: [kDefaultShadow],
      ),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: List.generate(
          menuItems.length,
          (index) => buildMenuItem(index),
        ),
      ),
    );
  }

  Widget buildMenuItem(int index) => InkWell(
        onTap: () {
          setState(() {
            selectedIndex = index;
          });
          // 点击时触发回调,通知主页滚动到对应区块
          widget.onMenuItemTap(index);
        },
        // 保留原有的hover交互逻辑
        child: MouseRegion(
          onEnter: (_) => setState(() => hoverIndex = index),
          onExit: (_) => setState(() => hoverIndex = -1),
          child: Container(
            padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
            decoration: BoxDecoration(
              color: (selectedIndex == index || hoverIndex == index)
                  ? Colors.grey.shade100
                  : Colors.transparent,
              borderRadius: BorderRadius.circular(8),
            ),
            child: Text(
              menuItems[index],
              style: TextStyle(
                fontWeight: selectedIndex == index ? FontWeight.bold : FontWeight.normal,
              ),
            ),
          ),
        ),
      );
}

注意事项

  1. 确保每个Section组件的根Widget可以被GlobalKey绑定(直接给Section组件传key即可,StatelessWidget支持接收key参数)
  2. 如果导航栏是固定在顶部的AppBar,需要调整Scrollable.ensureVisible的padding参数,值对应导航栏的高度,避免区块被导航栏遮挡
  3. 可以根据需求调整滚动动画的时长和曲线,优化交互体验

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 21:31:08