Flutter 实现 列表滑动过程控件停靠效果 学习
实现一个 Flutter 应用程序,使用 `Sliver` 系列组件来创建具有滚动效果的复杂布局。使用 `NestedScrollView` 和 `SliverPersistentHeader` 来实现固定和动态的头部效果,以及一个可滚动的列表。
前置知识点学习
SingleTickerProviderStateMixin
`SingleTickerProviderStateMixin` 是 Flutter 中一个常用的混入(mixin),主要用于 动画控制器(`AnimationController`) 的管理。它通常与 `State` 类结合使用,为动画提供 `Ticker`,从而高效管理动画的帧调用。
什么是 `Ticker`?
- `Ticker` 是 Flutter 动画系统的基础,它会按照屏幕刷新率(通常是每秒 60 次)调用一个回调函数,帮助你在每一帧更新动画。
- 使用 `Ticker` 可以让动画与设备的帧同步,从而实现平滑的动画效果。
`SingleTickerProviderStateMixin` 的作用?
`SingleTickerProviderStateMixin` 是 `TickerProvider` 的一个实现,专门用于只需要一个 `Ticker` 的动画场景。它避免了手动管理 `Ticker` 的麻烦,并确保动画与帧同步。
典型使用场景
`SingleTickerProviderStateMixin` 通常用于需要一个 `AnimationController` 的场景,例如:
- 页面切换动画。
- 简单的补间动画。
- 小型的交互动画。
使用 `SingleTickerProviderStateMixin` 的代码示例
以下是一个完整的例子,展示如何使用 `SingleTickerProviderStateMixin` 创建一个简单的补间动画。
import 'package:flutter/material.dart';class MyAnimatedWidget extends StatefulWidget {@override_MyAnimatedWidgetState createState() {return _MyAnimatedWidgetState();}
}class _MyAnimatedWidgetState extends State<MyAnimatedWidget>with SingleTickerProviderStateMixin {late AnimationController _controller; // 动画控制器late Animation<double> _animation; // 补间动画@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text("SingleTickerProviderStateMixin 示例")),body: Center(child: Container(width: _animation.value, // 动态更新宽度height: _animation.value, // 动态更新高度color: Colors.blue,),),);}@overridevoid dispose() {// 销毁动画控制器以释放资源_controller.dispose();super.dispose();}@overridevoid initState() {super.initState();// 初始化 AnimationController_controller = AnimationController(duration: const Duration(seconds: 2), // 动画持续时间vsync: this, // 使用 SingleTickerProviderStateMixin 提供的 vsync);// 使用 Tween 创建补间动画_animation = Tween<double>(begin: 0, end: 300).animate(_controller)..addListener(() {setState(() {}); // 每帧更新 UI});// 启动动画_controller.forward();}
}
SliverPersistentHeader
`SliverPersistentHeader` 是 Flutter 中 `Sliver` 系列组件的一部分,用于在滚动视图中创建一个具有持久行为的头部组件。它能够在滚动过程中根据需要进行伸缩、冻结或其他效果。
主要特点
持久性:`SliverPersistentHeader` 保留在滚动视图的顶部或底部,即使用户滚动内容,它也可以根据设置保持可见。
动态变化:可以动态调整其高度和内容。常用于实现如折叠效果的应用栏(AppBar)。
灵活性:通过实现 `SliverPersistentHeaderDelegate`,你可以自定义头部的布局和行为。
使用场景
创建一个在用户滚动时可以折叠的应用栏。
实现滚动时固定在顶部的导航栏。
制作具有粘性效果的分段标题。
实现步骤
实现 `SliverPersistentHeaderDelegate`:
- 这是一个抽象类,你需要实现它的方法以定义头部的行为和外观。
使用 `SliverPersistentHeader`:
- 将你自定义的 `SliverPersistentHeaderDelegate` 实例传递给它。
代码示例
下面是一个简单的例子,展示如何使用 `SliverPersistentHeader` 创建一个滚动时可以伸缩的头部。
import 'package:flutter/material.dart';class SliverPersistentHeaderExample extends StatelessWidget {const SliverPersistentHeaderExample({super.key});@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text("SliverPersistentHeaderExample 示例")),body: CustomScrollView(slivers: <Widget>[SliverPersistentHeader(delegate: MySliverAppBarDelegate(minHeight: 100.0,maxHeight: 200.0,child: Container(color: Colors.blue,child: const Center(child: Text('SliverPersistentHeader',style: TextStyle(color: Colors.white, fontSize: 24),),),)),pinned: true,),SliverList(delegate: SliverChildBuilderDelegate((BuildContext context, int index) {return ListTile(title: Text('Item #$index'),);},childCount: 50,))],),);}
}class MySliverAppBarDelegate extends SliverPersistentHeaderDelegate {final double minHeight;final double maxHeight;final Widget child;MySliverAppBarDelegate({required this.minHeight,required this.maxHeight,required this.child,});@overrideWidget build(BuildContext context, double shrinkOffset, bool overlapsContent) {return SizedBox.expand(child: child);}@overridedouble get maxExtent => maxHeight;@overridedouble get minExtent => minHeight;@overridebool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {return true;}
}
代码解析
`MySliverAppBarDelegate`:
- 继承自 `SliverPersistentHeaderDelegate`。
- 定义了 `minExtent` 和 `maxExtent`,分别表示头部的最小和最大高度。
- `build` 方法返回一个 `SizedBox.expand`,用于填充父级可用空间。
`SliverPersistentHeader`:
- 接受一个 `SliverPersistentHeaderDelegate` 实例。
- `pinned: true` 表示头部
- `delegate`: 这是 `SliverPersistentHeader` 的核心属性,它接收一个 `SliverPersistentHeaderDelegate` 的子类实例。在我们的例子中,它是 `MySliverAppBarDelegate`。这个委托类负责定义头部的内容以及在滚动过程中如何表现。
- `pinned`: 这个属性决定了头部是否在达到最小高度时固定在顶部。如果设置为 `true`,当用户滚动内容时,头部会固定在视图的顶部,不会继续滚动出屏幕。对于导航栏等需要始终可见的组件,这是一个常见的用法。
`SliverPersistentHeaderDelegate` 方法
- `build`: 此方法是每次需要构建头部时调用的。参数 `shrinkOffset` 表示头部已经收缩的距离,你可以根据这个值动态调整头部的样式或内容。`overlapsContent` 表示头部是否重叠在后续内容上,这个可以用来实现一些复杂的视觉效果。
- `shouldRebuild`: 这个方法用于确定当某些条件改变时,是否需要重新构建头部。通常情况下,如果头部的内容或布局依赖于外部状态,会返回 `true`。在简单场景中,直接返回 `true` 可以确保头部在每次状态变更时重新构建。
实际应用
通过 `SliverPersistentHeader`,你可以实现许多复杂的 UI 效果,如:
- 动态高度的应用栏:在滚动过程中,应用栏可以从全屏高度逐渐收缩到固定的高度。
- 粘性分段标题:在长列表中,分段标题可以在用户滚动到下一个分段时粘附到顶部,直到新的标题到达。
- 视觉转变效果:根据 `shrinkOffset` 的值,调整头部的透明度、颜色、甚至内容布局。
小结
`SliverPersistentHeader` 提供了一个非常灵活和强大的方式来管理滚动视图中的头部内容。通过结合 `SliverPersistentHeaderDelegate`,开发者可以完全自定义头部在滚动过程中的行为,满足各种复杂的 UI 需求。无论是简单的固定头部,还是复杂的动态变化效果,`SliverPersistentHeader` 都能提供支持。希望这些解释和示例能帮助你更好地理解和应用这个功能强大的组件!
SliverOverlapAbsorber
`SliverOverlapAbsorber` 是 Flutter 的一个高级布局组件,用于处理嵌套滚动视图中的重叠问题。在复杂的滚动布局中,比如有多个 `CustomScrollView` 或 `NestedScrollView` 嵌套时,可能会出现滚动内容重叠的情况。`SliverOverlapAbsorber` 旨在解决这些重叠问题,确保滚动视图能够正确显示和滚动。
主要功能
- 吸收重叠:`SliverOverlapAbsorber` 主要用于吸收滚动过程中产生的重叠区域,这样嵌套的滚动视图可以正确地处理其滚动内容。
- 协调滚动:在嵌套滚动中,帮助协调不同滚动视图之间的滚动行为。
使用场景
- 嵌套滚动视图:当有多个滚动视图嵌套在一起时,使用 `SliverOverlapAbsorber` 来处理滚动视图之间的重叠。
- `NestedScrollView`:通常与 `NestedScrollView` 一起使用,`NestedScrollView` 是一个专门用于处理嵌套滚动视图的组件。
典型结构
在典型的 `NestedScrollView` 使用中,`SliverOverlapAbsorber` 通常配合 `SliverOverlapInjector` 来使用:
- `SliverOverlapAbsorber`:放置在上层滚动视图,用于吸收重叠区域。
- `SliverOverlapInjector`:用于在下层滚动视图中重新插入吸收的重叠区域。
代码示例
以下是一个简单的例子,展示如何在 `NestedScrollView` 中使用 `SliverOverlapAbsorber` 和 `SliverOverlapInjector`:
import 'package:flutter/material.dart';class NestedScrollViewExample extends StatelessWidget {const NestedScrollViewExample({super.key});@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text("NestedScrollViewExample 示例")),body: NestedScrollView(headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {return <Widget>[const SliverAppBar(expandedHeight: 200.0,floating: false,pinned: true,flexibleSpace: FlexibleSpaceBar(title: Text("NestedScrollView Example"),),),SliverOverlapAbsorber(handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),sliver: const SliverAppBar(pinned: true,title: Text("Overlap Absorber"),),)];},body: Builder(builder: (BuildContext context) {return CustomScrollView(slivers: <Widget>[SliverOverlapInjector(handle:NestedScrollView.sliverOverlapAbsorberHandleFor(context),),SliverList(delegate: SliverChildBuilderDelegate((BuildContext context, int index) {return ListTile(title: Text('Item #$index'),);},childCount: 30,),),],);},),),);}
}
代码解析
`SliverOverlapAbsorber`:
- `handle`:`SliverOverlapAbsorber` 使用一个 `OverlapAbsorberHandle` 来管理重叠区域的状态。这个句柄是通过 `NestedScrollView.sliverOverlapAbsorberHandleFor(context)` 方法获取的。
- `sliver`:这个参数是吸收重叠的 `Sliver`,通常是一个 `SliverAppBar` 或其他 `Sliver` 组件。
`SliverOverlapInjector`:
- 用于在下层滚动视图中重新插入由 `SliverOverlapAbsorber` 吸收的重叠区域。
- 也使用相同的 `OverlapAbsorberHandle`,确保重叠区域在不同滚动视图之间正确协调。
如何工作
布局过程:
- `SliverOverlapAbsorber` 在上层滚动视图中捕捉重叠区域。这通常发生在 `NestedScrollView` 的 `headerSliverBuilder` 中。
- `SliverOverlapInjector` 在下层滚动视图中重新插入这些重叠区域,确保布局正确。这通常在 `NestedScrollView` 的 `body` 中。
滚动协调:
- 在嵌套滚动视图中,`SliverOverlapAbsorber` 和 `SliverOverlapInjector` 协同工作,确保滚动事件在不同层级的滚动视图中正确传播。
- 这对于实现复杂的滚动效果(如嵌套的 `ListView` 或 `GridView`)非常重要。
典型应用
- 复杂的应用栏:当应用栏中包含多个层级的滚动内容时,使用 `SliverOverlapAbsorber` 可以确保内容在滚动时不会被错误地覆盖或隐藏。
- 嵌套列表:在 `NestedScrollView` 中嵌套 `ListView` 或 `GridView` 时,使用这些组件可以管理滚动和布局之间的复杂交互。
小结
`SliverOverlapAbsorber` 和 `SliverOverlapInjector` 提供了一个强大的机制来处理复杂的嵌套滚动场景。在需要协调多个滚动视图并确保内容不会被错误覆盖时,这些工具非常有用。通过正确使用这些组件,你可以创建流畅且功能丰富的用户界面,适应各种复杂的布局需求。希望这个解释能够帮助你更好地理解和使用这些组件!
FloatingHeaderSnapConfiguration
`FloatingHeaderSnapConfiguration` 是 Flutter 中 `SliverAppBar` 的一个配置项,用于定义浮动头部在滚动视图中的行为。特别是在使用 `SliverAppBar` 实现滚动效果时,它用于控制头部的浮动和捕捉行为。
主要功能
- 浮动行为:当用户快速向上或向下滚动时,头部可以在滚动停止后自动浮动到一个捕捉位置。这种行为通常用于创建更自然的用户体验。
- 捕捉(Snap):在滚动停止时,头部会自动捕捉到一个预定义的位置,比如完全展开或完全折叠。
使用场景
- 用户体验优化:在应用中使用 `FloatingHeaderSnapConfiguration`,可以使得 `SliverAppBar` 在滚动时具有更平滑的捕捉效果,提升用户体验。
- 复杂的滚动布局:当你需要在复杂的滚动布局中确保头部在滚动停止时处于一个可预测的位置时,非常有用。
如何使用
`FloatingHeaderSnapConfiguration` 通常与 `SliverAppBar` 一起使用,特别是当 `SliverAppBar` 的 `floating` 属性设置为 `true` 时。为了让捕捉行为生效,`snap` 属性也需要设置为 `true`。
代码示例
以下是一个使用 `SliverAppBar` 实现浮动和捕捉行为的示例:
import 'package:flutter/material.dart';class SliverAppBarWithSnap extends StatelessWidget {const SliverAppBarWithSnap({super.key});@overrideWidget build(BuildContext context) {return Scaffold(body: CustomScrollView(slivers: <Widget>[SliverAppBar(title: const Text('Floating Header with Snap'),floating: true,snap: true,expandedHeight: 200.0,flexibleSpace: FlexibleSpaceBar(background: Image.asset("static/demo.png",fit: BoxFit.cover,)),),SliverList(delegate: SliverChildBuilderDelegate((BuildContext context, int index) {return ListTile(title: Text('Item #$index'),);},childCount: 50,))],),);}
}
代码解析
- `floating: true`:启用 `SliverAppBar` 的浮动行为,这意味着当用户向上滚动时,`AppBar` 会立即显示。
- `snap: true`:启用捕捉行为,确保当用户快速滚动并释放时,`AppBar` 会自动捕捉到完全展开或完全收缩的状态。
- `FlexibleSpaceBar`:用于定义 `SliverAppBar` 的可扩展背景区域。
注意事项
- 前提条件:`snap` 属性只能在 `floating` 为 `true` 时使用,因为捕捉行为依赖于 `AppBar` 的浮动特性。
- 用户体验:使用 `FloatingHeaderSnapConfiguration` 可以使滚动体验更加流畅自然,但需要根据应用的具体需求来决定是否启用此功能。
通过使用 `FloatingHeaderSnapConfiguration`,你可以在 Flutter 应用中实现一个具有浮动和捕捉行为的 `SliverAppBar`,从而提升用户的滚动体验。
SizedBox
`SizedBox` 是 Flutter 中一个非常常用的布局组件,用于在布局中创建具有特定宽度和高度的盒子。它可以用于添加特定的空白间距、限制子组件的尺寸,或者充当占位符。
主要功能
1.设置尺寸:`SizedBox` 可以指定其宽度和高度,以控制其在布局中的大小。
2.限制子组件尺寸:当 `SizedBox` 包含子组件时,它会限制子组件的尺寸为 `SizedBox` 的大小。
3.占位作用:在没有子组件的情况下,`SizedBox` 可以用作占位符,占据特定的空间。
4.间距:通过设置宽度或高度为零的 `SizedBox`,可以在布局中创建水平或垂直的间距。
使用场景
1.调整布局间距:在布局中插入 `SizedBox` 以创建一致的间距。
2.限制子组件尺寸:强制子组件在特定的宽度和高度内显示。
3.创建占位符:在布局中保留一个特定大小的空间,暂时不显示内容。
示例代码
以下是一些常见的 `SizedBox` 用法示例:
import 'package:flutter/material.dart';class SizedBoxExample extends StatelessWidget {const SizedBoxExample({super.key});@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text('SizedBox Example'),),body: Column(children: <Widget>[// 使用 SizedBox 设置固定的宽度和高度SizedBox(width: 100.0,height: 100.0,child: Container(color: Colors.blue,child: const Center(child: Text('100x100')),),),// 使用 SizedBox 作为间距const SizedBox(height: 20.0,),// 限制子组件宽度SizedBox(width: 200.0,child: Container(color: Colors.greenAccent,child: const Text('Width limited to 200',textAlign: TextAlign.center),),),// 使用 SizedBox 作为占位符const SizedBox(height: 50.0),const Text('Below is a 50px space'),],),);}
}
代码解析
- 尺寸设置:`SizedBox(width: 100.0, height: 100.0)` 创建一个 100x100 的盒子,其中的子组件会被限制在这个尺寸内。
- 间距:`SizedBox(height: 20.0)` 用于在两个组件之间创建 20 像素的垂直间距。
- 宽度限制:`SizedBox(width: 200.0)` 限制子组件的宽度为 200 像素,高度不受限制。
- 占位符:`SizedBox(height: 50.0)` 保留一个 50 像素的垂直空间。
注意事项
- 无子组件时尺寸:如果 `SizedBox` 没有子组件,它将只占据设置的宽度和高度。
- 无限尺寸:`SizedBox.expand()` 可以创建一个尽可能大的盒子,填充父组件允许的空间。
- 零尺寸:`SizedBox.shrink()` 可以创建一个尺寸为零的盒子,通常用于需要占位但不希望占据实际空间。
Expanded
`Expanded` 是 Flutter 中的一个布局小部件,通常用于 `Row`, `Column`, 或 `Flex` 布局中。它的主要作用是调整子组件的尺寸,以填充父组件中的可用空间。在使用 `Expanded` 时,子组件会在主轴方向上被拉伸,以占据尽可能多的空间。
主要功能
1.填充可用空间:`Expanded` 会让其子组件在布局的主轴方向上填充尽可能多的可用空间。
2.灵活分配空间:当多个 `Expanded` 小部件出现在同一个父布局中时,它们会根据各自的权重分配空间。
3.简化布局:通过使用 `Expanded`,可以轻松实现响应式布局,无需精确计算尺寸。
使用场景
- 创建响应式布局:在需要根据屏幕大小动态调整组件大小时,`Expanded` 非常有用。
- 均匀分布空间:在 `Row` 或 `Column` 中需要均匀分布子组件时。
- 占据剩余空间:当需要一个组件占据父布局中所有剩余的可用空间时。
示例代码
以下是一些使用 `Expanded` 的示例:
import 'package:flutter/material.dart';class ExpandedExampleDemo extends StatelessWidget {const ExpandedExampleDemo({super.key});@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text('Expanded Example'),),body: Column(children: <Widget>[Container(color: Colors.redAccent,height: 100.0,child: const Center(child: Text('Fixed Height')),),Expanded(child: Container(color: Colors.blue,child: const Center(child: Text('Expanded')))),Container(color: Colors.green,height: 100.0,child: const Center(child: Text('Fixed Height')),),],),);}
}
代码解析
- 固定大小的组件:顶部和底部的 `Container` 组件具有固定的高度(100.0),它们不会被 `Expanded` 影响。
- `Expanded` 的应用:中间的 `Container` 被 `Expanded` 包裹,这意味着它将占据父 `Column` 中所有剩余的可用空间。
多个 `Expanded` 的情况
当有多个 `Expanded` 小部件时,它们会均匀分配父布局中的可用空间,或者根据 `flex` 参数的值按比例分配:
Column(children: <Widget>[Expanded(flex: 1,child: Container(color: Colors.blue, child: Text('1 Flex')),),Expanded(flex: 2,child: Container(color: Colors.green, child: Text('2 Flex')),),],
)
解释
- `flex` 属性:用于定义 `Expanded` 在主轴方向上占据的比例空间。上例中,绿色容器将占据两倍于蓝色容器的空间。
注意事项
- 只能在 `Flex` 布局中使用:`Expanded` 只能用在 `Row`, `Column`, 或 `Flex` 中,因为它们在布局时考虑主轴方向。
- 交叉轴尺寸:`Expanded` 只会影响其子组件在主轴方向上的尺寸。对于交叉轴(即 `Row` 中的垂直方向和 `Column` 中的水平方向),你需要明确地设置尺寸属性(如 `width` 或 `height`)来控制。
- 配合 `Spacer` 使用:`Spacer` 是一个特殊的 `Expanded`,用于在布局中创建空白空间。它可以帮助你在不需要具体组件内容的地方,灵活调整组件之间的距离。
示例代码:`Spacer` 的使用
import 'package:flutter/material.dart';class ExpandedWithSpacerExample extends StatelessWidget {const ExpandedWithSpacerExample({super.key});@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text('Expanded with Spacer Example'),),body: Row(children: <Widget>[Container(width: 100.0,color: Colors.red,child: const Center(child: Text('Left')),),const Spacer(), // 通过 Spacer 创建灵活的空白空间Container(width: 100.0,color: Colors.green,child: const Center(child: Text('Right')),),],),);}}
代码解析
- `Spacer` 的作用:在 `Row` 中使用 `Spacer` 可以在两个容器之间创建一个弹性空白区域,使得它们在布局中保持一定的距离。`Spacer` 本质上是一个 `Expanded`,但不包含任何子组件。
注意事项
- 与 `Flexible` 的区别:`Flexible` 也是一个用于调整子组件尺寸的布局小部件。与 `Expanded` 不同的是,`Flexible` 可以让子组件在不需要填满所有可用空间时根据需要调整大小。`Expanded` 是一个 `Flexible` 的快捷实现,其 `flexFit` 属性默认为 `FlexFit.tight`。
- 布局性能:使用 `Expanded` 和 `Spacer` 可以帮助优化布局性能,因为它们简化了空间分配逻辑,减少了手动调整和计算的需求。
总结
`Expanded` 和 `Spacer` 是 Flutter 布局系统中强大且灵活的工具。通过这些小部件,你可以轻松创建响应式布局,确保组件在不同屏幕尺寸和方向上都能合理地显示和排列。了解如何使用这些工具将帮助你更高效地设计和实现复杂的用户界面。
列表滑动过程控件停靠效果代码学习
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'dart:math' as math;
import 'package:flutter/material.dart' as W;class SliverListDemoPage2222 extends StatefulWidget {const SliverListDemoPage2222({super.key});@override_MySliverListDemoPageState createState() {return _MySliverListDemoPageState();}
}class _MySliverListDemoPageState extends State<SliverListDemoPage2222>with SingleTickerProviderStateMixin {int listCount = 30;@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text("SliverListDemoPage"),),body: NestedScrollView(physics: const AlwaysScrollableScrollPhysics(),headerSliverBuilder: _sliverBuilder,body: CustomScrollView(slivers: [W.Builder(builder: (context) {return SliverOverlapInjector(handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context));},),SliverList(delegate: SliverChildBuilderDelegate((context, index) {return Card(child: Container(height: 60,padding: const EdgeInsets.only(left: 10),alignment: Alignment.centerLeft,child: Text("Item $index"),),);},childCount: 100,),)],),),);}List<Widget> _sliverBuilder(BuildContext context, bool innerBoxIsScrolled) {return <Widget>[SliverPersistentHeader(delegate: MySliverHeaderDelegate(maxHeight: 200,minHeight: 200,vSync: this,snapConfig: FloatingHeaderSnapConfiguration(curve: Curves.bounceInOut,duration: const Duration(milliseconds: 10),),child: Container(color: Colors.redAccent,)),),SliverOverlapAbsorber(handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),sliver: SliverPersistentHeader(pinned: true,delegate: MySliverHeaderDelegate(maxHeight: 60,minHeight: 60,changeSize: true,vSync: this,snapConfig: FloatingHeaderSnapConfiguration(curve: Curves.bounceInOut,duration: const Duration(milliseconds: 10),),builder: (BuildContext context, double shrinkOffset,bool overlapsContent) {///根据数值计算偏差var lr = 10 - shrinkOffset / 60 * 10;return SizedBox.expand(child: Padding(padding: EdgeInsets.only(bottom: 10, left: lr, right: lr, top: lr),child: Row(mainAxisSize: MainAxisSize.max,children: <Widget>[Expanded(child: Container(alignment: Alignment.center,color: Colors.orangeAccent,child: TextButton(onPressed: () {setState(() {listCount = 30;});},child: const Text("按键1"),),),),Expanded(child: Container(alignment: Alignment.center,color: Colors.orangeAccent,child: TextButton(onPressed: () {setState(() {listCount = 4;});},child: const Text("按键2"),),),),],),),);})),)];}
}class MySliverHeaderDelegate extends SliverPersistentHeaderDelegate {MySliverHeaderDelegate({required this.minHeight,required this.maxHeight,required this.snapConfig,required this.vSync,this.child,this.builder,this.changeSize = false});final double minHeight;final double maxHeight;final Widget? child;final Builder? builder;final bool changeSize;final TickerProvider vSync;final FloatingHeaderSnapConfiguration snapConfig;AnimationController? animationController;@overrideTickerProvider get vsync => vSync;@overrideWidget build(BuildContext context, double shrinkOffset, bool overlapsContent) {if (builder != null) {return builder!(context, shrinkOffset, overlapsContent);}return child!;}@overridedouble get maxExtent => math.max(maxHeight, minHeight);@overridedouble get minExtent => minHeight;@overridebool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {return true;}@overrideFloatingHeaderSnapConfiguration get snapConfiguration => snapConfig;
}typedef Builder = Widget Function(BuildContext context, double shrinkOffset, bool overlapsContent);
typedef
在 Flutter 中,`typedef` 是用于定义函数类型别名的关键字。它可以让你的代码更具可读性和可维护性,尤其是在需要传递复杂函数作为参数时。你提到的这个 `typedef` 定义了一个名为 `Builder` 的函数类型别名。
`typedef` 解析
typedef Builder = Widget Function(BuildContext context, double shrinkOffset, bool overlapsContent);
这个 `typedef` 定义了一个函数类型别名 `Builder`,它代表一个函数,该函数:
- 返回类型:`Widget`,表示此函数返回一个 Flutter 小部件。
- 参数:
`BuildContext context`: 这是 Flutter 中常见的参数,用来获取树中位置相关的信息,比如主题、方向、媒体查询等。
`double shrinkOffset`: 这个参数通常用于描述某种滚动或动画的偏移量。它是一个双精度浮点数,可能用于表示滚动视图中已滚动的距离。
`bool overlapsContent`: 这是一个布尔值,通常用来指示某个组件是否与其他内容重叠。例如,在实现自定义滚动效果时,可能需要知道当前组件是否覆盖了其他内容。
使用场景
这个 `typedef` 常用于需要根据滚动或其他动态变化来构建 UI 的场景。例如,在实现自定义的 `Sliver` 组件或其他需要根据滚动偏移量调整显示效果的组件时,这种类型的 `Builder` 函数非常有用。
示例用法
假设你在创建一个自定义的 `Sliver` 组件,需要根据滚动偏移量和是否重叠来动态构建其内容,你可能会这样使用:
import 'package:flutter/material.dart';class CustomSliver extends StatelessWidget {final Builder builder;CustomSliver({required this.builder});@overrideWidget build(BuildContext context) {return SliverPersistentHeader(delegate: _CustomSliverDelegate(builder),);}
}
class _CustomSliverDelegate extends SliverPersistentHeaderDelegate {final Builder builder;_CustomSliverDelegate(this.builder);@overrideWidget build(BuildContext context, double shrinkOffset, bool overlapsContent) {// 浣跨敤鎻愪緵鐨?Builder 鏋勫缓 Widgetreturn builder(context, shrinkOffset, overlapsContent);}@overridedouble get maxExtent => 200.0;@overridedouble get minExtent => 100.0;@overridebool shouldRebuild(covariant _CustomSliverDelegate oldDelegate) {return true;}
}
typedef Builder = Widget Function(BuildContext context, double shrinkOffset, bool overlapsContent);
解释
- `CustomSliver`:这是一个简单的自定义组件,它接受一个 `Builder` 类型的参数,用于构建其内容。
- `_CustomSliverDelegate`:这是一个 `SliverPersistentHeaderDelegate` 的实现,用于构建一个持久化的头部。它使用传入的 `builder` 来构建实际的 UI。
通过这种方式,你可以在创建组件时传入不同的 `Builder` 函数,以便在不同的滚动状态下构建不同的 UI,这为你提供了很大的灵活性和可扩展性。
TickerProvider
在 Flutter 中,`TickerProvider` 是一个接口,用于提供 `Ticker` 对象。`Ticker` 是一个能发出信号以驱动动画的对象。它的工作原理类似于一个时钟,每当屏幕刷新时(通常是每秒60次),它就会调用一个回调函数。这在 Flutter 中的动画系统中是非常重要的,它帮助动画在每一帧中更新状态。
Ticker 的作用
`Ticker` 在 Flutter 中的主要作用是:
1.驱动动画:通过提供每秒钟多次的时钟滴答来驱动动画。
2.同步帧速率:确保动画与设备的屏幕刷新率同步。
3.管理动画生命周期:在需要时,可以暂停、恢复或停止动画。
TickerProvider 的使用场景
`TickerProvider` 通常与 `AnimationController` 一起使用,因为 `AnimationController` 需要一个 `Ticker` 来驱动动画的更新。
常见的 TickerProvider 实现
1.`SingleTickerProviderStateMixin`:用于一个组件中只需要一个 `Ticker` 的场景。通常与 `StatefulWidget` 配合使用。
2.`TickerProviderStateMixin`:用于一个组件需要多个 `Ticker` 的场景。如果你有多个动画需要在同一组件中管理,可以使用这个 mixin。
示例代码
以下是如何在 `StatefulWidget` 中使用 `SingleTickerProviderStateMixin` 来驱动一个简单动画的示例:
import 'package:flutter/material.dart';
class MyAnimatedWidget extends StatefulWidget {@override_MyAnimatedWidgetState createState() => _MyAnimatedWidgetState();
}
class _MyAnimatedWidgetState extends State<MyAnimatedWidget> with SingleTickerProviderStateMixin {late AnimationController _controller;@overridevoid initState() {super.initState();// 初始化 AnimationController_controller = AnimationController(duration: const Duration(seconds: 2),vsync: this, // 提供 Ticker)..repeat(); // 循环动画}@overridevoid dispose() {_controller.dispose(); // 销毁 AnimationControllersuper.dispose();}@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text('TickerProvider Example')),body: Center(child: RotationTransition(turns: _controller, // 使用 AnimationControllerchild: Container(width: 100.0,height: 100.0,color: Colors.blue,),),),);}
}
代码解析
- `SingleTickerProviderStateMixin`:通过在 `State` 类中混入这个 mixin,当前类就成为了一个 `TickerProvider`,可以为 `AnimationController` 提供 `Ticker`。
- `AnimationController`:负责管理动画的生命周期,包括启动、停止和反向移动等。需要 `TickerProvider` 来同步动画。
- `vsync: this`:`vsync` 参数要求一个 `TickerProvider`,用于减少不必要的动画帧以提高性能。
注意事项
- 管理生命周期:确保在 `dispose` 方法中调用 `_controller.dispose()` 来释放资源,防止内存泄漏。
- 多个动画:如果需要管理多个动画,考虑使用 `TickerProviderStateMixin` 代替 `SingleTickerProviderStateMixin`。
相关文章:
Flutter 实现 列表滑动过程控件停靠效果 学习
实现一个 Flutter 应用程序,使用 Sliver 系列组件来创建具有滚动效果的复杂布局。使用 NestedScrollView 和 SliverPersistentHeader 来实现固定和动态的头部效果,以及一个可滚动的列表。 前置知识点学习 SingleTickerProviderStateMixin SingleTicker…...
STM32+WIFI获取网络时间+8位数码管显示+0.96OLED显
资料下载地址:STM32WIFI获取网络时间8位数码管显示0.96OLED 1、项目介绍 主控芯片STM32C8T6 接线:串口1:PA9 PA10 OELD :PB6 PB7 数码管使用:MAX7219 8位数码管 Max7219_pinCLK PAout(5) Max7219_pinC…...
Kivy App开发之UX控件Slider滑块
在app中可能会调节如音量,亮度等,可以使用Slider来实现,该控件调用方便,兼容性好,滑动平稳。在一些参数设置中,也可以用来调整数值。 支持水平和垂直方向,可以设置默认值,最小及最大值。 使用方法,需用引入Slider类,通过Slider类生成一个滑块并设置相关的样式后,再…...
STM32学习(十)
I2C模块内部结构 I2C(Inter-Integrated Circuit)模块是一种由Philips公司开发的二线式串行总线协议,用于短距离通信,允许多个设备共享相同的总线。 硬件连接简单:I2C通信仅需要两条总线,即SCL&…...
何为“正则表达式”!
详细解释: ^:在JSON的正则表达式中,^表示匹配输入字符串的开始位置。这意味着正则表达式将从字符串的开头开始进行匹配,确保整个字符串符合后续的模式要求。例如,对于字符串"3.14",正则表达式会…...
创建Java项目,并添加MyBatis包和驱动包
一 : Mybatis和jsp使用上,只有Dao层有区别 Mybatis 使用方法: 测试类的7步骤 1.读取核心配置文件 2.构建sql会话工厂 3.开启sql会话 4.获取mapper接口 5.调用相对应的增删改查方法 6.打印 7.关闭回话 /*** 用户列表* throws IOException*/Testpublic void roleList() throws IO…...
目标检测文献阅读-DETR:使用Transformer进行端到端目标检测
目录 摘要 Abstract 1 引言 2 DETR结构 2.1 Backbone 2.2 Encoder 2.3 Decoder 2.4 FFN 3 目标检测集合预测损失 3.1 二分图匹配损失 3.2 损失函数 总结 摘要 本周阅读的论文题目是《End-to-End Object Detection with Transformers》(使用Transformer进行端到端目…...
C#里对已经存在的文件进行压缩生成ZIP文件
先要对目录下所有文件获取到: private List<string> GetXDFiles(string dirPath){//获取目录中的所有文件string suffix = "*.txt"; // 要查找的文件后缀var files = Directory.GetFiles(dirPath, suffix, SearchOption.TopDirectoryOnly).ToList();return fi…...
单片机软件定时器V4.0
单片机软件定时器V4.0 用于单片机定时执行任务等,比如LED GPIO等定时控制,内置前后台工作模式 头文件有使用例子 #ifndef __SORFTIME_APP_H #define __SORFTIME_APP_H#ifdef __cplusplus extern "C" { #endif#include <stdint.h>// #…...
【嵌入式硬件】嵌入式显示屏接口
数字显示串行接口(Digital Display Serial Interface) SPI 不过多赘述。 I2C-bus interface 不过多赘述 MIPI DSI MIPI (Mobile Industry Processor Interface) Alliance, DSI (Display Serial Interface) 一般用于移动设备,下面是接口…...
vuedraggable 选项介绍
vuedraggable 是基于 SortableJS 的 Vue 组件,提供了丰富的选项来定制拖拽行为。以下是 vuedraggable 常用的选项和它们的详细说明: 常用选项介绍 group 配置拖拽分组。多个列表可以共享同一个分组,允许它们之间的项目互相拖拽。 group: { na…...
OpenAI CEO 奥特曼发长文《反思》
OpenAI CEO 奥特曼发长文《反思》 --- 引言:从 ChatGPT 到 AGI 的探索 ChatGPT 诞生仅一个多月,如今我们已经过渡到可以进行复杂推理的下一代模型。新年让人们陷入反思,我想分享一些个人想法,谈谈它迄今为止的发展,…...
Appium(一)--- 环境搭建
一、Android自动化环境搭建 1、JDK 必须1.8及以上(1) 安装:默认安装(2) 环境变量配置新建JAVA_HOME:安装路径新建CLASSPath%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar在path中增加:%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;(3) 验证…...
如何查看服务器上的MySQL/Redis等系统服务状态和列表
如果呢你知道系统服务名称,要看状态很简单: systemctl status server-name 比如 systemctl status nginxsystemctl status redis # 等 这是一个nginx的示例: 那问题是 当你不知道服务名称时该怎么办。举个例子,比如mysql在启动…...
多模态论文笔记——U-ViT
大家好,这里是好评笔记,公主号:Goodnote,专栏文章私信限时Free。本文详细介绍U-ViT的模型架构和实验细节,虽然没有后续的DiT在AIGC领域火爆,但为后来的研究奠定了基础,但其开创性的探索值得学习…...
08-1_队列的理论讲解
08-1_队列的理论讲解 队列概念理解 队列就是一个传送带,就是一个流水线 工人放入的数据, 放在传送带上,送给消费者, 消费者第一次拿到的数据,就是工人第一次放上的数据(类比队列,先进先出) freertos队列应用思考 队列对于消费者(取数据): (1)当队列没有数据, 消费者需要进入…...
HTML基础入门——简单网页页面
目录 一,网上转账电子账单 编辑 1,所利用到的标签 2,代码编写 3,运行结果 二,李白诗词 1,所用到的标签 2,照片的编辑 3,代码编写 4,运行结果 一,网…...
Proteus-8086调试汇编格式的一点心得
这阵子开始做汇编的微机实验(微机原理与接口技术题解及实验指导,吴宁版本13章),中间出了挺多问题,解决后记录下。 先上电路图 用子电路来仿真发现仿真的时候子电路这块根本没有高低电平输出,只好把子电路拿…...
Python入门教程 —— 多任务
1.线程 1.1.线程安全问题 线程访问全局变量 import threading g_num = 0 def test(n):global g_numfor x in range(n):g_num += xg_num -= xprint(g_num)if __name__ == __main__:t1 = threading.Thread(target=test, args=(10,))t2 = threading.Thread(target=test, args=(…...
【笔记】算法记录
1、求一个数的素因子(试除法) // 获取一个数的所有素因子 set<int> getPrimeFactors(int num) {set<int> primeFactors;for (int i 2; i * i < num; i) {while (num % i 0) {primeFactors.insert(i);num / i;}}if (num > 1) {prime…...
2024年1月4日蜻蜓hr人才招聘系统v1.1.7更新-正式版发布-客户端源代码开源发布供学习-本产品完成上线正式版-修复多个bug-优雅草果果|小无
2024年1月4日蜻蜓hr人才招聘系统v1.1.7更新-正式版发布-客户端源代码开源发布供学习-本产品完成上线正式版-修复多个bug-优雅草果果|小无 前端代码开源库 关于开源说明:企业服务-招聘信息管理系统-前端uniapp-系统前端开放源代码仅供学习-优雅草科技-目前优雅草科…...
【网络】计算机网络的分类 局域网 (LAN) 广域网 (WAN) 城域网 (MAN)个域网(PAN)
局域网是通过路由器接入广域网的 分布范围 局域网Local Area Network:小范围覆盖,速度高,延迟低(办公室,家庭,校园,网络) 广域网Wide Area Network 大范围覆盖,速度相对低,延迟高…...
DeepSeek:性能强劲的开源模型
deepseek 全新系列模型 DeepSeek-V3 首个版本上线并同步开源。登录官网 chat.deepseek.com 即可与最新版 V3 模型对话。 性能对齐海外领军闭源模型 DeepSeek-V3 为自研 MoE 模型,671B 参数,激活 37B,在 14.8T token 上进行了预训练。 论…...
MySql 通过 LOAD DATA INFILE 导入大量数据
背景:要在本地Mysql导入几十万-百万条数据非常耗时,从网上找到通过load data infile 方式可以快速导入大量数据,就动手尝试了。结果就是不太适合复杂的格式数据导入,比如字段多,数据格式多等,但对于简单的表…...
Linux系统中解决端口占用问题
在日常的 Linux 系统管理和开发过程中,端口占用是一个常见且令人头疼的问题。无论是部署新服务、调试应用程序,还是进行系统维护,遇到端口被占用都可能导致服务无法正常启动或运行。本文将详细介绍在 Linux 系统中如何识别和解决端口占用问题…...
两种方式实现Kepware与PLC之间的心跳检测
两种方式实现Kepware与PLC之间的心跳检测 实现Kepware与PLC之间的心跳检测1.OPCUA 外挂程序2.Kepware Advanced Tag 实现Kepware与PLC之间的心跳检测 1.OPCUA 外挂程序 这是通过上位程序来触发心跳的一种机制,在C#中,可以利用OPC UAOPCAutodll的方式…...
使用DockerCompose部署服务
由于格式或图片解析问题,为了更好的阅读体验,可前往 阅读原文 以前我们总是用命令管理每个容器的启动、停止等等,若有多个容器时可能还存在启动优先级的问题,那就要等到指定的容器启动后再去启动另一个容器,对于整体的…...
第P5周-Pytorch实现运动鞋品牌识别
🍨 本文为🔗365天深度学习训练营 中的学习记录博客🍖 原作者:K同学啊 具体实现 (一)环境 语言环境:Python 3.10 编 译 器: PyCharm 框 架: Pytorch (二)具体步骤 时间…...
react-quill 富文本组件编写和应用
index.tsx文件 import React, { useRef, useState } from react; import { Modal, Button } from antd; import RichEditor from ./RichEditor;const AnchorTouchHistory: React.FC () > {const editorRef useRef<any>(null);const [isModalVisible, setIsModalVis…...
因泰立科技激光车检器,高速公路的精准流量统计专家
高速公路自由流门架激光车检器,专为解决高速公路交通管理中的各种挑战而设计。该产品采用先进的激光测量技术,能够对动态通过的车辆进行高速动态扫描,通过测量物体表面点的反射距离,迅速换算成三维空间坐标,实现对车辆…...
windows中,git bash 使用conda命令
1、首先在Anaconda的安装路径如/Anaconda3/Scripts下,打开git bash窗口,然后输入下面的命令。 ./conda init bash 运行之后,会在用户目录下面生成.bash_profile文件,文件内容如下: # >>> conda initialize…...
《繁星路》V1.8.3(Build16632266)官方中文学习版
《繁星路》官方中文版https://pan.xunlei.com/s/VODae2_2Z3QyMF02I5y321uHA1?pwdqgsh# 作为一款星际模拟游戏,完美融合了硬科幻元素与基地建设玩法,体验改造行星的恢弘与壮阔。化身人工意识AMI,遵照基本指示推进火星改造的各项工作…...
vscode 无法使用npm, cmd命令行窗口可以正常执行
解决方法: 执行命令获得命令的位置 get-command npm 得到如下 然后删除或者修改 npm.ps1文件 让其不能使用就行。然后重启vscode即可。 pnpm 同理即可 另外加速源 国内镜像源(淘宝): npm config set registry https://regist…...
html + css 顶部滚动通知栏示例
前言 在现代网页设计中,一个吸引人的顶部滚动通知栏不仅能够有效传达重要信息,还能提升用户体验。通过使用HTML和CSS,我们可以创建既美观又功能强大的组件,这些组件可以在不影响网站整体性能的情况下提供实时更新或紧急通知。 本…...
阿里mod_asr3.0集成webrtc静音算法
alibabacloud-nls-cpp-sdk-master 先到阿里官网下载nls库的源代码,编译生成对应的库文件和头文件。 我编译的放到了以下目录。 /home/jp/2025/alibabacloud-nls-cpp-sdk-master/build/install/NlsSdk3.X_LINUX/include/ /home/jp/2025/alibabacloud-nls-cpp-sdk-…...
【MySQL】表的基本查询
文章目录 1. 插入 insert1.1 单行插入2.2 多行插入2.3 插入 否则更新2.4 替换 2. 查找 select2.1 select 列2.2 where 条件2.3 order by 排序2.4 筛选分页结果 3. 更新 update4. 删除 delete5. 截断 truncate6. 插入查询结果7. 聚合函数8. group by子句 我们经常说数据库就是CR…...
2025-01-07日SSH钓鱼日志
今天发现了挖矿程序部署脚本,可针对性排查。 时间来源类型详情20:16:08146.190.50.206:36694CHANNEL_REQUEST通道:1,类型:exec(command:uname -a)20:16:08146.190.50.206:36694OPEN_CHANNEL通道:1,类型&am…...
C++语言的计算机基础
C语言的计算机基础 引言 C是一种通用的编程语言,由Bjarne Stroustrup于1979年开发。它在C语言的基础上增加了面向对象的特性,使得程序设计变得更加灵活和高效。C广泛应用于系统软件、游戏开发、实时仿真等领域。本文将深入探讨C语言的基础知识…...
怎么把word试题转成excel?
在教育行业、学校管理以及在线学习平台中,试题库的高效管理是一项核心任务。许多教育工作者和系统开发人员常常面临将 Word 中的试题批量导入 Excel 的需求。本文将详细介绍如何快速将试题从 Word 转换为 Excel,帮助您轻松解决繁琐的数据整理问题&#x…...
在线商城系统设计与开发-代码(源码+数据库+文档)
亲测完美运行带论文:文末获取源码 文章目录 项目简介(论文摘要)运行视频包含的文件列表(含论文)前端运行截图后端运行截图 项目简介(论文摘要) 现代经济快节奏发展以及不断完善升级的信息化技术…...
HCIE-day10-ISIS
ISIS ISIS(Intermediate System-to-Intermediate System)中间系统到中间系统,属于IGP(内部网关协议);是一种链路状态协议,使用最短路径优先SPF算法进行路由计算,与ospf协议有很多相…...
解决SSH连接时遇到的“远程主机身份验证已更改 (WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!)”警告
文章目录 引言警告信息解读造成原因解决方案 引言 在使用SSH协议进行远程服务器管理的过程中,可能会遇到这样的情况:当尝试通过SSH连接到某个远程主机时,终端突然弹出一条警告信息,提示“远程主机身份验证已更改”。 警告信息解…...
C语言的循环实现
C语言的循环实现 C语言是一种功能强大且广泛使用的编程语言,其语法简单易懂,适用于系统软件、嵌入式系统和高性能应用程序等多个领域。在C语言中,控制程序执行流程的基本结构之一就是循环。循环允许程序在满足某个条件的情况下重复执行一段代…...
如何使用vue引入three.js
在 Vue.js 项目中引入和使用 Three.js 是一个常见的需求,Three.js 是一个用于在浏览器中创建和显示动画 3D 计算机图形的 JavaScript 库。以下是一个基本的示例,展示如何在 Vue 项目中引入和使用 Three.js。 1. 创建 Vue 项目 如果你还没有一个 Vue 项…...
【C++】深入理解迭代器(Iterator)
博客主页: [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C 文章目录 💯前言💯什么是迭代器?迭代器与指针的比较 💯std::string 中的迭代器示例代码与图示分析运行结果:图示说明: 小提示 💯正…...
微信小程序实现长按录音,点击播放等功能,CSS实现语音录制动画效果
有一个需求需要在微信小程序上实现一个长按时进行语音录制,录制时间最大为60秒,录制完成后,可点击播放,播放时再次点击停止播放,可以反复录制,新录制的语音把之前的语音覆盖掉,也可以主动长按删…...
QPS和TPS 的区别是什么?QPS 大了会有什么问题,怎么解决?
QPS 和 TPS 的区别是什么?QPS 大了会有什么问题,怎么解决? QPS(Queries Per Second)和 TPS(Transactions Per Second)都是衡量系统性能的重要指标,尤其是在 Web 服务、数据库和分布…...
小程序开发-页面事件之上拉触底实战案例
🎥 作者简介: CSDN\阿里云\腾讯云\华为云开发社区优质创作者,专注分享大数据、Python、数据库、人工智能等领域的优质内容 🌸个人主页: 长风清留杨的博客 🍃形式准则: 无论成就大小,…...
[读书日志]从零开始学习Chisel 第六篇:Scala面向对象编程——特质(敏捷硬件开发语言Chisel与数字系统设计)
3.4特质 3.4.1什么是特质 特质使用trait开头,它与单例对象很像,两者都不能有输入参数,但单例对象是具体的,特质是抽象的。两者都不能用new实例化,类,单例对象,特质三者内部都可以包含字段和方…...
导航技术的分类
导航技术可以根据不同的分类标准进行划分,以下是从不同角度对导航技术的分类: 一、按导航信息获取原理分类 无线电导航:利用无线电波的传播特性来测定运动体的位置、速度等导航参数。常见的无线电导航系统包括罗兰-C、奥米加、台卡等。卫星…...