dart 如何在Flutter中的SliverAppBar上覆盖可滚动的Sliver?

nkkqxpd9  于 2024-01-04  发布在  Flutter
关注(0)|答案(4)|浏览(275)

我有一个CustomScrollView与一个SliverAppBar和一些长条。我想覆盖一些图形上的SliverAppBar,并有他们滚动与其余的名单。
下面是我使用的代码:

  1. import 'package:flutter/material.dart';
  2. void main() async {
  3. runApp(const MyApp());
  4. }
  5. class MyApp extends StatelessWidget {
  6. const MyApp({super.key});
  7. @override
  8. Widget build(BuildContext context) {
  9. return const MaterialApp(
  10. home: SliverTest(),
  11. );
  12. }
  13. }
  14. class SliverTest extends StatefulWidget {
  15. const SliverTest({super.key});
  16. @override
  17. State<SliverTest> createState() => _SliverTestState();
  18. }
  19. class _SliverTestState extends State<SliverTest> {
  20. @override
  21. Widget build(BuildContext context) {
  22. return MaterialApp(
  23. home: Scaffold(
  24. body: CustomScrollView(
  25. slivers: [
  26. SliverAppBar(
  27. expandedHeight: 350.0,
  28. floating: false,
  29. pinned: true,
  30. flexibleSpace: FlexibleSpaceBar(
  31. background: Container(
  32. color: Colors.yellow,
  33. child: const Center(child: Text('My Parallax Image!')),
  34. ),
  35. ),
  36. ),
  37. SliverToBoxAdapter(
  38. child: Container(
  39. height: 100,
  40. color: Colors.blueAccent,
  41. child: Stack(
  42. children: [
  43. Align(
  44. alignment: const Alignment(0.0, -2.0),
  45. child: Container(
  46. width: 50,
  47. height: 50,
  48. decoration: const BoxDecoration(
  49. shape: BoxShape.circle,
  50. color: Colors.red,
  51. ),
  52. child: const Center(child: Text('Red')),
  53. ),
  54. ),
  55. ],
  56. ),
  57. ),
  58. ),
  59. SliverList(
  60. delegate: SliverChildBuilderDelegate(
  61. (BuildContext context, int index) {
  62. return Container(
  63. height: 50,
  64. color: Colors.teal[100 * ((index % 8) + 1)],
  65. child: Center(child: Text('Item #$index')),
  66. );
  67. },
  68. childCount: 20,
  69. ),
  70. ),
  71. ],
  72. ),
  73. ),
  74. );
  75. }
  76. }

字符集
在这个设置中,我想要红色的圆圈(当前已剪裁)绘制在SliverAppBar的黄色部分顶部(请注意,黄色部分只是一些具有视差效果的实际图像的简单占位符,并且使用纯黄色仅仅是为了简单起见)我将红色圆圈放在一个长条内,因为我希望当用户与可滚动区域交互时,它能与列表的其余部分一起沿着。
有谁能提供一个简单的例子来说明如何在Flutter中实现这一点吗?我愿意接受任何其他使用slivers的解决方案,因为它们为我的真实的应用程序的其他部分提供了巨大的便利。否则,我知道我可能会在不使用slivers的情况下重新创建它。任何帮助都将不胜感激。提前感谢!

uqdfh47h

uqdfh47h1#

这里你有另一个没有使用滚动条/滚动条的示例,只是在玩大小:)。
测试结果:
x1c 0d1x的数据
代码:

  1. class SliverTest extends StatefulWidget {
  2. const SliverTest({super.key});
  3. @override
  4. State<SliverTest> createState() => _SliverTestState();
  5. }
  6. class _SliverTestState extends State<SliverTest> {
  7. @override
  8. Widget build(BuildContext context) {
  9. const circleSize = 50.0;
  10. const expandedHeight = 350.0;
  11. const headerColor = Colors.yellow;
  12. return MaterialApp(
  13. home: Scaffold(
  14. backgroundColor: headerColor,
  15. body: CustomScrollView(
  16. slivers: [
  17. SliverAppBar(
  18. expandedHeight: expandedHeight - circleSize / 2,
  19. floating: false,
  20. pinned: true,
  21. flexibleSpace: FlexibleSpaceBar(
  22. background: Container(
  23. color: headerColor,
  24. child: const Center(child: Text('Yellow')),
  25. ),
  26. ),
  27. ),
  28. SliverToBoxAdapter(
  29. child: Container(
  30. color: Colors.blueAccent,
  31. height: 200, //any size
  32. child: Stack(
  33. children: [
  34. Positioned(
  35. top: 0,
  36. left: 0,
  37. right: 0,
  38. height: circleSize / 2,
  39. child: Container(
  40. color: headerColor,
  41. ),
  42. ),
  43. Align(
  44. alignment: Alignment.topCenter,
  45. child: Container(
  46. width: circleSize,
  47. height: circleSize,
  48. decoration: const BoxDecoration(
  49. shape: BoxShape.circle,
  50. color: Colors.red,
  51. ),
  52. child: const Center(child: Text('Red')),
  53. ),
  54. ),
  55. ],
  56. ),
  57. ),
  58. ),
  59. SliverList(
  60. delegate: SliverChildBuilderDelegate(
  61. (BuildContext context, int index) {
  62. return Container(
  63. height: 50,
  64. color: Colors.teal[100 * ((index % 8) + 1)],
  65. child: Center(child: Text('Item #$index')),
  66. );
  67. },
  68. childCount: 20,
  69. ),
  70. ),
  71. ],
  72. ),
  73. ),
  74. );
  75. }
  76. }

