Flutter 实现 列表滑动过程控件停靠效果 学习

news2025/1/7 15:06:15

实现一个 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; // 补间动画

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("SingleTickerProviderStateMixin 示例")),
      body: Center(
        child: Container(
          width: _animation.value, // 动态更新宽度
          height: _animation.value, // 动态更新高度
          color: Colors.blue,
        ),
      ),
    );
  }

  @override
  void dispose() {
    // 销毁动画控制器以释放资源
    _controller.dispose();
    super.dispose();
  }

  @override
  void 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});

  @override
  Widget 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,
  });

  @override
  Widget build(
      BuildContext context, double shrinkOffset, bool overlapsContent) {
    return SizedBox.expand(child: child);
  }

  @override
  double get maxExtent => maxHeight;

  @override
  double get minExtent => minHeight;

  @override
  bool 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});

  @override
  Widget 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});

  @override
  Widget 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});

  @override
  Widget 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});

  @override
  Widget 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});

  @override
  Widget 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;

  @override
  Widget 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;

  @override
  TickerProvider get vsync => vSync;

  @override
  Widget build(
      BuildContext context, double shrinkOffset, bool overlapsContent) {
    if (builder != null) {
      return builder!(context, shrinkOffset, overlapsContent);
    }
    return child!;
  }

  @override
  double get maxExtent => math.max(maxHeight, minHeight);

  @override
  double get minExtent => minHeight;

  @override
  bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
    return true;
  }

  @override
  FloatingHeaderSnapConfiguration 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});
  @override
  Widget build(BuildContext context) {
    return SliverPersistentHeader(
      delegate: _CustomSliverDelegate(builder),
    );
  }
}
class _CustomSliverDelegate extends SliverPersistentHeaderDelegate {
  final Builder builder;
  _CustomSliverDelegate(this.builder);
  @override
  Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
    // 浣跨敤鎻愪緵鐨?Builder 鏋勫缓 Widget
    return builder(context, shrinkOffset, overlapsContent);
  }
  @override
  double get maxExtent => 200.0;
  @override
  double get minExtent => 100.0;
  @override
  bool 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;
  @override
  void initState() {
    super.initState();
    // 初始化 AnimationController
    _controller = AnimationController(
      duration: const Duration(seconds: 2),
      vsync: this, // 提供 Ticker
    )..repeat(); // 循环动画
  }
  @override
  void dispose() {
    _controller.dispose(); // 销毁 AnimationController
    super.dispose();
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('TickerProvider Example')),
      body: Center(
        child: RotationTransition(
          turns: _controller, // 使用 AnimationController
          child: 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`。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2271829.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2025-01-04 Unity插件 YodaSheet1 —— 插件介绍

文章目录 1 介绍2 工作原理2.1 ScriptableObject -> YadeSheetData2.2 YadeDatabase 存储多个 YadeSheetData 3 用途4 缺点5 推荐 1 介绍 ​ Yade 提供类似于 Excel 或者 Google Sheets 的表格编辑器&#xff0c;可以轻松地在 Unity 编辑器中 编辑&#xff0c;搜索&#xf…

用 C++ 创建控制台计算器

本文内容 先决条件创建应用项目验证新应用是否生成并运行编辑代码 显示另外 5 个 C 程序员通常从在命令行上运行的“Hello, world!”应用程序开始。 你将以本文为起点&#xff0c;逐步进阶&#xff0c;加深学习难度&#xff1a;计算器应用。 先决条件 在 Visual Studio 中…

IDEA 撤销 merge 操作(详解)

作为一个开发者&#xff0c;我们都知道Git是一个非常重要的版本控制工具&#xff0c;尤其是在协作开发的过程中。然而&#xff0c;在使用Git的过程中难免会踩一些坑&#xff0c;今天我来给大家分享一个我曾经遇到的问题&#xff1a;在使用IDEA中进行merge操作后如何撤销错误的合…

限时特惠,香港服务器,低至53元/年

家人们谁懂啊&#xff01;香港服务器这价格简直逆天了&#xff0c;居然比内地的还便宜&#xff01;就拿阿里云来说&#xff0c;人家最低配置的服务器&#xff0c;价格都很难做到这么亲民。 最低配的就不说了&#xff0c;2 核 4G 的配置&#xff0c;应对日常业务稳稳当当&#x…

EF Core配置及使用

Entity Framework Core是微软官方的ORM框架。 ORM&#xff1a;Object Relational Mapping。让开发者用对象操作的形式操作关系数据库。 EF Core是对于底层ADO.NET Core的封装&#xff0c;因此ADO.NET Core支持的数据库不一定被EF Core支持。 代码创建数据库Code First 建实…

GPT分区 使用parted标准分区划分,以及相邻分区扩容

parted 是一个功能强大的命令行工具&#xff0c;用于创建和管理磁盘分区表和分区。它支持多种分区表类型&#xff0c;如 MBR&#xff08;msdos&#xff09;、GPT&#xff08;GUID Partition Table&#xff09;等&#xff0c;并且可以处理大容量磁盘。parted 提供了一个交互式界…

【mybatis-plus问题集锦系列】使用mybatis实现数据的基础增删改查

使用mybatis实现数据的基础增删改查,简单的增删改查操作方法步骤 代码实现 pom.xml <dependencies><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>3.0.…

tlias项目实战笔记

一个小项目写了一个多月&#xff0c;在考试周穿插&#xff0c;终于能有时间来写个小总结了&#xff0c;废话少说&#xff0c;我们直接来步入正题。 一、项目开发规范 1.开发风格Restful 案例是基于当前最为主流的前后端分离模式进行开发。 在前后端分离的开发模式中&#xff…

Arduino Uno简介与使用方法

目录 一、Arduino Uno概述 1. 硬件特性 2. 开发环境 二、Arduino Uno的基本使用方法 1. 硬件连接 2. 软件编程 三、Arduino Uno编程基础 1. 基本语法 2. 常用函数 四、Arduino Uno应用举例 1. LED闪烁 2. 温度检测 3. 超声波测距 五、Arduino Uno的扩展与应用 1…

go 模拟TCP粘包和拆包,及解决方法

1. 什么是 TCP 粘包与拆包&#xff1f; 粘包&#xff08;Sticky Packet&#xff09; 粘包是指在发送多个小的数据包时&#xff0c;接收端会将这些数据包合并成一个数据包接收。由于 TCP 是面向流的协议&#xff0c;它并不会在每次数据发送时附加边界信息。所以当多个数据包按顺…

新能源电动汽车动力电池技术

新能源电动汽车动力电池技术是新能源汽车发展的核心之一&#xff0c;以下是动力电池技术的一些关键方面&#xff1a; 技术进展 能量密度提升&#xff1a;近年来&#xff0c;动力电池的能量密度有了显著提升&#xff0c;从2010年的100Wh/kg提高到2024年的300Wh/kg。能量密度的…

仓颉笔记——windows11安装启用cangjie语言,并使用vscode编写“你好,世界”

2025年1月1日第一篇日记&#xff0c;大家新年好。 去年就大致看了一下&#xff0c;感觉还不错&#xff0c;但一直没上手&#xff0c;这次借着元旦的晚上安装了一下&#xff0c;今年正式开动&#xff0c;公司众多的应用国产化正等着~~ 第一步&#xff1a;准备 官网&#xff1a;…

JVM对象内存结构

1对象内存结构说明 注意&#xff1a; 如果对象为数组对象&#xff0c;在对象头后面有4字节存储数组长度&#xff1b; 1.1对象头 对象头分为Mark Word和Class Pointer两部分&#xff1b; Mark Word&#xff1a;对象基础信息 32位操作系统中占4字节&#xff0c;64位操作系统中占8…

doris:基于 Arrow Flight SQL 的高速数据传输链路

Doris 基于 Arrow Flight SQL 协议实现了高速数据链路&#xff0c;支持多种语言使用 SQL 从 Doris 高速读取大批量数据。 用途​ 从 Doris 加载大批量数据到其他组件&#xff0c;如 Python/Java/Spark/Flink&#xff0c;可以使用基于 Arrow Flight SQL 的 ADBC/JDBC 替代过去…

算法题(25):只出现一次的数字(三)

审题&#xff1a; 该题中有两个元素只出现一次并且其他元素都出现两次&#xff0c;需要返回这两个只出现一次的数&#xff0c;并且不要求返回顺序 思路: 由于对空间复杂度有要求&#xff0c;我们这里不考虑哈希表。我们采用位运算的方法解题 方法&#xff1a;位运算 首先&#…

HTML——75. 内联框架

<!DOCTYPE html> <html><head><meta charset"UTF-8"><title>内联框架</title><style type"text/css">iframe{width: 100%;height: 500px;}</style></head><body><!--iframe元素会创建包含…

MotionCtrl: A Unified and Flexible Motion Controller for Video Generation 论文解读

目录 一、概述 二、相关工作 三、前置知识 1、LVDM Introduction 2、LVDM Method 3、LVDM for Short Video Generation 4、Hierarchical LVDM for Long Video Generation 5、训练细节 6、推理过程 四、MotionCtrl 1、CMCM 2、OMCM 3、训练策略 五、实验 一、概述…

vue2实现excel文件预览

一、插件 通过xlsx插件解析excel数据&#xff0c;对解析后的html组件进行渲染展示。 npm install xlsx 二、完整代码 <template><!-- excel文件预览 --><divelement-loading-text"拼命加载中"element-loading-spinner"el-icon-loading"…

uniapp:跳转第三方地图

1.跳转第三方高德地图 //跳转地图 toMap(item){uni.navigateTo({url: (window.location.href https://uri.amap.com/navigation?to${item.lng},${item.lat},${item.shopName}&modecar&policy1&srchttps://gawl.gazhcs.com/wap/index.html&callnative0)}) },…

纯前端实现将pdf转为图片(插件pdfjs)

需求来源 预览简历功能在移动端&#xff0c;由于用了一层iframe把这个功能嵌套在了app端&#xff0c;再用一个iframe来预览&#xff0c;只有ios能看到&#xff0c;安卓就不支持&#xff0c;查了很多资料和插件&#xff0c;原理基本上都是用iframe实现的。最终转换思路&#xf…