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