Flutter中是否有类似AnimatedSize的Sliver尺寸动画组件?
Flutter中实现Sliver尺寸变化动画的方案
Flutter官方并没有提供与AnimatedSize直接对应的Sliver组件,但可以通过以下两种方式实现Sliver的尺寸变化动画:
一、错误原因分析
你之前的代码报错是因为:
AnimatedSize仅支持RenderBox类型的普通组件,而Sliver属于RenderSliver类型,直接嵌套会导致类型不匹配;- 错误地将
SliverList作为AnimatedSize的子组件,Sliver组件只能放在CustomScrollView的slivers列表中,不能作为普通Widget的子节点。
二、方案1:用SliverToBoxAdapter包裹AnimatedSize(适合静态内容)
将需要动画的内容用普通Widget实现,通过AnimatedSize做尺寸动画,再用SliverToBoxAdapter转换为Sliver组件,适用于非滚动的动态高度内容(比如展开/折叠的详情块):
class Testingyeye extends StatefulWidget { const Testingyeye({Key? key}) : super(key: key); @override State<Testingyeye> createState() => _TestingyeyeState(); } class _TestingyeyeState extends State<Testingyeye> { bool _expanded = false; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("Testing"), ), body: CustomScrollView( slivers: [ SliverToBoxAdapter( child: GestureDetector( // 点击切换展开/折叠状态 onTap: () => setState(() => _expanded = !_expanded), child: AnimatedSize( duration: const Duration(milliseconds: 500), curve: Curves.fastOutSlowIn, child: Container( color: Colors.blue[100], // 根据状态切换显示内容 child: _expanded ? Column( children: List.generate(10, (index) => Padding( padding: const EdgeInsets.all(8.0), child: Text("hiii $index"), )), ) : const SizedBox.shrink(), ), ), ), ), // 其他Sliver组件 const SliverFillRemaining( child: Center(child: Text("下方内容")), ), ], ), ); } }
三、方案2:自定义SliverAnimatedSize组件(适合滚动内容)
如果需要给滚动型Sliver(比如列表)做尺寸动画,可以自定义一个基于动画控制器的Sliver组件,动态控制其高度:
自定义组件代码
class SliverAnimatedSize extends StatefulWidget { final Widget child; final Duration duration; final Curve curve; final bool isExpanded; const SliverAnimatedSize({ super.key, required this.child, required this.duration, this.curve = Curves.linear, required this.isExpanded, }); @override State<SliverAnimatedSize> createState() => _SliverAnimatedSizeState(); } class _SliverAnimatedSizeState extends State<SliverAnimatedSize> with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation<double> _heightAnimation; @override void initState() { super.initState(); _controller = AnimationController(vsync: this, duration: widget.duration); // 动画值从0到无限大(实际会根据子组件内容自适应) _heightAnimation = Tween<double>(begin: 0, end: double.infinity).animate( CurvedAnimation(parent: _controller, curve: widget.curve) ); // 初始状态如果是展开,直接播放正向动画 if (widget.isExpanded) _controller.forward(); } @override void didUpdateWidget(covariant SliverAnimatedSize oldWidget) { super.didUpdateWidget(oldWidget); // 状态变化时切换动画方向 if (widget.isExpanded != oldWidget.isExpanded) { widget.isExpanded ? _controller.forward() : _controller.reverse(); } } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return AnimatedBuilder( animation: _heightAnimation, builder: (context, child) { return SliverToBoxAdapter( child: SizedBox( height: _heightAnimation.value, // 子组件需设置shrinkWrap和禁止内部滚动(外层已有CustomScrollView) child: child, ), ); }, child: widget.child, ); } }
使用示例
class Testingyeye extends StatefulWidget { const Testingyeye({Key? key}) : super(key: key); @override State<Testingyeye> createState() => _TestingyeyeState(); } class _TestingyeyeState extends State<Testingyeye> { bool _expanded = false; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("Testing"), ), body: CustomScrollView( slivers: [ SliverToBoxAdapter( child: TextButton( onPressed: () => setState(() => _expanded = !_expanded), child: Text(_expanded ? "收起列表" : "展开列表"), ), ), // 使用自定义的SliverAnimatedSize SliverAnimatedSize( duration: const Duration(milliseconds: 500), curve: Curves.fastOutSlowIn, isExpanded: _expanded, child: ListView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemCount: 15, itemBuilder: (context, index) => ListTile( title: Text("列表项 $index"), ), ), ), const SliverFillRemaining( child: Center(child: Text("下方内容")), ), ], ), ); } }
总结
- 静态内容优先用方案1,实现简单;
- 滚动内容用自定义
SliverAnimatedSize,灵活控制动画过程。
内容的提问来源于stack exchange,提问作者Shawn
相关产品推荐
相关产品推荐

