morphing_sheet 0.1.0
morphing_sheet: ^0.1.0 copied to clipboard
A production-ready multi-stage, gesture-driven, spatially continuous UX sheet with physics-based snapping and morphing fullscreen transitions.
morphing_sheet #
A production-ready Flutter package implementing a multi-stage, gesture-driven, spatially continuous UX sheet with physics-based snapping and morphing fullscreen transitions.
Features #
- Three-stage sheet -- collapsed (25%), half-expanded (60%), fullscreen (100%) with smooth continuous transitions between them.
- Physics-based gestures -- velocity-aware snap resolution, rubber-band resistance at bounds, and configurable flick thresholds.
- Single animation source of truth -- one
AnimationControllerdrives all visual properties (height, radius, elevation, blur, scale) preventing rebuild storms. - State-management agnostic --
SheetControllerextendsChangeNotifier, works with Provider, Riverpod, Bloc, or vanillaListenableBuilder. - Fully customizable -- snap points, curves, radii, elevation, background
effects, and physics are all configurable through
SheetConfig. - Horizontal page switching -- built-in
PageViewwith per-snap-point control over whether swiping is enabled. - Injectable physics -- extend
SheetPhysicsto implement magnetic snap points, spring simulations, or platform-specific motion. - Performance optimized --
RepaintBoundaryisolation,ListenableBuilderfor targeted rebuilds, nosetStateduring animations.
Installation #
dependencies:
morphing_sheet:
path: ../ # or from pub once published
Quick Start #
import 'package:morphing_sheet/morphing_sheet.dart';
class MyPage extends StatefulWidget {
@override
State<MyPage> createState() => _MyPageState();
}
class _MyPageState extends State<MyPage>
with SingleTickerProviderStateMixin {
late final SheetController _controller;
@override
void initState() {
super.initState();
_controller = SheetController(vsync: this);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: MorphingSheet(
controller: _controller,
pageCount: 3,
headerBuilder: (context, progress, state) {
return Container(
padding: const EdgeInsets.all(16),
child: Text('Progress: ${(progress * 100).toInt()}%'),
);
},
contentBuilder: (context, index, progress, state) {
return Center(child: Text('Page $index'));
},
child: const Center(child: Text('Background')),
),
);
}
}
Architecture #
lib/
├── morphing_sheet.dart # barrel export
└── src/
├── core/
│ └── sheet_physics.dart # abstract physics interface
├── physics/
│ ├── default_sheet_physics.dart
│ └── snap_resolver.dart
├── controllers/
│ └── sheet_controller.dart
├── models/
│ ├── sheet_state.dart
│ ├── sheet_config.dart
│ ├── sheet_visual_state.dart
│ └── snap_point.dart
├── animations/
│ ├── curve_presets.dart
│ ├── sheet_tween.dart
│ └── transition_spec.dart
└── widgets/
├── morphing_sheet_widget.dart
├── sheet_scaffold.dart
├── gesture_layer.dart
├── background_transform.dart
└── sheet_page_view.dart
Layer responsibilities #
| Layer | Purpose |
|---|---|
| Models | Immutable state (SheetState), configuration (SheetConfig), value objects (SnapPoint), sealed state hierarchy (SheetVisualState) |
| Core | Abstract interfaces (SheetPhysics) for dependency inversion |
| Physics | Concrete physics implementations, pure-function snap resolution |
| Controllers | SheetController -- MVVM ViewModel, owns AnimationController, exposes immutable state snapshots |
| Animations | Interpolation helpers (SheetTween), curve presets (SheetCurves), per-property timing (TransitionSpec) |
| Widgets | Presentation layer -- MorphingSheet, SheetScaffold, GestureLayer, BackgroundTransform, SheetPageView |
Configuration #
const config = SheetConfig(
snapPoints: [
SnapPoint(position: 0.2, label: 'collapsed'),
SnapPoint(position: 0.5, label: 'half'),
SnapPoint(position: 1.0, label: 'full', enableHorizontalSwipe: false),
],
cornerRadius: 28,
elevation: 12,
backgroundMinScale: 0.92,
backgroundMaxBlur: 8,
expandCurve: SheetCurves.expand,
collapseCurve: SheetCurves.collapse,
);
Custom Physics #
class MagneticPhysics extends SheetPhysics {
const MagneticPhysics();
@override
double applyResistance(double delta, double pos, double min, double max) {
// Stronger pull near snap points
return delta * 0.8;
}
@override
SnapPoint resolveSnap(double pos, double velocity, List<SnapPoint> points) {
return SnapResolver.resolve(pos, velocity, points, flickThreshold: 0.5);
}
@override
bool shouldFlick(double velocity) => velocity.abs() > 0.5;
}
MorphingSheet(physics: const MagneticPhysics(), ...)
Visual State Matching #
switch (controller.visualState) {
case CollapsedState():
// show minimal UI
case HalfExpandedState():
// show richer content
case ExpandedState():
// show full detail
case TransitioningState(:final from, :final to, :final localProgress):
// animate between states
}
Minimum Requirements #
- Flutter >= 3.10.0
- Dart >= 3.10.4
License #
MIT