Snap Floater

A draggable floating button for Flutter that snaps to predefined screen positions. Controlled programmatically from anywhere in the widget tree via SnapFloaterScope.of(context).

For better understanding how it works check demo page

Open Demo

Introduction

When building Flutter apps you often need a persistent floating action button that stays out of the way, can be repositioned by the user, and can be shown or hidden depending on app state. SnapFloaterScope wraps your app once and handles all of that — drag, snap, visibility, persistence — without any boilerplate in your screens.

Getting started

Add snap_floater to your pubspec.yaml:

dependencies:
  snap_floater: ^0.1.0

Or using the command:

flutter pub add snap_floater

Import the package in your Dart file:

import 'package:snap_floater/snap_floater.dart';

Usage

Basic example

Wrap your MaterialApp with SnapFloaterScope. The floater hides itself while onPressed runs and reappears when it completes:

MaterialApp(
  home: const HomePage(),
  builder: (context, child) => SnapFloaterScope(
    onPressed: () async {
      await someAction();
    },
    child: child!,
  ),
);

Custom button and preview animation

MaterialApp(
  home: const HomePage(),
  builder: (context, child) => SnapFloaterScope.builder(
    buttonBuilder: (context) => FancyButton(
      onPressed: () => SnapFloaterScope.of(context).runHidden(
        () => someAction(),
      ),
    ),
    previewBuilder: SnapFloaterAnimation.blur,
    child: child!,
  ),
);

Control from anywhere in the tree

final controller = SnapFloaterScope.of(context);

controller.show();
controller.hide();
controller.snapTo(Alignment.topRight);

// Hide while async work runs, show again when done — even if it throws
await controller.runHidden(() async {
  await Navigator.of(context).push(...);
});

Settings

Configure via SnapFloaterSettings:

SnapFloaterScope(
  onPressed: () async {
    await someAction();
  },
  settings: const SnapFloaterSettings(
    initialAlignment: Alignment.bottomRight,
    snapAlignments: [
      Alignment.topRight,
      Alignment.bottomRight,
      Alignment.bottomLeft,
      Alignment.topLeft,
    ],
    isEnabled: true,
    showPreview: true,
    storage: MyStorage(),
  ),
  child: child!,
)
Parameter Default Description
snapAlignments [bottomRight] Available snap targets. Drag is disabled when fewer than two
initialAlignment bottomRight Starting position before any interaction
isEnabled true When false, the floater is hidden and show() has no effect
showPreview true Show preview widgets at snap targets while dragging
storage null Provide to persist position across app launches

Preview animations

Pass any static method from SnapFloaterAnimation as previewBuilder:

SnapFloaterScope.builder(
  previewBuilder: SnapFloaterAnimation.slide,
  ...
)
Animation Description
SnapFloaterAnimation.base Instantly placed, no position animation
SnapFloaterAnimation.slide Slides from current drag position to target
SnapFloaterAnimation.pop Scale overshoot on appear: 0 → 1.15 → 1.0
SnapFloaterAnimation.blur Transitions from blurred to sharp on appear

Custom preview animation

Any function matching PreviewBuilder works as a custom animation:

Widget CustomPreview(
  BuildContext context,
  Size floaterSize,
  Offset currentOffset,
  Offset targetOffset,
  bool isVisible,
  bool isNearest,
  Widget child,
) => Transform.translate(
  offset: targetOffset,
  child: AnimatedOpacity(
    duration: const Duration(milliseconds: 200),
    opacity: isVisible ? (isNearest ? 0.8 : 0.25) : 0.0,
    child: child,
  ),
);

SnapFloaterScope.builder(
  previewBuilder: CustomPreview.new,
  ...
)

Persistent storage

Implement SnapFloaterStorage to persist the floater's position across app launches:

class SharedPrefsStorage implements SnapFloaterStorage {
  SharedPrefsStorage(this._prefs);

  final SharedPreferences _prefs;
  static const _key = 'snap_floater_data';

  @override
  Future<SnapFloaterStorageModel?> read() async {
    final raw = _prefs.getString(_key);
    if (raw == null) return null;
    return SnapFloaterStorageModel.fromJson(jsonDecode(raw));
  }

  @override
  Future<void> write(SnapFloaterStorageModel model) =>
      _prefs.setString(_key, jsonEncode(model.toJson()));

  @override
  Future<void> clear() => _prefs.remove(_key);
}

Pass the implementation via SnapFloaterSettings:

SnapFloaterScope(
  onPressed: () async {
    await someAction();
  },
  settings: SnapFloaterSettings(
    storage: SharedPrefsStorage(await SharedPreferences.getInstance()),
  ),
  child: child!,
)

Features

  • Snaps to predefined alignments on drag release
  • Show / hide programmatically from anywhere in the widget tree
  • Auto-hide while an async action runs, restores automatically when done
  • Custom button widget and custom preview animation via factory constructor
  • Preview widgets shown at snap targets during drag
  • Persists position across app launches via a pluggable storage interface
  • Safe area and edge padding aware
  • Architecturally impossible to have more than one floater per subtree

API reference

SnapFloaterScope

Member Description
SnapFloaterScope({onPressed, child, ...}) Default factory — renders BasicSnapFloaterButton
SnapFloaterScope.builder({buttonBuilder, previewBuilder, child, ...}) Custom button and preview
SnapFloaterScope.of(context) Returns the nearest SnapFloaterController. Throws if not found
SnapFloaterScope.maybeOf(context) Returns null if not found

SnapFloaterController

Member Description
alignment Current snap alignment
isVisible Whether the floater is currently visible
isDragging Whether the user is actively dragging
show() Shows the floater
hide() Hides the floater
snapTo(alignment) Snaps to a specific alignment and persists the change
runHidden(action) Hides while action runs, then shows again

Changelog

The list of changes is available in the file CHANGELOG.md

Contributions

Feel free to contribute to this project. If you find a bug or want to add a new feature but don't know how to fix or implement it, please write in issues. If you fixed a bug or implemented a feature, please make a pull request.

License

MIT License — see LICENSE file for details

Libraries

snap_floater