字符集

更新

好吧,基于你最新的评论,我不得不更深入:),现在它的工作符合你最后的要求(但也更复杂)。
结果如下:



代码:

  1. class SliverTest extends StatefulWidget {
  2. const SliverTest({super.key});
  3. @override
  4. State<SliverTest> createState() => _SliverTestState();
  5. }
  6. class _SliverTestState extends State<SliverTest> {
  7. final _scrollController = ScrollController();
  8. final _layerLink = LayerLink();
  9. final _expandedHeight = 350.0;
  10. final _circleSize = 50.0;
  11. double _visiblePercent = 1.0;
  12. OverlayEntry? _overlayEntry;
  13. void _onListen() {
  14. final total = _scrollController.offset + kToolbarHeight + _circleSize / 2;
  15. final difference = total - _expandedHeight;
  16. _visiblePercent =
  17. ((_circleSize - difference).clamp(0.0, _circleSize).toDouble() /
  18. _circleSize);
  19. _overlayEntry?.markNeedsBuild();
  20. }
  21. @override
  22. void initState() {
  23. _scrollController.addListener(_onListen);
  24. WidgetsBinding.instance.addPostFrameCallback((_) {
  25. _showCircularItem();
  26. });
  27. super.initState();
  28. }
  29. @override
  30. void dispose() {
  31. _scrollController.removeListener(_onListen);
  32. _scrollController.dispose();
  33. _overlayEntry?.remove();
  34. super.dispose();
  35. }
  36. void _showCircularItem() {
  37. _overlayEntry = OverlayEntry(
  38. builder: (BuildContext context) {
  39. return Positioned(
  40. top: 0,
  41. left: 0,
  42. right: 0,
  43. child: CompositedTransformFollower(
  44. link: _layerLink,
  45. offset: Offset(0.0, -_circleSize / 2),
  46. child: Material(
  47. color: Colors.transparent,
  48. child: ClipRect(
  49. clipper: _MyClipper(
  50. visiblePercent: _visiblePercent,
  51. ),
  52. child: Container(
  53. width: _circleSize,
  54. height: _circleSize,
  55. decoration: const BoxDecoration(
  56. shape: BoxShape.circle,
  57. color: Colors.red,
  58. ),
  59. child: const Center(child: Text('Red')),
  60. ),
  61. ),
  62. ),
  63. ),
  64. );
  65. },
  66. );
  67. Overlay.of(context).insert(_overlayEntry!);
  68. }
  69. @override
  70. Widget build(BuildContext context) {
  71. return MaterialApp(
  72. home: Scaffold(
  73. body: CustomScrollView(
  74. controller: _scrollController,
  75. slivers: [
  76. SliverAppBar(
  77. expandedHeight: _expandedHeight,
  78. floating: false,
  79. pinned: true,
  80. flexibleSpace: FlexibleSpaceBar(
  81. background: Image.network(
  82. 'https://t4.ftcdn.net/jpg/05/49/86/39/360_F_549863991_6yPKI08MG7JiZX83tMHlhDtd6XLFAMce.jpg',
  83. fit: BoxFit.cover,
  84. ),
  85. ),
  86. ),
  87. SliverToBoxAdapter(
  88. child: CompositedTransformTarget(
  89. link: _layerLink,
  90. child: Container(
  91. color: Colors.greenAccent,
  92. height: 200, //any size
  93. ),
  94. ),
  95. ),
  96. SliverList(
  97. delegate: SliverChildBuilderDelegate(
  98. (BuildContext context, int index) {
  99. return Container(
  100. height: 50,
  101. color: Colors.teal[100 * ((index % 8) + 1)],
  102. child: Center(child: Text('Item #$index')),
  103. );
  104. },
  105. childCount: 20,
  106. ),
  107. ),
  108. ],
  109. ),
  110. ),
  111. );
  112. }
  113. }
  114. class _MyClipper extends CustomClipper<Rect> {
  115. _MyClipper({
  116. required this.visiblePercent,
  117. });
  118. final double visiblePercent;
  119. @override
  120. Rect getClip(Size size) {
  121. return Rect.fromLTRB(
  122. 0.0,
  123. size.height * (1.0 - visiblePercent),
  124. size.width,
  125. size.height,
  126. );
  127. }
  128. @override
  129. bool shouldReclip(_MyClipper oldClipper) {
  130. return visiblePercent != oldClipper.visiblePercent;
  131. }
  132. }

