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

Flutter开发求助:实现固定顶部的Sliver水平导航栏及选中动效

实现滚动固定的水平导航栏(覆盖图片底部+选中动效)

以下是符合需求的完整实现代码,基于你已有的CustomScrollView和sliver_tools库进行改造:

import 'package:find_your_event/models/festival.dart';
import 'package:flutter/material.dart';
import 'package:sliver_tools/sliver_tools.dart';

class FestivalInformation extends StatefulWidget {
  const FestivalInformation({required this.festival, super.key});

  final Festival festival;

  @override
  State<FestivalInformation> createState() => _FestivalInformationState();
}

class _FestivalInformationState extends State<FestivalInformation> {
  // 跟踪选中的导航项索引
  int _selectedIndex = 0;
  // 导航项列表(可根据需求修改)
  final List<String> _tabs = ['About', 'Lineup', 'Tickets', 'FAQ'];
  // 用于获取导航项位置/宽度的全局Key
  final List<GlobalKey> _tabKeys = List.generate(4, (_) => GlobalKey());

  @override
  Widget build(BuildContext context) {
    return CustomScrollView(
      slivers: [
        // 顶部固定的Medium样式SliverAppBar
        SliverAppBar.medium(
          pinned: true,
          title: Text(widget.festival.name),
          leading: const Icon(Icons.arrow_back),
        ),
        MultiSliver(
          pushPinnedChildren: true, // 让固定导航栏推挤下方内容,避免遮挡
          children: [
            // 叠加图片与导航栏的SliverStack
            SliverStack(
              insetOnOverlap: false,
              children: [
                // 横幅图片
                SliverToBoxAdapter(
                  child: Image.network(
                    widget.festival.bannerURL,
                    width: double.infinity,
                    height: 250,
                    fit: BoxFit.cover,
                  ),
                ),
                // 固定在图片底部、滚动后吸附到顶部的导航栏
                SliverPinnedHeader(
                  child: Container(
                    height: 60,
                    padding: const EdgeInsets.symmetric(horizontal: 16),
                    // 渐变背景让图片到导航栏过渡自然
                    decoration: const BoxDecoration(
                      gradient: LinearGradient(
                        begin: Alignment.topCenter,
                        end: Alignment.bottomCenter,
                        colors: [Colors.transparent, Colors.white],
                      ),
                    ),
                    child: Stack(
                      children: [
                        // 水平导航项
                        Row(
                          children: List.generate(_tabs.length, (index) {
                            return Expanded(
                              child: GestureDetector(
                                key: _tabKeys[index],
                                onTap: () => setState(() => _selectedIndex = index),
                                child: Padding(
                                  padding: const EdgeInsets.symmetric(vertical: 16),
                                  child: Text(
                                    _tabs[index],
                                    textAlign: TextAlign.center,
                                    style: TextStyle(
                                      fontWeight: _selectedIndex == index ? FontWeight.bold : FontWeight.normal,
                                      color: _selectedIndex == index ? Colors.black : Colors.grey[600],
                                    ),
                                  ),
                                ),
                              ),
                            );
                          }),
                        ),
                        // 选中项下方的粉色移动条
                        AnimatedPositioned(
                          duration: const Duration(milliseconds: 300),
                          curve: Curves.easeInOut,
                          left: _getTabLeftPosition(_selectedIndex),
                          bottom: 0,
                          width: _getTabWidth(_selectedIndex),
                          child: Container(
                            height: 3,
                            color: Colors.pink,
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
              ],
            ),
            // 下方内容区域(可根据选中标签切换内容)
            SliverList(
              delegate: SliverChildBuilderDelegate(
                (context, index) {
                  return Container(
                    padding: const EdgeInsets.all(16),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Text(
                          _tabs[_selectedIndex],
                          style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
                        ),
                        const SizedBox(height: 16),
                        Text(
                          '这是${_tabs[_selectedIndex]}标签下的内容,滚动时导航栏会固定在顶部。',
                          style: const TextStyle(fontSize: 16, height: 1.5),
                        ),
                        // 模拟长内容,方便测试滚动效果
                        ...List.generate(20, (i) => Text('内容行 ${i+1}', style: const TextStyle(fontSize: 16))),
                      ],
                    ),
                  );
                },
                childCount: 1,
              ),
            ),
          ],
        ),
      ],
    );
  }

  // 获取选中导航项的左侧偏移量
  double _getTabLeftPosition(int index) {
    final renderBox = _tabKeys[index].currentContext?.findRenderObject() as RenderBox?;
    if (renderBox == null) return 0;
    final offset = renderBox.localToGlobal(Offset.zero);
    return offset.dx - 16; // 减去容器的水平内边距
  }

  // 获取选中导航项的宽度
  double _getTabWidth(int index) {
    final renderBox = _tabKeys[index].currentContext?.findRenderObject() as RenderBox?;
    return renderBox?.size.width ?? 0;
  }
}

关键实现细节

  • 状态管理:将原无状态组件改为有状态组件,跟踪选中索引和导航项位置,支持动态更新UI
  • 导航栏固定:通过SliverPinnedHeader实现导航栏滚动时吸附到顶部,MultiSliver的pushPinnedChildren属性确保内容不会被导航栏遮挡
  • 覆盖图片布局:使用SliverStack将导航栏叠加在图片底部,配合渐变背景提升视觉过渡效果
  • 粉色条动效:通过AnimatedPositioned结合导航项的实际位置/宽度,实现平滑的移动动画

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 15:02:05