Skip to content

Instantly share code, notes, and snippets.

@emri99
Last active March 1, 2025 16:47
Show Gist options
  • Save emri99/74a4da7a18327814fd8a165949da449e to your computer and use it in GitHub Desktop.
Save emri99/74a4da7a18327814fd8a165949da449e to your computer and use it in GitHub Desktop.
Draggable flutter bottom sheet & Autoroute (flutter 3.16, auto_route 7.8.4)
import 'modal_bottom_sheet_autoroute.dart';
export 'package:auto_route/auto_route.dart';
export 'app_router.gr.dart';
@AutoRouterConfig(replaceInRouteName: 'View,Route')
class AppRouter extends $AppRouter {
@override
final List<AutoRoute> routes = [
AutoRoute(path: '/', page: HomeRoute.page),
ModalBottomSheetAutoRoute(path: 'sample', page: SampleRoute.page, barrierDismissible: false),
]
}
import 'package:flutter/material.dart';
import 'app_router.dart';
import 'modal_bottom_sheet_autoroute.dart';
void main() => runApp(App());
class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: MaterialApp.router(
routerDelegate: AutoRouterDelegate(appRouter),
routeInformationParser: appRouter.defaultRouteParser(),
),
);
}
}
@RoutePage<void>()
class HomeView extends StatelessWidget {
const HomeView({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Home'),),
body: Center(
child: ElevatedButton(
onPressed: () {
AutoRouter.of(context).push(const SampleRoute());
},
child: const Text('Open bottomsheet'),
),
),
);
}
}
@RoutePage<void>()
class SampleView extends StatelessWidget with DraggableScrollControllerMixin {
const SampleView({super.key});
@override
Widget build(BuildContext context) {
return ListView.builder(
controller: getScrollController(context),
itemBuilder: (context, index) => ListTile(title: Text('Item $index')),
itemCount: 100,
);
}
}
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'modal_draggable_sheet.dart';
class ModalBottomSheetAutoRoute extends CustomRoute {
ModalBottomSheetAutoRoute({
required super.page,
super.path,
super.usesPathAsKey,
super.guards,
super.fullMatch,
super.meta,
super.maintainState,
super.fullscreenDialog,
super.children,
super.title,
super.restorationId,
super.keepHistory,
super.initial,
super.transitionsBuilder,
super.durationInMilliseconds,
super.reverseDurationInMilliseconds,
super.opaque,
super.barrierDismissible,
super.barrierLabel,
super.barrierColor,
double? fixedSize,
double initialSize = .5,
double minSize = .25,
double maxSize = .95,
ShapeBorder? shape,
bool? isScrollControlled,
bool? enableDrag,
bool? showDragHandle,
Color? backgroundColor,
bool? useSafeArea,
String? barrierOnTapHint,
}) : super(
customRouteBuilder: routeBuilderFactory(
initialChildSize: fixedSize ?? initialSize,
maxChildSize: fixedSize ?? maxSize,
minChildSize: fixedSize ?? minSize,
barrierColor: barrierColor,
barrierDismissible: barrierDismissible,
backgroundColor: backgroundColor,
barrierLabel: barrierLabel,
shape: shape,
isScrollControlled: isScrollControlled,
enableDrag: enableDrag,
showDragHandle: showDragHandle,
useSafeArea: useSafeArea,
barrierOnTapHint: barrierOnTapHint,
),
);
static CustomRouteBuilder routeBuilderFactory({
required double initialChildSize,
required double minChildSize,
required double maxChildSize,
required bool barrierDismissible,
Color? backgroundColor,
Color? barrierColor,
ShapeBorder? shape,
bool? enableDrag,
bool? showDragHandle,
bool? isScrollControlled,
bool? useSafeArea,
String? barrierLabel,
String? barrierOnTapHint,
}) {
return <T>(BuildContext context, Widget child, AutoRoutePage<T> page) {
return ModalBottomSheetRoute<T>(
backgroundColor: backgroundColor,
isDismissible: barrierDismissible,
modalBarrierColor: barrierColor,
shape: shape,
isScrollControlled: isScrollControlled ?? true,
enableDrag: enableDrag ?? true,
showDragHandle: showDragHandle,
barrierLabel: barrierLabel,
settings: page,
useSafeArea: useSafeArea ?? false,
barrierOnTapHint: barrierOnTapHint,
builder: (context) {
return isScrollControlled ?? true
? ModalDraggableSheet(
initialChildSize: initialChildSize,
minChildSize: minChildSize,
maxChildSize: maxChildSize,
child: child,
)
: child;
},
);
};
}
}
import 'package:flutter/material.dart';
// this widgets can be used without autoroute
class ModalDraggableSheet extends StatelessWidget {
const ModalDraggableSheet({
super.key,
required this.child,
this.initialChildSize = .5,
this.minChildSize = .25,
this.maxChildSize = .95,
});
final Widget child;
final double initialChildSize;
final double minChildSize;
final double maxChildSize;
@override
Widget build(BuildContext context) {
return DraggableScrollableSheet(
initialChildSize: initialChildSize,
minChildSize: minChildSize,
maxChildSize: maxChildSize,
expand: false,
builder: (context, ScrollController scrollController) => DraggableScrollController(
scrollController: scrollController,
child: child,
),
);
}
}
/// this widget is responsible of supplying the scroll controller of a draggable
/// scrollable sheet to its children.
///
/// * see
/// * [DraggableScrollControllerStateMixin] for stateful widget mixin
/// Use the getter [DraggableScrollControllerStateMixin.scrollController]
/// to get the scroll controller
/// * [DraggableScrollControllerStateMixin] for stateless widget mixin
/// Use the method [DraggableScrollControllerMixin.getScrollController]
/// to get the scroll controller
///
class DraggableScrollController extends InheritedWidget {
const DraggableScrollController({
super.key,
this.scrollController,
required super.child,
});
final ScrollController? scrollController;
static ScrollController? of(BuildContext context, {bool listen = false}) {
if (listen) {
return context.dependOnInheritedWidgetOfExactType<DraggableScrollController>()?.scrollController;
}
final widget = context.getElementForInheritedWidgetOfExactType<DraggableScrollController>()?.widget
as DraggableScrollController?;
return widget?.scrollController;
}
@override
bool updateShouldNotify(covariant InheritedWidget oldWidget) {
return false;
}
}
/// mixin to simplify retrieving the scrollController on stateful widget that may be
/// inserted inside a [ModalDraggableSheet]
mixin DraggableScrollControllerStateMixin<T extends StatefulWidget> on State<T> {
ScrollController? get scrollController => DraggableScrollController.of(context);
}
/// mixin to simplify retrieving the scrollController on stateless widget that may be
/// inserted inside a [ModalDraggableSheet]
mixin DraggableScrollControllerMixin on StatelessWidget {
ScrollController? getScrollController(BuildContext context) => DraggableScrollController.of(context);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment