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