上一节讲了 CustomScrollView ,可以发现有的地方滚动并不是很连贯。
这时候就需要 NestedScrollView 来处理了。
今天会写一个如下图的例子来实现滚动一致。
原文 https://ducafecat.com/blog/flutter-sliver-nested-scroll-view
https://api.flutter.dev/flutter/widgets/NestedScrollView-class.html
https://api.flutter.dev/flutter/widgets/SliverOverlapAbsorber-class.html
https://api.flutter.dev/flutter/widgets/SliverOverlapInjector-class.html
NestedScrollView
是 Flutter 中的一个 Widget,它可以嵌套多个滚动视图,例如 ListView
、GridView
、SliverAppBar
等。NestedScrollView
可以让多个滚动视图联动滚动,从而实现一些复杂的交互效果。
常见的业务场景:
NestedScrollView
和 CustomScrollView
都是支持自定义滚动视图的 Widget。它们的区别在于,CustomScrollView
可以通过添加多个 Sliver
来实现复杂的滚动视图效果,而 NestedScrollView
则是将多个滚动视图嵌套在一起,并提供了一些方便的接口来协调它们之间的滚动。因此,NestedScrollView
的使用场景更加适合于多个可滚动区域之间需要协调滚动的情况。
NestedScrollView 分为头部和内容两个部分,我们分别来实现。
lib/nested.dart
编写头部组件函数,创建页面 NestedScrollPage
class NestedScrollPage extends StatefulWidget { const NestedScrollPage({super.key}); State<NestedScrollPage> createState() => _NestedScrollPageState(); } class _NestedScrollPageState extends State<NestedScrollPage> { final List<String> _tabs = const ['tab1', 'tab2', "tab3", "tab4"];
准备 _tabs 数据
build 函数
Widget build(BuildContext context) { return Scaffold( body: DefaultTabController( length: _tabs.length, child: NestedScrollView( headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { return <Widget>[ _buildHeader(context, innerBoxIsScrolled), ]; }, body: _buildTabBarView(), ), ), ); }
headerSliverBuilder 头部实现函数
// 头部 Widget _buildHeader(BuildContext context, bool innerBoxIsScrolled) { return // SliverOverlapAbsorber 的作用是处理重叠滚动效果, // 防止 CustomScrollView 中的滚动视图与其他视图重叠。 SliverOverlapAbsorber( handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), sliver: // SliverAppBar 的作用是创建可折叠的顶部应用程序栏, // 它可以随着滚动而滑动或固定在屏幕顶部,并且可以与其他 Sliver 小部件一起使用。 SliverAppBar( title: const Text('滚动一致性'), pinned: true, elevation: 6, //影深 expandedHeight: 300.0, forceElevated: innerBoxIsScrolled, //为true时展开有阴影 flexibleSpace: FlexibleSpaceBar( background: Image.asset( "assets/images/banner-bg.jpg", fit: BoxFit.cover, ), ), // 底部固定栏 bottom: MyCustomAppBar( child: Column( children: [ Container( color: Colors.greenAccent, child: const Center(child: Text('固定高度内容')), ), TabBar( tabs: _tabs .map((String name) => Tab( text: name, )) .toList(), ), ], ), ), ), ); }
SliverOverlapAbsorber 与 SliverOverlapInjector,作用是防止 CustomScrollView 中的滚动视图与其他视图重叠。
编写 MyCustomAppBar 悬停 Bar
lib/app_bar.dart
import 'package:flutter/material.dart'; class MyCustomAppBar extends StatelessWidget implements PreferredSizeWidget { final Widget child; const MyCustomAppBar({super.key, required this.child}); Widget build(BuildContext context) { return child; } Size get preferredSize => const Size.fromHeight(kToolbarHeight + 20.0); }
lib/nested.dart
TabBarView 混入各种情况:横向滚动、固定高度、SliverList列表
Widget _buildTabBarView() { return TabBarView( children: _tabs.map((String name) { return SafeArea( top: false, bottom: false, child: Builder( builder: (BuildContext context) { return CustomScrollView( key: PageStorageKey<String>(name), slivers: <Widget>[ // SliverOverlapInjector 的作用是处理重叠滚动效果, // 确保 CustomScrollView 中的滚动视图不会与其他视图重叠。 SliverOverlapInjector( handle: NestedScrollView.sliverOverlapAbsorberHandleFor( context), ), // 横向滚动 SliverToBoxAdapter( child: SizedBox( height: 100, child: PageView( children: [ Container( color: Colors.yellow, child: const Center(child: Text('横向滚动')), ), Container(color: Colors.green), Container(color: Colors.blue), ], ), ), ), // 固定高度内容 SliverToBoxAdapter( child: Container( height: 100, color: Colors.greenAccent, child: const Center(child: Text('固定高度内容')), ), ), // 列表 buildContent(name), // 固定高度内容 SliverToBoxAdapter( child: Container( height: 100, color: Colors.greenAccent, child: const Center(child: Text('固定高度内容')), ), ), // 列表 100 行 SliverList( delegate: SliverChildBuilderDelegate( (BuildContext context, int index) { return ListTile(title: Text('Item $index')); }, childCount: 100, ), ), ], ); }, ), ); }).toList(), ); }
SliverOverlapInjector 的作用是处理重叠滚动效果,
确保 CustomScrollView 中的滚动视图不会与其他视图重叠。
内容列表
// 内容列表 Widget buildContent(String name) => SliverPadding( padding: const EdgeInsets.all(8.0), sliver: SliverFixedExtentList( itemExtent: 48.0, delegate: SliverChildBuilderDelegate( (BuildContext context, int index) { return ListTile( title: Text('$name - $index'), ); }, childCount: 50, ), ), );
启动
lib/main.dart
import 'package:flutter/material.dart'; import 'nested.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); // This widget is the root of your application. Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, useMaterial3: true, ), // home: const MyPageView(), home: const NestedScrollPage(), ); } }
直接设置 home 进入
NestedScrollPage
界面
lib/app_bar.dart
import 'package:flutter/material.dart'; class MyCustomAppBar extends StatelessWidget implements PreferredSizeWidget { final Widget child; const MyCustomAppBar({super.key, required this.child}); Widget build(BuildContext context) { return child; } Size get preferredSize => const Size.fromHeight(kToolbarHeight + 20.0); }
lib/nested.dart
import 'package:flutter/material.dart'; import 'app_bar.dart'; class NestedScrollPage extends StatefulWidget { const NestedScrollPage({super.key}); State<NestedScrollPage> createState() => _NestedScrollPageState(); } class _NestedScrollPageState extends State<NestedScrollPage> { final List<String> _tabs = const ['tab1', 'tab2', "tab3", "tab4"]; Widget build(BuildContext context) { return Scaffold( body: DefaultTabController( length: _tabs.length, child: NestedScrollView( headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { return <Widget>[ _buildHeader(context, innerBoxIsScrolled), ]; }, body: _buildTabBarView(), ), ), ); } // 头部 Widget _buildHeader(BuildContext context, bool innerBoxIsScrolled) { return // SliverOverlapAbsorber 的作用是处理重叠滚动效果, // 防止 CustomScrollView 中的滚动视图与其他视图重叠。 SliverOverlapAbsorber( handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), sliver: // SliverAppBar 的作用是创建可折叠的顶部应用程序栏, // 它可以随着滚动而滑动或固定在屏幕顶部,并且可以与其他 Sliver 小部件一起使用。 SliverAppBar( title: const Text('滚动一致性'), pinned: true, elevation: 6, //影深 expandedHeight: 300.0, forceElevated: innerBoxIsScrolled, //为true时展开有阴影 flexibleSpace: FlexibleSpaceBar( background: Image.asset( "assets/images/banner-bg.jpg", fit: BoxFit.cover, ), ), // 底部固定栏 bottom: MyCustomAppBar( child: Column( children: [ Container( color: Colors.greenAccent, child: const Center(child: Text('固定高度内容')), ), TabBar( tabs: _tabs .map((String name) => Tab( text: name, )) .toList(), ), ], ), ), ), ); } Widget _buildTabBarView() { return TabBarView( children: _tabs.map((String name) { return SafeArea( top: false, bottom: false, child: Builder( builder: (BuildContext context) { return CustomScrollView( key: PageStorageKey<String>(name), slivers: <Widget>[ // SliverOverlapInjector 的作用是处理重叠滚动效果, // 确保 CustomScrollView 中的滚动视图不会与其他视图重叠。 SliverOverlapInjector( handle: NestedScrollView.sliverOverlapAbsorberHandleFor( context), ), // 横向滚动 SliverToBoxAdapter( child: SizedBox( height: 100, child: PageView( children: [ Container( color: Colors.yellow, child: const Center(child: Text('横向滚动')), ), Container(color: Colors.green), Container(color: Colors.blue), ], ), ), ), // 固定高度内容 SliverToBoxAdapter( child: Container( height: 100, color: Colors.greenAccent, child: const Center(child: Text('固定高度内容')), ), ), // 列表 buildContent(name), // 固定高度内容 SliverToBoxAdapter( child: Container( height: 100, color: Colors.greenAccent, child: const Center(child: Text('固定高度内容')), ), ), // 列表 100 行 SliverList( delegate: SliverChildBuilderDelegate( (BuildContext context, int index) { return ListTile(title: Text('Item $index')); }, childCount: 100, ), ), ], ); }, ), ); }).toList(), ); } Widget buildContent(String name) => SliverPadding( padding: const EdgeInsets.all(8.0), sliver: SliverFixedExtentList( itemExtent: 48.0, delegate: SliverChildBuilderDelegate( (BuildContext context, int index) { return ListTile( title: Text('$name - $index'), ); }, childCount: 50, ), ), ); }
使用 NestedScrollView
是一个非常强大和灵活的 widget,可以实现许多常见的滚动视图布局,例如带有悬浮标题的列表视图,或者带有可展开/折叠部分的折叠面板。
感谢阅读本文
如果我有什么错?请在评论中让我知道。我很乐意改进。
© 猫哥 ducafecat.com
end
Copyright 2023 ducafecat. All rights reserved.
微信: ducafecat, line: ducafecat,京ICP备2021009050号-3