draggable_floating_fab 0.1.4
draggable_floating_fab: ^0.1.4 copied to clipboard
A draggable, sticky FAB overlay with a drag-to-dismiss zone for Flutter apps.
import 'package:flutter/material.dart';
import 'package:draggable_floating_fab/draggable_floating_fab.dart';
void main() {
runApp(const ExampleApp());
}
class ExampleApp extends StatefulWidget {
const ExampleApp({super.key});
@override
State<ExampleApp> createState() => _ExampleAppState();
}
class _ExampleAppState extends State<ExampleApp> {
final GlobalKey<NavigatorState> _navigatorKey = GlobalKey<NavigatorState>();
String _currentPath = HomeScreen.routeName;
late final _PathObserver _pathObserver = _PathObserver((String path) {
if (_currentPath == path) {
return;
}
setState(() => _currentPath = path);
});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Draggable Floating FAB Demo',
navigatorKey: _navigatorKey,
navigatorObservers: <NavigatorObserver>[_pathObserver],
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF143087)),
useMaterial3: true,
),
initialRoute: HomeScreen.routeName,
routes: <String, WidgetBuilder>{
HomeScreen.routeName: (_) => const HomeScreen(),
HiddenScreen.routeName: (_) => const HiddenScreen(),
SupportScreen.routeName: (_) => const SupportScreen(),
},
builder: (BuildContext context, Widget? child) {
return DraggableSupportFabOverlay(
onFabTap: () {
_navigatorKey.currentState?.pushNamed(SupportScreen.routeName);
},
currentPath: () => _currentPath,
hiddenRoutePaths: const <String>{
HiddenScreen.routeName,
SupportScreen.routeName,
},
fabColor: const Color(0xFF143087),
bottomSafeInset: 16,
child: child ?? const SizedBox.shrink(),
);
},
);
}
}
class _PathObserver extends NavigatorObserver {
_PathObserver(this.onPath);
final ValueChanged<String> onPath;
void _emit(Route<dynamic>? route) {
onPath(route?.settings.name ?? HomeScreen.routeName);
}
@override
void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
_emit(route);
}
@override
void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
_emit(previousRoute);
}
@override
void didReplace({Route<dynamic>? newRoute, Route<dynamic>? oldRoute}) {
_emit(newRoute);
}
}
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
static const String routeName = '/';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Home')),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Text('Drag the support FAB, or tap it.'),
const SizedBox(height: 16),
FilledButton(
onPressed: () {
Navigator.of(context).pushNamed(HiddenScreen.routeName);
},
child: const Text('Open hidden route'),
),
],
),
),
);
}
}
class HiddenScreen extends StatelessWidget {
const HiddenScreen({super.key});
static const String routeName = '/hidden';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Hidden route')),
body: const Center(child: Text('FAB is hidden on this route.')),
);
}
}
class SupportScreen extends StatelessWidget {
const SupportScreen({super.key});
static const String routeName = '/support';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Support')),
body: const Center(child: Text('Opened from the floating FAB.')),
);
}
}