展开查看全部
8fq7wneg

8fq7wneg2#

您可以考虑使用Stack widget将图形覆盖在SliverAppBar上。然而,挑战在于使图形随CustomScrollView一起沿着。您可以尝试调整图形的位置以响应CustomScrollView的滚动偏移。
使用NotificationListener监听滚动事件。根据滚动偏移量计算图形的位置。使用Stack并在用户滚动时动态调整图形的位置。

  1. class _SliverTestState extends State<SliverTest> {
  2. double _offset = 0.0;
  3. @override
  4. Widget build(BuildContext context) {
  5. return MaterialApp(
  6. home: Scaffold(
  7. body: NotificationListener<ScrollNotification>(
  8. onNotification: (ScrollNotification scrollInfo) {
  9. setState(() {
  10. _offset = scrollInfo.metrics.pixels;
  11. });
  12. return true;
  13. },
  14. child: Stack(
  15. children: [
  16. CustomScrollView(
  17. slivers: [
  18. SliverAppBar(
  19. expandedHeight: 350.0,
  20. floating: false,
  21. pinned: true,
  22. flexibleSpace: FlexibleSpaceBar(
  23. background: Container(
  24. color: Colors.yellow,
  25. child: const Center(child: Text('Yellow')),
  26. ),
  27. ),
  28. ),
  29. SliverToBoxAdapter(
  30. child: Container(
  31. height: 100,
  32. color: Colors.blueAccent,
  33. ),
  34. ),
  35. // other slivers
  36. ],
  37. ),
  38. Positioned(
  39. top: 350 - _offset, // Adjust position based on scroll offset
  40. left: MediaQuery.of(context).size.width / 2 - 25,
  41. child: Container(
  42. width: 50,
  43. height: 50,
  44. decoration: const BoxDecoration(
  45. shape: BoxShape.circle,
  46. color: Colors.red,
  47. ),
  48. child: const Center(child: Text('Red')),
  49. ),
  50. ),
  51. ],
  52. ),
  53. ),
  54. ),
  55. );
  56. }
  57. }

字符集
您可以看到NotificationListener侦听滚动事件并更新_offset变量。Stack小部件将红色圆圈覆盖在CustomScrollView上。Positioned小部件根据滚动偏移量调整红色圆圈的位置。
看看这是否会将红色圆圈放在SliverAppBar的顶部,并使其与列表的其余部分一起滚动。
有一些警告。
例如,由于图形(红色圆圈)实际上不是任何长条的一部分,它显然没有参与过度滚动效果,保持其固定位置,因此表现出一些设计缺陷,因此我在OP中强调。
另外,在检查它之后,我意识到红色圆圈需要在SliverAppBar后面,我坚持要覆盖在上面。有没有简单的解决方法?
为了满足让红色圆圈沿着滚动并出现在SliverAppBar后面的要求,您可以尝试将CustomScrollViewSliverPersistentHeader一起使用。SliverPersistentHeader将允许您创建一个自定义标题,其行为类似于SliverAppBar,但对其内容和布局有更多的控制。
因此,创建一个包含黄色背景和红色圆圈的SliverPersistentHeader。调整自定义标题中红色圆圈的位置,使其最初出现在所需位置,但仍与其余内容一起滚动。通过使用SliverPersistentHeader,红色圆圈在滚动时自然应放置在SliverAppBar的后面。

  1. class _SliverTestState extends State<SliverTest> {
  2. @override
  3. Widget build(BuildContext context) {
  4. return MaterialApp(
  5. home: Scaffold(
  6. body: CustomScrollView(
  7. slivers: [
  8. SliverAppBar(
  9. expandedHeight: 350.0,
  10. floating: false,
  11. pinned: true,
  12. flexibleSpace: FlexibleSpaceBar(
  13. title: Text('App Bar'),
  14. ),
  15. ),
  16. SliverPersistentHeader(
  17. pinned: true,
  18. delegate: _CustomHeaderDelegate(
  19. minHeight: 100.0,
  20. maxHeight: 350.0,
  21. child: Container(
  22. color: Colors.yellow,
  23. alignment: Alignment.topCenter,
  24. child: Container(
  25. width: 50,
  26. height: 50,
  27. decoration: BoxDecoration(
  28. shape: BoxShape.circle,
  29. color: Colors.red,
  30. ),
  31. child: Center(child: Text('Red')),
  32. ),
  33. ),
  34. ),
  35. ),
  36. // other slivers
  37. ],
  38. ),
  39. ),
  40. );
  41. }
  42. }
  43. class _CustomHeaderDelegate extends SliverPersistentHeaderDelegate {
  44. final double minHeight;
  45. final double maxHeight;
  46. final Widget child;
  47. _CustomHeaderDelegate({
  48. required this.minHeight,
  49. required this.maxHeight,
  50. required this.child,
  51. });
  52. @override
  53. double get minExtent => minHeight;
  54. @override
  55. double get maxExtent => maxHeight;
  56. @override
  57. Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
  58. return SizedBox.expand(child: child);
  59. }
  60. @override
  61. bool shouldRebuild(_CustomHeaderDelegate oldDelegate) {
  62. return maxHeight != oldDelegate.maxHeight ||
  63. minHeight != oldDelegate.minHeight ||
  64. child != oldDelegate.child;
  65. }
  66. }


