Flutter中CustomScrollView实现类似Android AppBarLayout的滚动行为
Flutter 实现类似Android AppBarLayout的滚动行为(CustomScrollView版)
可以直接用Flutter内置的Sliver组件实现需求,核心是通过SliverPersistentHeader自定义Delegate来匹配原生的滚动标记行为,无需依赖第三方库。
1. 实现scroll|snap|enterAlways效果的组件
这个组件需要满足两个核心特性:
- 吸附(snap):无中间滚动状态,要么完全显示要么完全隐藏
- enterAlways:无论当前滚动位置,向下滚动立即显示自身
通过SliverPersistentHeader的floating: true实现enterAlways效果,snap: true配合自定义Delegate完成吸附逻辑:
class EnterAlwaysSnapHeaderDelegate extends SliverPersistentHeaderDelegate { final double height; final Widget child; EnterAlwaysSnapHeaderDelegate({required this.height, required this.child}); @override double get maxExtent => height; @override double get minExtent => 0; @override Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) { return SizedBox(height: maxExtent - shrinkOffset, child: child); } @override bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) { return oldDelegate.maxExtent != height || oldDelegate != this; } @override FloatingHeaderSnapConfiguration? get snapConfiguration => const FloatingHeaderSnapConfiguration(); }
在CustomScrollView中使用:
SliverPersistentHeader( delegate: EnterAlwaysSnapHeaderDelegate( height: 60, child: Container( color: Colors.blue, alignment: Alignment.center, child: const Text( "Scroll|Snap|EnterAlways", style: TextStyle(color: Colors.white, fontSize: 16), ), ), ), floating: true, // 实现enterAlways:向下滚动立即显示 snap: true, // 实现吸附效果 )
2. 实现noScroll粘性头部
这个组件需要固定在顶部,不随滚动隐藏,同样用SliverPersistentHeader实现,通过设置pinned: true并让Delegate的最小/最大高度一致:
class StickyHeaderDelegate extends SliverPersistentHeaderDelegate { final double height; final Widget child; StickyHeaderDelegate({required this.height, required this.child}); @override double get maxExtent => height; @override double get minExtent => height; // 最小高度等于最大高度,禁止收缩 @override Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) { return child; } @override bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) { return oldDelegate.maxExtent != height || oldDelegate != this; } }
在CustomScrollView中使用:
SliverPersistentHeader( delegate: StickyHeaderDelegate( height: 50, child: Container( color: Colors.green, alignment: Alignment.center, child: const Text( "NoScroll (Sticky)", style: TextStyle(color: Colors.white, fontSize: 16), ), ), ), pinned: true, // 固定在顶部,不随滚动隐藏 )
完整示例代码
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( home: Scaffold( body: CustomScrollView( slivers: [ // scroll|snap|enterAlways 组件 SliverPersistentHeader( delegate: EnterAlwaysSnapHeaderDelegate( height: 60, child: Container( color: Colors.blue, alignment: Alignment.center, child: const Text( "Scroll|Snap|EnterAlways", style: TextStyle(color: Colors.white, fontSize: 16), ), ), ), floating: true, snap: true, ), // noScroll 粘性头部 SliverPersistentHeader( delegate: StickyHeaderDelegate( height: 50, child: Container( color: Colors.green, alignment: Alignment.center, child: const Text( "NoScroll (Sticky)", style: TextStyle(color: Colors.white, fontSize: 16), ), ), ), pinned: true, ), // 滚动列表内容 SliverList( delegate: SliverChildBuilderDelegate( (context, index) => ListTile(title: Text("Item $index")), childCount: 50, ), ), ], ), ), ); } } class EnterAlwaysSnapHeaderDelegate extends SliverPersistentHeaderDelegate { final double height; final Widget child; EnterAlwaysSnapHeaderDelegate({required this.height, required this.child}); @override double get maxExtent => height; @override double get minExtent => 0; @override Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) { return SizedBox(height: maxExtent - shrinkOffset, child: child); } @override bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) { return oldDelegate.maxExtent != height || oldDelegate != this; } @override FloatingHeaderSnapConfiguration? get snapConfiguration => const FloatingHeaderSnapConfiguration(); } class StickyHeaderDelegate extends SliverPersistentHeaderDelegate { final double height; final Widget child; StickyHeaderDelegate({required this.height, required this.child}); @override double get maxExtent => height; @override double get minExtent => height; @override Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) { return child; } @override bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) { return oldDelegate.maxExtent != height || oldDelegate != this; } }
补充说明
关于你提到的SliverAppBar:它本质是基于SliverPersistentHeader封装的应用栏组件,虽然自带工具栏、TabBar等功能,但如果你的场景不需要这些元素,直接用SliverPersistentHeader自定义Delegate是更轻量、灵活的选择,完全可以替代原生AppBarLayout的滚动行为。
内容的提问来源于stack exchange,提问作者SqueezyMo
相关产品推荐
相关产品推荐

