字节笔记本

2026年3月22日

Flutter SnapList - 列表滑动 Snapping 效果组件

本文介绍 flutter_snaplist,一个轻量级的 Flutter 库,用于创建具有吸附效果的可滚动列表视图。在日常的移动应用开发中,我们经常需要实现类似轮播图、卡片选择器等交互效果——用户滑动列表后,列表会自动吸附到最近的某一项上。flutter_snaplist 正是为解决这一需求而生的,它提供了简洁的 API 和流畅的手势检测,让开发者能够轻松实现各种吸附式列表。

项目简介

flutter_snaplist 是一个由开发者 ariedov 维护的开源 Flutter 库,已获得 Awesome Flutter 认证。它的核心理念是"small and cozy"——小巧而舒适。该库通过 SnapList 这个 StatefulWidget,封装了复杂的吸附计算逻辑,让开发者只需关注界面渲染和数据绑定。

与 Flutter 自带的 PageViewListView 相比,flutter_snaplist 的最大区别在于:它支持不同尺寸的子项,并且能够对每个子项进行精确的吸附计算。这意味着你可以在同一个列表中放置大小不一的卡片或组件,而吸附行为依然能够正确工作。

该项目在 GitHub 上积极欢迎社区贡献,Issue 和 Pull Request 都受到鼓励。

核心特性

  • 吸附式滚动:用户滑动列表后,列表会自动吸附到最近的一项,提供流畅的交互体验
  • 动态尺寸支持:支持列表中每个子项具有不同的大小,吸附计算会根据实际尺寸进行精确调整
  • 进度回调:提供 centernextprogress 三个关键数据,让开发者可以实时感知滚动和吸附状态
  • 灵活的方向控制:默认水平滚动,也支持垂直方向滚动
  • 自定义分隔符:通过 separatorProvider 可以自定义子项之间的间距
  • 手势检测驱动:基于手势检测实现滑动,确保用户交互的流畅性

技术栈

  • Flutter/Dart:基于 Flutter 框架开发,纯 Dart 实现
  • 手势检测:使用 Flutter 原生的手势系统进行滑动检测和吸附计算
  • 发布平台:通过 pub.dev 发布,版本号为 0.1.8

安装指南

在 Flutter 项目的 pubspec.yaml 文件中添加依赖:

yaml
dependencies:
  snaplist: ^0.1.8

然后在需要使用的 Dart 文件中导入:

dart
import 'package:snaplist/snaplist.dart';

安装完成后,运行 flutter pub get 命令获取依赖包。

快速开始

SnapList 组件有 4 个必填参数,理解它们是使用该库的关键:

  • sizeProvider:提供每个子项的尺寸。库会根据此尺寸将每个子项包裹在指定大小的 SizedBox 中,这是吸附计算正确工作的基础
  • separatorProvider:提供子项之间的分隔间距大小,与 sizeProvider 类似
  • builder:构建子项的函数,类似于 Flutter 中常见的 builder 模式,会传入 context、当前索引和附加数据
  • count:列表子项的总数

使用示例

基础用法

以下是一个最基本的 SnapList 使用示例:

dart
import 'package:flutter/material.dart';
import 'package:snaplist/snaplist.dart';

class SnapListExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('SnapList 示例')),
      body: SnapList(
        sizeProvider: (index, data) => Size(100.0, 160.0),
        separatorProvider: (index, data) => Size(10.0, 0.0),
        builder: (context, index, data) => Container(
          color: Colors.blue.withOpacity(0.5 + (index % 5) * 0.1),
          child: Center(
            child: Text('Item $index'),
          ),
        ),
        count: 10,
      ),
    );
  }
}

利用进度数据实现动态效果

SnapList 的 builder 和 provider 函数都会接收一个 data 参数,其中包含三个字段:

  • center:当前显示在中心位置的子项索引
  • next:用户正在滑向的目标位置索引,空闲时为 -1
  • progress:滚动和吸附的进度值,范围为 0 到 100

利用这些数据,可以实现丰富的动态视觉效果:

dart
SnapList(
  sizeProvider: (index, data) => Size(120.0, 160.0),
  separatorProvider: (index, data) => Size(12.0, 0.0),
  count: 20,
  builder: (context, index, data) {
    // 根据是否为中心项来调整透明度和缩放
    final isCenter = index == data.center;
    final progress = data.progress / 100.0;

    return Transform.scale(
      scale: isCenter ? 1.0 : 0.85,
      child: Opacity(
        opacity: isCenter ? 1.0 : 0.7,
        child: Container(
          decoration: BoxDecoration(
            color: Colors.primaries[index % Colors.primaries.length],
            borderRadius: BorderRadius.circular(12),
            boxShadow: [
              BoxShadow(
                color: Colors.black.withOpacity(isCenter ? 0.3 : 0.1),
                blurRadius: isCenter ? 12 : 4,
              ),
            ],
          ),
          child: Center(
            child: Text(
              'Card $index',
              style: TextStyle(
                color: Colors.white,
                fontSize: isCenter ? 18 : 14,
                fontWeight: isCenter ? FontWeight.bold : FontWeight.normal,
              ),
            ),
          ),
        ),
      ),
    );
  },
)

垂直方向滚动

通过设置 axis 参数为 Axis.vertical,可以切换为垂直方向的吸附列表:

dart
SnapList(
  axis: Axis.vertical,
  sizeProvider: (index, data) => Size(300.0, 80.0),
  separatorProvider: (index, data) => Size(0.0, 8.0),
  builder: (context, index, data) => Container(
    color: Colors.teal.withOpacity(0.6 + (index % 3) * 0.15),
    child: Center(child: Text('Vertical Item $index')),
  ),
  count: 15,
)

动态尺寸子项

SnapList 支持列表中每个子项具有不同的大小,只需在 sizeProvider 中根据索引返回不同尺寸即可:

dart
SnapList(
  sizeProvider: (index, data) {
    // 每隔 3 个项目设置一个较大尺寸
    if (index % 3 == 0) {
      return Size(160.0, 200.0);
    }
    return Size(100.0, 140.0);
  },
  separatorProvider: (index, data) => Size(8.0, 0.0),
  builder: (context, index, data) => Container(
    color: Colors.orange.withOpacity(0.5 + (index % 4) * 0.12),
    child: Center(child: Text('Item $index')),
  ),
  count: 12,
)

注意事项

  1. 手势冲突:flutter_snaplist 使用手势检测来实现滑动吸附,因此需要确保列表内部子组件的手势操作不会与之冲突,以获得最佳的用户体验
  2. 性能考量:虽然库本身足够轻量,但在子项数量较大且每个子项较为复杂时,建议结合 builder 中的 progress 数据进行条件渲染优化
  3. 尺寸准确性sizeProvider 返回的尺寸必须与实际渲染的子项尺寸一致,否则吸附效果会出现偏差

项目链接

分享: