共计 7984 个字符,预计需要花费 20 分钟才能阅读完成。
Flutter NestedScrollView 内嵌视图滚动行为统一
视频
https://www.bilibili.com/video/BV1Gh4y1571p/
前言
上一节讲了 CustomScrollView,能够发现有的中央滚动并不是很连贯。
这时候就须要 NestedScrollView 来解决了。
明天会写一个如下图的例子来实现滚动统一。
原文 https://ducafecat.com/blog/flutter-sliver-nested-scroll-view
参考
https://api.flutter.dev/flutter/widgets/NestedScrollView-clas…
https://api.flutter.dev/flutter/widgets/SliverOverlapAbsorber…
https://api.flutter.dev/flutter/widgets/SliverOverlapInjector…
知识点 NestedScrollView
NestedScrollView
是 Flutter 中的一个 Widget,它能够嵌套多个滚动视图,例如 ListView
、GridView
、SliverAppBar
等。NestedScrollView
能够让多个滚动视图联动滚动,从而实现一些简单的交互成果。
常见的业务场景:
- 一个页面上有多个可滚动的区域,而且这些区域之间的滚动是互相独立的,然而它们的滚动行为须要协调一致,比方一个列表和一个悬浮的顶部栏。
- 实现相似于网易云音乐个人主页的成果,即在滚动过程中,一个悬浮的头部会被逐步放大,同时顶部的导航栏会突变隐没,直到最初整个头部齐全占据整个屏幕。
- 在列表中嵌套一个可滚动的子列表,例如在一个电商利用中,展现一个大分类下的多个小分类,每个小分类上面又有多个商品。
NestedScrollView
和 CustomScrollView
都是反对自定义滚动视图的 Widget。它们的区别在于,CustomScrollView
能够通过增加多个 Sliver
来实现简单的滚动视图成果,而 NestedScrollView
则是将多个滚动视图嵌套在一起,并提供了一些不便的接口来协调它们之间的滚动。因而,NestedScrollView
的应用场景更加适宜于多个可滚动区域之间须要协调滚动的状况。
步骤
NestedScrollView 分为头部和内容两个局部,咱们别离来实现。
第一步:实现 NestedScrollView 头部
lib/nested.dart
编写头部组件函数,创立页面 NestedScrollPage
class NestedScrollPage extends StatefulWidget {const NestedScrollPage({super.key}); | |
@override | |
State<NestedScrollPage> createState() => _NestedScrollPageState(); | |
} | |
class _NestedScrollPageState extends State<NestedScrollPage> {final List<String> _tabs = const ['tab1', 'tab2', "tab3", "tab4"]; |
筹备 _tabs 数据
build 函数
@override | |
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}); | |
@override | |
Widget build(BuildContext context) {return child;} | |
@override | |
Size get preferredSize => const Size.fromHeight(kToolbarHeight + 20.0); | |
} |
第二步:实现 NestedScrollView 内容
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. | |
@override | |
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}); | |
@override | |
Widget build(BuildContext context) {return child;} | |
@override | |
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}); | |
@override | |
State<NestedScrollPage> createState() => _NestedScrollPageState(); | |
} | |
class _NestedScrollPageState extends State<NestedScrollPage> {final List<String> _tabs = const ['tab1', 'tab2', "tab3", "tab4"]; | |
@override | |
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, | |
), | |
), | |
); | |
} |
代码
https://github.com/ducafecat/flutter_develop_tips/blob/main/flutter_application_sliver_scroll/lib/nested.dart
小结
应用 NestedScrollView
是一个十分弱小和灵便的 widget,能够实现许多常见的滚动视图布局,例如带有悬浮题目的列表视图,或者带有可开展 / 折叠局部的折叠面板。
感激浏览本文
如果我有什么错?请在评论中让我晓得。我很乐意改良。
© 猫哥
ducafecat.com
end