SnappingSheet2 是一个 Flutter 包,可实现可拖动的底部弹出面板,并能垂直和水平方向上自动吸附到预设位置。它提供流畅的动画效果和可自定义的吸附点,非常适合用于模态框、筛选器或交互式面板。该组件支持 Android、iOS、Web、macOS、Windows 和 Linux 多个平台,确保在不同设备上行为一致。通过流畅的手势操作和响应式布局,显著提升用户体验。
适合场景
交互式底部面板
筛选或设置抽屉
带有拖动打开的模态对话框
可自定义的滑动界面
多方向的表格导航
主要特点
吸附到多个垂直/水平位置
平滑拖拽动画
跨平台支持
可配置的吸附点
基于手势的交互
适合对象
开发动态底部表单的开发者
需要基于手势的模态框的应用
需要灵活面板定位的 UI
针对多个平台的项目
使用交互式覆盖层设计系统
使用建议
需要仔细处理布局约束
可能需要对复杂内容进行性能调优
仅限垂直/水平对齐
不适用于全屏叠加
依赖于正确的父控件配置
原始 README
以下为关联源码仓库的 README 原文快照,最新内容请访问源码仓库。
Snapping Sheet
A package that provides a highly customizable sheet widget that snaps to different vertical & horizontal positions
Can adapt to scrollable
widgets inside the sheet.
Fully customizable animation
and content.
Not limited to the bottom part
of the app.
You can find and run the examples closing this repository and running the app from the example folder.
Getting started
As usual, begin by adding the package to your pubspec.yaml file, see install instruction.
Here is the most basic setup with the Snapping Sheet:
import'package:flutter/material.dart';
import'package:snapping_sheet/snapping_sheet.dart';
classGettingStartedExampleextendsStatelessWidget{
@overrideWidgetbuild(BuildContext context) {
returnScaffold(
body: SnappingSheet(
// TODO: Add your content that is placed// behind the sheet. (Can be left empty)
child: MyOwnPageContent(),
grabbingHeight: 75,
// TODO: Add your grabbing widget here,
grabbing: MyOwnGrabbingWidget(),
sheetBelow: SnappingSheetContent(
draggable: true,
// TODO: Add your sheet content here
child: MyOwnSheetContent(),
),
),
);
}
}
Customize snapping positions
To change the snap positions for the sheet, change the snappingPositions parameter
witch takes in a list of SnappingPosition.factor or SnappingPosition.pixels. These
two objects are used to specify the location using a factor or pixels. You also have the option
to specify the duration and curve of how the sheet should snap to that given position.
You can place content both below or/and above the grabbing part of the sheet. If you do not want any content in the above or below part of the sheet, pass in null.
sizeBehavior: How the size of the content should behave. Can either be SheetSizeFill which fills the available height of the sheet, or SheetSizeStatic, which takes in a height that is respected in the sheet.
draggable: If the sheet itself can be draggable to expand or close the Snapping Sheet.
In order to make the sheet know about the scroll controller, you need to provide it in the SnappingSheetContent class (See example below). It is recommended to set lockOverflowDrag to true to prevent the sheet to be dragged above or below its max and min snapping position.
SnappingSheet(
lockOverflowDrag: true, // (Recommended) Set this to true.
sheetBelow: SnappingSheetContent(
// Pass in the scroll controller here!
childScrollController: _myScrollController,
draggable: true,
child: ListView(
// And in the scrollable widget that you create!
controller: _myScrollController,
// OBS! Should be false if it is in sheetBelow.// OBS! Should be true if it is in sheetAbove.
reverse: false,
),
),
)
OBS that the scrollable widget, e.g ListView, SingleChildScrollView, etc. needs to have the correct reverse value depending on were it is located. If the scrollable widget is in the sheetBelow, the reverse value should be set to false. If it is located in the sheetAbove it should be set to true. The reason is that the current logic of the SnappingSheet only support that configuration of a scrollable widget.
Using the SnappingSheetController
You can control the Snapping Sheet using the SnappingSheetController
// Create your controllerfinal snappingSheetController = SnappingSheetController();
SnappingSheet(
// Connect it to the SnappingSheet
controller: SnappingSheetController();
sheetAbove: SnappingSheetContent(
draggable: false,
child: Container(color: Colors.blue),
),
sheetBelow: SnappingSheetContent(
draggable: true,
child: Container(color: Colors.red),
),
)
// You can now control the sheet in multiple ways.
snappingSheetController.snapToPosition(
SnappingPosition.factor(positionFactor: 0.75),
);
snappingSheetController.stopCurrentSnapping();
snappingSheetController.setSnappingSheetPosition(100);
// You can also extract information from the sheet
snappingSheetController.currentPosition;
snappingSheetController.currentSnappingPosition;
snappingSheetController.currentlySnapping;
snappingSheetController.isAttached;
Listen to changes
You can listen to movement and when a snapping animation is completed by using the following parameters:
SnappingSheet(
onSheetMoved: (sheetPosition) {
print("Current position ${sheetPosition.pixels}");
},
onSnapCompleted: (sheetPosition, snappingPosition) {
print("Current position ${sheetPosition.pixels}");
print("Current snapping position $snappingPosition");
},
onSnapStart: (sheetPosition, snappingPosition) {
print("Current position ${sheetPosition.pixels}");
print("Next snapping position $snappingPosition");
},
)
Horizontal Snapping Sheet
There also exist a horizontal mode for the snapping sheet. To use it, see the code below:
SnappingSheet.horizontal(
// TODO: Add your content that is placed// behind the sheet. (Can be left empty)
child: MyOwnPageContent(),
grabbingWidth: 50,
// TODO: Add your grabbing widget here,
grabbing: MyOwnGrabbingWidget(),
sheetLeft: SnappingSheetContent(
draggable: true,
// TODO: Add your sheet content here
child: MyOwnSheetContent(),
),
),
A more complex example of a horizontal SnappingSheet can be found here.