Flutter中实现元素消失与收缩的Sliver动画方案咨询
Flutter实现滚动时顶部Logo/底部图标渐隐+中间文本收缩的Sliver方案
要实现你需要的滚动动画效果,核心是利用Flutter的Slivers组件监听滚动偏移量,动态控制元素的透明度、尺寸和位置。以下是完整的可运行实现方案:
核心思路
- 用
CustomScrollView作为滚动容器,搭配SliverPersistentHeader实现可滚动的头部区域 - 通过
shrinkOffset计算滚动进度,以此驱动Logo的渐隐、标题的收缩,以及底部操作栏的渐隐 - 利用
Opacity、动态样式赋值和Transform实现平滑过渡效果
完整代码实现
import 'package:flutter/material.dart'; import 'dart:math' as math; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Sliver Scroll Animation Demo', theme: ThemeData(primarySwatch: Colors.blue), home: const ScrollAnimationPage(), ); } } class ScrollAnimationPage extends StatefulWidget { const ScrollAnimationPage({super.key}); @override State<ScrollAnimationPage> createState() => _ScrollAnimationPageState(); } class _ScrollAnimationPageState extends State<ScrollAnimationPage> { final ScrollController _scrollController = ScrollController(); @override void dispose() { _scrollController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: CustomScrollView( controller: _scrollController, slivers: [ // 顶部可收缩Header(包含Logo和标题) SliverPersistentHeader( pinned: true, delegate: _SliverHeaderDelegate( maxHeight: 220, minHeight: 60, ), ), // 主内容列表 SliverList( delegate: SliverChildBuilderDelegate( (context, index) => ListTile( title: Text('列表项 ${index + 1}'), subtitle: Text('这是第${index + 1}条滚动内容'), ), childCount: 30, ), ), // 底部操作栏 SliverToBoxAdapter( child: AnimatedBuilder( animation: _scrollController, builder: (context, child) { double progress = 0.0; if (_scrollController.hasClients) { final offset = _scrollController.offset; // 滚动超过180时开始触发底部栏渐隐 progress = math.max(0, (offset - 180) / 100); progress = math.min(progress, 1.0); } return Opacity( opacity: 1 - progress, child: Transform.translate( offset: Offset(0, progress * 50), child: Container( height: 60, color: Colors.white, padding: const EdgeInsets.symmetric(horizontal: 20), child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ IconButton(icon: const Icon(Icons.home), onPressed: () {}), IconButton(icon: const Icon(Icons.favorite), onPressed: () {}), IconButton(icon: const Icon(Icons.settings), onPressed: () {}), ], ), ), ), ); }, ), ), ], ), ); } } // 自定义SliverHeader代理类 class _SliverHeaderDelegate extends SliverPersistentHeaderDelegate { final double maxHeight; final double minHeight; _SliverHeaderDelegate({ required this.maxHeight, required this.minHeight, }); @override Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) { // 计算收缩进度:0=完全展开,1=完全收缩 final progress = shrinkOffset / maxHeight; return Container( color: Colors.white, child: Stack( alignment: Alignment.center, children: [ // Logo区域:渐隐+上移 Opacity( opacity: 1 - progress, child: Transform.translate( offset: Offset(0, progress * -80), child: const FlutterLogo(size: 120), ), ), // 标题区域:收缩+上移 Positioned( top: 20 + progress * 20, left: 20, right: 20, child: Text( '我的页面', textAlign: TextAlign.center, style: TextStyle( fontSize: 28 - progress * 12, // 从28px收缩到16px fontWeight: FontWeight.bold, color: Colors.blue, ), ), ), ], ), ); } @override double get maxExtent => maxHeight; @override double get minExtent => minHeight; @override bool shouldRebuild(covariant _SliverHeaderDelegate oldDelegate) { return maxHeight != oldDelegate.maxHeight || minHeight != oldDelegate.minHeight; } }
关键细节说明
- 滚动进度统一控制:通过
shrinkOffset / maxExtent得到0-1的进度值,让所有动画保持同步节奏 - Logo动画:结合
Opacity和Transform.translate,让Logo在上移过程中逐渐消失,过渡更自然 - 标题动画:动态修改字体大小和位置,实现从居中大标题到顶部小标题的平滑转换
- 底部操作栏:监听滚动偏移量,当滚动到指定位置后触发渐隐+上移动画,避免遮挡内容
内容的提问来源于stack exchange,提问作者bd_28
相关产品推荐
相关产品推荐

