Flutter中如何滚动AppBar实现自动缩放并唤出抽屉菜单Drawer
问题根因
- 你的SliverAppBar被包裹在Scaffold.appBar属性下的CustomScrollView中,该CustomScrollView和页面主滚动逻辑完全独立,页面滚动事件无法传递到SliverAppBar,因此onStretchTrigger不会触发,尺寸也无法联动滚动动态更新
- Sliver系列组件的正确用法是作为CustomScrollView的子元素放在Scaffold.body中,嵌套在appBar属性中属于错误用法,自然无法实现滚动伸缩效果
修复方案
你需要使用NestedScrollView组件来联动SliverAppBar和TabBarView的滚动逻辑,这是Flutter中实现带TabBar的伸缩AppBar的标准方案,核心修改如下:
- 移除原来Scaffold.appBar属性下的PreferredSize、CustomScrollView嵌套逻辑
- 将Scaffold.body替换为NestedScrollView,把SliverAppBar和TabBar放在headerSliverBuilder中,原有TabBarView作为NestedScrollView的body
- 给SliverAppBar配置
pinned、expandedHeight、stretchTriggerOffset等必要参数,保证拉伸触发逻辑正常运行 - 用
SliverPersistentHeader包裹TabBar,保证TabBar在滚动时可以固定在顶部
修正后核心代码
import '../widgets/drawer_app.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import '../dummy_items.dart'; import './item_screen.dart'; class HomeScreen extends StatefulWidget { static const routeName = '/home_screen'; int _selectedTab = 2; @override _HomeScreenState createState() => _HomeScreenState(); } class _HomeScreenState extends State<HomeScreen> { @override Widget build(BuildContext context) { return DefaultTabController( initialIndex: 2, length: 5, child: Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ Theme.of(context).primaryColor, const Color.fromRGBO(0, 56, 100, 1.0) ]), ), child: Scaffold( backgroundColor: Colors.transparent, drawer: DrawerApp(), body: NestedScrollView( headerSliverBuilder: (context, innerBoxIsScrolled) { return [ SliverAppBar( backgroundColor: Colors.transparent, shadowColor: Colors.transparent, pinned: true, stretch: true, expandedHeight: 180, // 按需调整展开最大高度 stretchTriggerOffset: 100, // 按需调整拉伸触发阈值 onStretchTrigger: () { print("拉伸触发成功"); // 如需触发打开Drawer,直接调用下面的代码即可 // Scaffold.of(context).openDrawer(); return Future<void>.value(); }, flexibleSpace: const FlexibleSpaceBar( // 可自定义伸缩区域的内容,比如背景、标题等 background: SizedBox.expand(), ), ), SliverPersistentHeader( pinned: true, // 设为false则TabBar会跟着滚动收起 delegate: _TabBarDelegate( TabBar( onTap: (index) { setState(() { widget._selectedTab = index; }); }, tabs: [ Tab( icon: Icon( CupertinoIcons.add, size: widget._selectedTab == 0 ? 24 : 20, ), ), Tab( icon: Icon(Icons.ac_unit, size: widget._selectedTab == 1 ? 24 : 20)), Tab( icon: Icon(CupertinoIcons.alarm, size: widget._selectedTab == 2 ? 24 : 20), ), Tab( icon: Icon(CupertinoIcons.music_note, size: widget._selectedTab == 3 ? 24 : 20), ), Tab( icon: Icon(CupertinoIcons.tickets, size: widget._selectedTab == 4 ? 24 : 20), ) ], unselectedLabelColor: const Color.fromRGBO(142, 142, 147, 1), labelColor: const Color.fromRGBO(0, 122, 255, 1), overlayColor: MaterialStateProperty.all(Colors.transparent), ), ), ) ]; }, body: const TabBarView( children: [ ItemScreen(Icon(Icons.ac_unit), Icon(Icons.ac_unit), dummyFirst), ItemScreen(Icon(CupertinoIcons.eye_fill), Icon(CupertinoIcons.eye_slash_fill), dummySecond), ItemScreen(Icon(CupertinoIcons.eye_fill), Icon(CupertinoIcons.eye_slash_fill), dummyThird), ItemScreen(Icon(CupertinoIcons.tags_solid), Icon(CupertinoIcons.tags), dummyFourth), ItemScreen(Icon(CupertinoIcons.tags_solid), Icon(CupertinoIcons.tags), dummyFith), ], ), ), ), ), ); } } class _TabBarDelegate extends SliverPersistentHeaderDelegate { final TabBar tabBar; _TabBarDelegate(this.tabBar); @override Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) { return Container(child: tabBar); } @override double get maxExtent => tabBar.preferredSize.height; @override double get minExtent => tabBar.preferredSize.height; @override bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) { return false; } }
注意事项
- 不需要再手动维护additionalSize变量修改AppBar高度,SliverAppBar会根据expandedHeight和滚动偏移自动计算尺寸
- 如果不需要TabBar固定在顶部,只需将SliverPersistentHeader的pinned属性改为false即可
- 若要实现拉伸触发打开Drawer,直接在onStretchTrigger回调中调用
Scaffold.of(context).openDrawer()即可
内容的提问来源于stack exchange,提问作者Joh
相关产品推荐
相关产品推荐