SliverAppBar应该保持固定在顶部,自定义标题在它下面向上滚动。
检查是否看到红色圆圈与列表的其余部分一起滚动,并停留在SliverAppBar后面。

展开查看全部
zwghvu4y

zwghvu4y3#

这是根据我的理解为您的问题的解决方案。

  1. class SliverTest extends StatefulWidget {
  2. const SliverTest({super.key});
  3. @override
  4. State<SliverTest> createState() => _SliverTestState();
  5. }
  6. class _SliverTestState extends State<SliverTest> {
  7. late ScrollController _scrollController;
  8. @override
  9. void initState() {
  10. _scrollController = ScrollController();
  11. super.initState();
  12. }
  13. @override
  14. void dispose() {
  15. _scrollController.dispose();
  16. super.dispose();
  17. }
  18. @override
  19. Widget build(BuildContext context) {
  20. return MaterialApp(
  21. home: Scaffold(
  22. body: CustomScrollView(
  23. controller: _scrollController,
  24. shrinkWrap: true,
  25. slivers: [
  26. SliverAppBar.large(
  27. pinned: true,
  28. floating: false,
  29. stretch: true,
  30. automaticallyImplyLeading: false,
  31. expandedHeight: 350.0,
  32. backgroundColor: Colors.purple,
  33. title: Center(
  34. child: Container(
  35. width: 50,
  36. height: 50,
  37. decoration: const BoxDecoration(
  38. shape: BoxShape.circle,
  39. color: Colors.red,
  40. ),
  41. child: const Center(child: Text('Red')),
  42. ),
  43. ),
  44. flexibleSpace: FlexibleSpaceBar(
  45. background: Container(
  46. color: Colors.yellow,
  47. child: const Center(child: Text('Yellow')),
  48. ),
  49. ),
  50. ),
  51. SliverToBoxAdapter(
  52. child: Container(
  53. // height: 100,
  54. color: Colors.blueAccent,
  55. child: Stack(
  56. children: [
  57. SingleChildScrollView(
  58. child: Column(
  59. children: [
  60. for (int i = 0; i < 20; i++) ...[
  61. Container(
  62. height: 50,
  63. color: Colors.teal[100 * ((i % 8) + 1)],
  64. child: Center(child: Text('Item #$i')),
  65. )
  66. ],
  67. ],
  68. ),
  69. ),
  70. ],
  71. ),
  72. ),
  73. ),
  74. // SliverList(
  75. // delegate: SliverChildBuilderDelegate(
  76. // (BuildContext context, int index) {
  77. // return Container(
  78. // height: 50,
  79. // color: Colors.teal[100 * ((index % 8) + 1)],
  80. // child: Center(child: Text('Item #$index')),
  81. // );
  82. // },
  83. // childCount: 20,
  84. // ),
  85. // ),
  86. ],
  87. ),
  88. ),
  89. );
  90. }
  91. }

字符集

展开查看全部
mwkjh3gx

mwkjh3gx4#

要让appBar不遮挡圆圈:您可以将SliverAppBar更改为SliverPersistentHeader,并在delegate部分自定义您的标题和红圈,这样红圈将始终位于顶部,不再被appBar遮挡。
如何根据列表的滚动或者appBar的滚动自定义红圈:如果你想按列表滚动,你只需要在列表中添加ScrollController,并监听scrollController。如果你想改变appBar的滚动,那么在构建函数的delegate部分,有一个选项shrinkOffset可用,你可以计算这个。

[更新2023/12/08]
source

插图

x1c 0d1x的数据

相关问题