Flutter NestedScrollView 内嵌视图滚动行为一致
Flutter NestedScrollView 内嵌视图滚动行为一致

视频
https://youtu.be/_h7CkzXY3aM
https://www.bilibili.com/video/BV1Gh4y1571p/
前言
上一节讲了 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
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
相关文章:

Flutter NestedScrollView 内嵌视图滚动行为一致
Flutter NestedScrollView 内嵌视图滚动行为一致 视频 https://youtu.be/_h7CkzXY3aM https://www.bilibili.com/video/BV1Gh4y1571p/ 前言 上一节讲了 CustomScrollView ,可以发现有的地方滚动并不是很连贯。 这时候就需要 NestedScrollView 来处理了。 今天会写…...

展示用HTML编写的个人简历信息
展示用HTML编写的个人简历信息 相关代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document…...

PostgreSQL 实体化视图的使用
上周的教程中,通过 DVD Rental Database 示例,让我们了解了在 PostgreSQL 中创建实体化视图的过程。正如我们所了解的,PostgreSQL 实体化视图提供了一种强大的机制,通过预计算和存储查询结果集为物理表来提高查询性能。接下来的内…...

【MySQL】数据库索引详解 | 聚簇索引 | 最左匹配原则 | 索引的优缺点
创作不易,本篇文章如果帮助到了你,还请点赞 关注支持一下♡>𖥦<)!! 主页专栏有更多知识,如有疑问欢迎大家指正讨论,共同进步! 🔥c系列专栏:C/C零基础到精通 🔥 给大…...
HarmonyOS 自定义进度条 Stage模型
通过onTouch监听滑动,动态改变圆角 let radius 0Entry Component struct TestPage {State flip: boolean falseState progress:number 20build() {Row() {Column() {RelativeContainer(){Rect({ width: 100%, height: 40 }).radius(10).fill("#505050"…...

Flink双流(join)
一、介绍 Join大体分类只有两种:Window Join和Interval Join Window Join有可以根据Window的类型细分出3种:Tumbling(滚动) Window Join、Sliding(滑动) Window Join、Session(会话) Widnow Join。 🌸Window 类型的join都是利用window的机制…...

使用Nginx或者Fiddler快速代理调试
1 背景问题 在分析业务系统程序问题时,存在服务系统环境是其它部门或者其它小组搭建或运维的,并且现在微服务时代,服务多且复杂,在个人机器上搭建起如此环境,要么费事费力,要么不具备充足条件。 急需有一种方法或者工具可以快速辅助调试定位分析问题。本文下面介绍代理方…...
MySQL高级特性篇(3)-全文检索的实现与优化
MySQL数据库全文检索是指对数据库中的文本字段进行高效地搜索和匹配。在MySQL数据库中,可以使用全文检索来实现快速的文本搜索功能,并且可以通过一些优化手段提高全文检索的性能。 一、MySQL全文检索的基本概念 全文检索是一种将关键字搜索与自然语言处…...

MySQL加锁策略详解
我们主要从三个方面来讨论这个问题: 啥时候加?如何加?什么时候该加什么时候不该加? 1、啥时候加 1.1 显式锁 MySQL 的加锁可以分为显式加锁和隐式加锁,显式加锁我们比较好识别的,因为他往往直接体现在 S…...

会声会影2024新功能及剪辑视频步骤教程
会声会影2024的新功能主要包括: 全新的标题动态与特效:用户可以为文字标题指定进入、中场和退出的不同动态效果,比如闪现进入、中场弹跳和淡出退出等,让文字标题更具动感。此外,还新增了多个标题特效,包括…...

Rust Vs Go:从头构建一个web服务
Go 和 Rust 之间的许多比较都强调它们在语法和初始学习曲线上的差异。然而,最终的决定性因素是重要项目的易用性。 “Rust 与 Go”争论 Rust vs Go 是一个不断出现的话题,并且已经有很多关于它的文章。部分原因是开发人员正在寻找信息来帮助他们决定下…...

几个常见的C/C++语言冷知识
当涉及到C/C语言时,有一些冷知识可能并不为人所熟知,但却可以让你更深入地理解这门古老而强大的编程语言。以下是一些有趣的C/C语言冷知识。 1. 数组的下标可以是负数 在我们日常的C语言编程中,数组是一个非常常见的数据结构。我们习惯性地使…...
低代码开发:学校低成本数字化转型的新引擎
随着科技的飞速发展,数字化转型已经成为教育行业的一大趋势。然而,对于许多学校来说,高昂的数字化改造成本成为了一道难以逾越的门槛。本文将探讨如何通过低代码开发,以低成本实现学校数字化转型,为教育行业注入新活力…...
【es6】解决箭头函数所有的问题,箭头函数的 this 指针,使用 new 操作符
箭头函数是 es6 提出的this 指针指向 定义箭头函数的全局上下文或函数上下文,是固定的call\apply\bind无法改变箭头函数的this没有 arguments 对象,但是可以访问外部作用域中的 arguments 对象没有 prototype 属性不能用作 generator 函数,不…...

2-1SDN(软件定义网络)环境测试实验(无默认控制器)-静态流表
控制器是为了更好集中控制网络的行为,当然在实际中我们可以根据需要选择是否选择控制器,或是结合ovs交换机的特点进行针对性的转发行为设置(功能需要书写代码进行软件定义),转发功能需要手工添加流表才能实现 如&#…...

飞天使-k8s知识点22-kubernetes实操7-ingress
文章目录 ingress环境准备准备service和pod验证效果 https 代理效果 ingress 在 Kubernetes 中,Ingress 是一种 API 对象,它管理外部访问集群内部服务的规则。你可以将其视为一个入口,它可以将来自集群外部的 HTTP 和 HTTPS 路由到集群内部的…...

SwiftUI 集合视图(Grid)拖放交换 Cell 的极简实现
概览 自从 SwiftUI 横空出世那天起,小伙伴们都感受到了它惊人的简单与便捷。而在本课中,我们将会用一个小“栗子”更直观的让大家体验到它无与伦比简洁的描述性特质: 如上图所示,我们在 SwiftUI 中实现了 Grid 中拖放交换 Cell 的…...

MATLAB中gtext函数用法
目录 语法 说明 示例 使用鼠标将文本添加到图窗 指定字体大小和颜色 在创建后修改文本 gtext函数的功能是使用鼠标将文本添加到图窗。 语法 gtext(str) gtext(str,Name,Value) t gtext(___) 说明 gtext(str) 在使用鼠标选择的位置插入文本 str。当将鼠标指针悬停在图…...

迁移SVN和GIT的云端数据
在新服务器搭建GIT仓库 教程很多,大致的流程是: 1. 新建linux用户密码专用于git操作 2. 新建git库的存放文件夹并在此初始化git 3. 配置git库所在目录权限 *只需要有一个库和有一个用户,与在windows上建库是一样的。不需要搭建类似gitla…...

算法--动态规划(背包问题)
这里写目录标题 总览dp问题的优化01背包问题概述算法思想算法思想中的注意点例题代码 完全背包问题概述 多重背包问题概述 分组背包问题概述 总览 dp问题的优化 要清楚:dp问题的优化一般是对dp问题的代码或者计算方程做一个等效变形 有了这个前提,我们在…...
挑战杯推荐项目
“人工智能”创意赛 - 智能艺术创作助手:借助大模型技术,开发能根据用户输入的主题、风格等要求,生成绘画、音乐、文学作品等多种形式艺术创作灵感或初稿的应用,帮助艺术家和创意爱好者激发创意、提高创作效率。 - 个性化梦境…...

Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例
使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件,常用于在两个集合之间进行数据转移,如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model:绑定右侧列表的值&…...
IGP(Interior Gateway Protocol,内部网关协议)
IGP(Interior Gateway Protocol,内部网关协议) 是一种用于在一个自治系统(AS)内部传递路由信息的路由协议,主要用于在一个组织或机构的内部网络中决定数据包的最佳路径。与用于自治系统之间通信的 EGP&…...

【JVM】- 内存结构
引言 JVM:Java Virtual Machine 定义:Java虚拟机,Java二进制字节码的运行环境好处: 一次编写,到处运行自动内存管理,垃圾回收的功能数组下标越界检查(会抛异常,不会覆盖到其他代码…...

为什么需要建设工程项目管理?工程项目管理有哪些亮点功能?
在建筑行业,项目管理的重要性不言而喻。随着工程规模的扩大、技术复杂度的提升,传统的管理模式已经难以满足现代工程的需求。过去,许多企业依赖手工记录、口头沟通和分散的信息管理,导致效率低下、成本失控、风险频发。例如&#…...
大语言模型如何处理长文本?常用文本分割技术详解
为什么需要文本分割? 引言:为什么需要文本分割?一、基础文本分割方法1. 按段落分割(Paragraph Splitting)2. 按句子分割(Sentence Splitting)二、高级文本分割策略3. 重叠分割(Sliding Window)4. 递归分割(Recursive Splitting)三、生产级工具推荐5. 使用LangChain的…...

智能仓储的未来:自动化、AI与数据分析如何重塑物流中心
当仓库学会“思考”,物流的终极形态正在诞生 想象这样的场景: 凌晨3点,某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径;AI视觉系统在0.1秒内扫描包裹信息;数字孪生平台正模拟次日峰值流量压力…...
大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计
随着大语言模型(LLM)参数规模的增长,推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长,而KV缓存的内存消耗可能高达数十GB(例如Llama2-7B处理100K token时需50GB内存&a…...

华为OD机考-机房布局
import java.util.*;public class DemoTest5 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseSystem.out.println(solve(in.nextLine()));}}priv…...
Linux系统部署KES
1、安装准备 1.版本说明V008R006C009B0014 V008:是version产品的大版本。 R006:是release产品特性版本。 C009:是通用版 B0014:是build开发过程中的构建版本2.硬件要求 #安全版和企业版 内存:1GB 以上 硬盘…...