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; // 补间动画

  @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`。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

3D立体无人机夜间表演技术详解

3D立体无人机夜间表演技术是一种结合了无人机技术、灯光艺术和计算机编程的创新表演形式。以下是该技术的详细解析&#xff1a; 一、技术基础 1. 无人机技术&#xff1a; 无人机通常采用四旋翼设计&#xff0c;具有强大的飞行控制能力&#xff0c;可以实现前飞、后飞、悬停、…

MATLAB深度学习实战文字识别

文章目录 前言视频演示效果1.DB文字定位环境配置安装教程与资源说明1.1 DB概述1.2 DB算法原理1.2.1 整体框架1.2.2 特征提取网络Resnet1.2.3 自适应阈值1.2.4 文字区域标注生成1.2.5 DB文字定位模型训练 2.CRNN文字识别2.1 CRNN概述2.2 CRNN原理2.2.1 CRNN网络架构实现2.2.2 CN…

H2数据库在单元测试中的应用

H2数据库特征 用比较简洁的话来介绍h2数据库&#xff0c;就是一款轻量级的内存数据库&#xff0c;支持标准的SQL语法和JDBC API&#xff0c;工业领域中&#xff0c;一般会使用h2来进行单元测试。 这里贴一下h2数据库的主要特征 Very fast database engineOpen sourceWritten…

Android 10.0 授权app获取cpu温度和电池温度功能实现

1.前言 在10.0的系统定制化开发中&#xff0c;在开发某些产品的老化应用的时候&#xff0c;需要app获取cpu温度和电池 温度等功能&#xff0c;有些产品带温度传感器&#xff0c;大部分的产品都不包含温度传感器&#xff0c;所以就需要读取 sys下的相关节点来获取相关温度值 2.…

IDEA 撤销 merge 操作(详解)

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

WD5105同步降压转换器:9.2V-95V宽电压输入,4.5A大电流输出,95%高效率,多重保护功能

概述 • WD5105同步降压转换器 • 封装形式&#xff1a;QFN-20封装 • 应用场景&#xff1a;适用于车载充电器、电动车仪表、电信基站电源、电源适配器等 性能特点 • 输入电压范围&#xff1a;9.2V至95V • 输出电流&#xff1a;可提供4.5A连续负载电流 • 效率&#xff1a;高…

【C++】B2108 图像模糊处理

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C 文章目录 &#x1f4af;前言&#x1f4af;题目描述题目内容输入格式输出格式示例输入&#xff1a;输出&#xff1a; &#x1f4af;题目分析问题拆解 &#x1f4af;我的做法代码实现代码分析 &#x1f4af;老师的做法…

怎么把word试题转成excel?

在教育行业、学校管理以及在线学习平台中&#xff0c;试题库的高效管理是一项核心任务。许多教育工作者和系统开发人员常常面临将 Word 中的试题批量导入 Excel 的需求。本文将详细介绍如何快速将试题从 Word 转换为 Excel&#xff0c;帮助您轻松解决繁琐的数据整理问题&#x…

minibatch时,损失如何记录

目录 minibatch时&#xff0c;损失如何记录 报错&#xff1a;UnboundLocalError: local variable coef referenced before assignment是什么回事 未溢出则不会报错&#xff0c;可以完整滴运行完成 indent 缩进 炫酷技能&#xff1a;一遍运行&#xff0c;一遍画图 实例1 解释…

Linux : Linux环境开发工具vim / gcc / makefile / gdb / git的使用

Linux环境开发工具的使用 一、操作系统的生态二、程序下载安装&#xff08;一&#xff09;程序安装方式&#xff08;二&#xff09;包管理器 yum / apt 运行原理 三、文本编辑器 vim&#xff08;一&#xff09;认识vim 下的操作模式&#xff08;二&#xff09;命令模式常用的快…

国产游戏崛起,燕云十六移动端1.9上线,ToDesk云电脑先开玩

游戏爱好者的利好消息出新了&#xff01;网易大型武侠仙游《燕云十六声》正式官宣&#xff0c;移动端要在1月9日正式上线了&#xff01;你期待手游版的燕云吗&#xff1f;不妨评论区留言说说你的看法。小编分别花了几个小时在台式机电脑和手机上都试了下&#xff0c;欣赏画面还…

力扣刷题:数组OJ篇(下)

大家好&#xff0c;这里是小编的博客频道 小编的博客&#xff1a;就爱学编程 很高兴在CSDN这个大家庭与大家相识&#xff0c;希望能在这里与大家共同进步&#xff0c;共同收获更好的自己&#xff01;&#xff01;&#xff01; 目录 1.轮转数组&#xff08;1&#xff09;题目描述…

有序数据中插入不确定数据保证数据插入的位置顺序正确排序

解决有序数据中插入不确定数据保证数据插入的位置顺序正确排序 前言 java 数据库中存储自增id 有序的数据&#xff0c; 前端页面基于 id 5和 6 之间新增一条数据&#xff0c;在 id 6 和 7之间新增 2条&#xff0c;或者更复杂的场景&#xff0c;后台接口如何保存数据使得页面数…

python无需验证码免登录12306抢票 --selenium(2)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 [TOC](python无需验证码免登录12306抢票 --selenium(2)) 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 就在刚刚我抢的票&#xff1a;2025年1月8日…

DNS协议漏洞利用实验_hust计算机网络安全实验

文章目录 计算机网络安全实验 DNS协议漏洞利用实验 docker使用 建立实验环境docker常用指令 一些注意事项设置本地 DNS 服务器 配置用户计算机设置本地DNS服务器在本地 DNS 服务器中建一个区域 修改主机文件&#xff08;可略&#xff09;netwox实施DNS的用户响应欺骗攻击netwo…

基于MP157AAA的I2C练习

练习要求&#xff1a; 通过I2C分别实现与芯片si7006(获取湿度、温度)和芯片ap3216(获取环境光照强度)的通讯&#xff1b; 1、运行效果 2、分析ap3216如何获取光照强度 2.1、需要操作的寄存器 通过分析手册&#xff0c;需要操作以下寄存器: 0x00&#xff1a;系统配置 0x0C&…

【Linux】深入理解文件系统(超详细)

目录 一.磁盘 1-1 磁盘、服务器、机柜、机房 &#x1f4cc;补充&#xff1a; &#x1f4cc;通常网络中用高低电平&#xff0c;磁盘中用磁化方向来表示。以下是具体说明&#xff1a; &#x1f4cc;如果有一块磁盘要进行销毁该怎么办&#xff1f; 1-2 磁盘存储结构 ​编辑…

网络安全图谱以及溯源算法

​ 本文提出了一种网络攻击溯源框架&#xff0c;以及一种网络安全知识图谱&#xff0c;该图由六个部分组成&#xff0c;G <H&#xff0c;V&#xff0c;A&#xff0c;E&#xff0c;L&#xff0c;S&#xff0c;R>。 1|11.知识图 ​ 网络知识图由六个部分组成&#xff0c…

《Spring Framework实战》7:4.1.2.容器概述

欢迎观看《Spring Framework实战》视频教程 容器概述 该接口表示 Spring IoC 容器&#xff0c;并负责实例化、配置和组装 bean。 容器在组件上获取其指令&#xff0c;以实例化、配置和 通过读取配置元数据进行汇编。可以表示配置元数据 作为带注释的组件类、具有工厂方法的配置…

学生公寓技术规格书如何编写?

学生公寓限电柜的技术规格书主要包括以下内容‌&#xff1a; ‌用电计量计费‌&#xff1a;限电柜可以通过计算机售电管理系统进行用电计量计费&#xff0c;学生需要预交电费&#xff0c;系统会自动将数据传给控电柜和配电箱&#xff0c;对宿舍的电量进行累减计量‌。 ‌时间控…