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

求助实现特定Sliver滚动效果:视差背景+固定头部等需求

实现指定滚动效果的Flutter方案

核心思路

放弃SliverAppBar的默认逻辑,改用CustomScrollView搭配SliverPersistentHeader自定义头部行为,结合Stack+Positioned实现堆叠布局,视差效果通过监听滚动偏移动态调整背景位置实现。

完整代码实现

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: 'Scroll Effect Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const ScrollEffectPage(),
    );
  }
}

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

  @override
  State<ScrollEffectPage> createState() => _ScrollEffectPageState();
}

class _ScrollEffectPageState extends State<ScrollEffectPage> {
  final ScrollController _scrollController = ScrollController();
  double _parallaxOffset = 0.0;
  final double _maxParallaxOffset = 100.0; // 背景最大偏移量

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

  void _updateParallax() {
    setState(() {
      // 限制偏移范围,避免背景移出可视区域
      _parallaxOffset = _scrollController.offset.clamp(0.0, _maxParallaxOffset);
    });
  }

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

  @override
  Widget build(BuildContext context) {
    final double statusBarHeight = MediaQuery.of(context).padding.top;
    final double headerCollapsedHeight = kToolbarHeight + statusBarHeight;
    final double headerExpandedHeight = 300.0; // 头部展开高度

    return Scaffold(
      body: CustomScrollView(
        controller: _scrollController,
        slivers: [
          // 自定义持久化头部
          SliverPersistentHeader(
            pinned: true,
            delegate: _CustomHeaderDelegate(
              statusBarHeight: statusBarHeight,
              expandedHeight: headerExpandedHeight,
              collapsedHeight: headerCollapsedHeight,
              parallaxOffset: _parallaxOffset,
              maxParallaxOffset: _maxParallaxOffset,
            ),
          ),
          // 测试滚动的内容区域
          SliverList(
            delegate: SliverChildBuilderDelegate(
              (context, index) => ListTile(
                title: Text('列表项 ${index + 1}'),
                subtitle: Text('滚动测试内容'),
              ),
              childCount: 50,
            ),
          ),
        ],
      ),
    );
  }
}

class _CustomHeaderDelegate extends SliverPersistentHeaderDelegate {
  final double statusBarHeight;
  final double expandedHeight;
  final double collapsedHeight;
  final double parallaxOffset;
  final double maxParallaxOffset;

  _CustomHeaderDelegate({
    required this.statusBarHeight,
    required this.expandedHeight,
    required this.collapsedHeight,
    required this.parallaxOffset,
    required this.maxParallaxOffset,
  });

  @override
  Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
    // 计算头部收缩比例,0为完全展开,1为完全收缩
    final shrinkRatio = shrinkOffset / (expandedHeight - collapsedHeight);
    final isCollapsed = shrinkRatio >= 1.0;

    return Stack(
      fit: StackFit.expand,
      children: [
        // 视差背景图
        Positioned(
          top: -parallaxOffset,
          left: 0,
          right: 0,
          height: expandedHeight + maxParallaxOffset,
          child: Image.asset(
            'assets/background.jpg', // 替换为你的背景图路径
            fit: BoxFit.cover,
          ),
        ),
        // 堆叠的红色容器+圆形Logo
        Positioned(
          bottom: isCollapsed ? 0 : 20,
          left: MediaQuery.of(context).size.width / 2 - 40,
          child: Opacity(
            opacity: isCollapsed ? 0 : 1 - shrinkRatio,
            child: Container(
              width: 80,
              height: 80,
              decoration: BoxDecoration(
                color: Colors.red,
                borderRadius: BorderRadius.circular(12),
              ),
              child: Center(
                child: Container(
                  width: 60,
                  height: 60,
                  decoration: const BoxDecoration(
                    color: Colors.white,
                    shape: BoxShape.circle,
                  ),
                  child: const Icon(Icons.person, size: 30), // 替换为你的Logo
                ),
              ),
            ),
          ),
        ),
        // 固定头部(标题+副标题)
        Positioned(
          top: statusBarHeight,
          left: 0,
          right: 0,
          height: kToolbarHeight,
          child: Container(
            color: isCollapsed ? Colors.white : Colors.transparent,
            padding: const EdgeInsets.symmetric(horizontal: 16),
            alignment: Alignment.centerLeft,
            child: Opacity(
              opacity: isCollapsed ? 1.0 : shrinkRatio,
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    '主标题',
                    style: TextStyle(
                      fontSize: 20,
                      fontWeight: FontWeight.bold,
                      color: isCollapsed ? Colors.black : Colors.white,
                    ),
                  ),
                  const SizedBox(height: 4),
                  Text(
                    '副标题',
                    style: TextStyle(
                      fontSize: 14,
                      color: isCollapsed ? Colors.grey[600] : Colors.white70,
                    ),
                  ),
                ],
              ),
            ),
          ),
        ),
      ],
    );
  }

  @override
  double get maxExtent => expandedHeight;

  @override
  double get minExtent => collapsedHeight;

  @override
  bool shouldRebuild(_CustomHeaderDelegate oldDelegate) {
    return oldDelegate.parallaxOffset != parallaxOffset ||
        oldDelegate.statusBarHeight != statusBarHeight;
  }
}

关键实现说明

  • 视差滚动:通过ScrollController监听滚动偏移,动态调整背景图的top值,让背景滚动速度慢于内容,形成视差效果。
  • 堆叠红色容器:用Positioned固定位置,根据头部收缩比例控制透明度和显示状态,滚动到顶部时自动隐藏。
  • 适配安全区域的固定头部:通过MediaQuery获取状态栏高度,头部收缩时切换背景色为白色,同时调整文字颜色确保可读性,全程适配状态栏高度。

注意事项

  1. 替换代码中assets/background.jpg为你的实际背景图路径,并在pubspec.yaml中配置资源。
  2. 可调整expandedHeight、maxParallaxOffset等参数适配不同UI需求。
  3. 若需更细腻的动画,可结合AnimationController替代直接监听滚动偏移。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 15:36:03