Dynamic Bottom Sheet In Flutter
In this article, We will explore how to implement a Dynamic Bottom Sheet in Flutter
Before proceeding, please consider subscribing to our YOUTUBE CHANNEL
It gives us a lot of motivation to produce high-quality content for you guys.
if you are interested in watching the video tutorial, Check out below.
Let's start by creating a StatefulWidget
and name it MyDraggableSheet
.
class MyDraggableSheet extends StatefulWidget {
@override
State<MyDraggableSheet> createState() => _MyDraggableSheetState();
}
class _MyDraggableSheetState extends State<MyDraggableSheet> {
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
return PlaceHolder();
}
}
We need a few variables to work with. Let's add them.
DraggableScrollableController
is disposed of inside the dispose()
method once the usage is completed.
class MyDraggableSheet extends StatefulWidget {
final Widget child;
const MyDraggableSheet({super.key, required this.child});
@override
State<MyDraggableSheet> createState() => _MyDraggableSheetState();
}
class _MyDraggableSheetState extends State<MyDraggableSheet> {
final sheet = GlobalKey();
final controller = DraggableScrollableController();
@override
void initState() {
super.initState();
controller.addListener(onChanged);
}
@override
void dispose() {
super.dispose();
controller.dispose();
}
DraggableScrollableSheet get getSheet =>
(sheet.currentWidget as DraggableScrollableSheet);
@override
Widget build(BuildContext context) {
return PlaceHolder;
}
}
Our bottom sheet will have a few states which we will call onChanged()
collapse()
anchor()
expand()
hide()
class MyDraggableSheet extends StatefulWidget {
final Widget child;
const MyDraggableSheet({super.key, required this.child});
@override
State<MyDraggableSheet> createState() => _MyDraggableSheetState();
}
class _MyDraggableSheetState extends State<MyDraggableSheet> {
final sheet = GlobalKey();
final controller = DraggableScrollableController();
@override
void initState() {
super.initState();
controller.addListener(onChanged);
}
void onChanged() {
final currentSize = controller.size;
if (currentSize <= 0.05) collapse();
}
void collapse() => animateSheet(getSheet.snapSizes!.first);
void anchor() => animateSheet(getSheet.snapSizes!.last);
void expand() => animateSheet(getSheet.maxChildSize);
void hide() => animateSheet(getSheet.minChildSize);
void animateSheet(double size) {
controller.animateTo(
size,
duration: const Duration(milliseconds: 50),
curve: Curves.easeInOut,
);
}
@override
void dispose() {
super.dispose();
controller.dispose();
}
DraggableScrollableSheet get getSheet =>
(sheet.currentWidget as DraggableScrollableSheet);
@override
Widget build(BuildContext context) {
return PlaceHolder();
}
SliverToBoxAdapter topButtonIndicator() {
return SliverToBoxAdapter(
child: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Container(
child: Center(
child: Wrap(children: <Widget>[
Container(
width: 100,
margin: const EdgeInsets.only(top: 10, bottom: 10),
height: 5,
decoration: const BoxDecoration(
color: Colors.black45,
shape: BoxShape.rectangle,
borderRadius: BorderRadius.all(Radius.circular(8.0)),
)),
]))),
])),
);
}
}
I’m using a LayoutBuilder
which returns a DraggableScrollableSheet
where we pass the widget value.
class MyDraggableSheet extends StatefulWidget {
final Widget child;
const MyDraggableSheet({super.key, required this.child});
@override
State<MyDraggableSheet> createState() => _MyDraggableSheetState();
}
class _MyDraggableSheetState extends State<MyDraggableSheet> {
final sheet = GlobalKey();
final controller = DraggableScrollableController();
@override
void initState() {
super.initState();
controller.addListener(onChanged);
}
void onChanged() {
final currentSize = controller.size;
if (currentSize <= 0.05) collapse();
}
void collapse() => animateSheet(getSheet.snapSizes!.first);
void anchor() => animateSheet(getSheet.snapSizes!.last);
void expand() => animateSheet(getSheet.maxChildSize);
void hide() => animateSheet(getSheet.minChildSize);
void animateSheet(double size) {
controller.animateTo(
size,
duration: const Duration(milliseconds: 50),
curve: Curves.easeInOut,
);
}
@override
void dispose() {
super.dispose();
controller.dispose();
}
DraggableScrollableSheet get getSheet =>
(sheet.currentWidget as DraggableScrollableSheet);
@override
Widget build(BuildContext context) {
return LayoutBuilder(builder: (context, constraints) {
return DraggableScrollableSheet(
key: sheet,
initialChildSize: 0.5,
maxChildSize: 0.95,
minChildSize: 0,
expand: true,
snap: true,
snapSizes: [
60 / constraints.maxHeight,
0.5,
],
controller: controller,
builder: (BuildContext context, ScrollController scrollController) {
return DecoratedBox(
decoration: const BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.yellow,
blurRadius: 10,
spreadRadius: 1,
offset: Offset(0, 1),
),
],
borderRadius: BorderRadius.only(
topLeft: Radius.circular(22),
topRight: Radius.circular(22),
),
),
child: CustomScrollView(
controller: scrollController,
slivers: [
topButtonIndicator(),
SliverToBoxAdapter(
child: widget.child,
),
],
),
);
},
);
});
}
SliverToBoxAdapter topButtonIndicator() {
return SliverToBoxAdapter(
child: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Container(
child: Center(
child: Wrap(children: <Widget>[
Container(
width: 100,
margin: const EdgeInsets.only(top: 10, bottom: 10),
height: 5,
decoration: const BoxDecoration(
color: Colors.black45,
shape: BoxShape.rectangle,
borderRadius: BorderRadius.all(Radius.circular(8.0)),
)),
]))),
])),
);
}
}
I have created a dummy UI for our bottom sheet. We pass this dummy UI through our main page.
class BottomSheetDummyUI extends StatelessWidget {
const BottomSheetDummyUI({super.key});
@override
Widget build(BuildContext context) {
return Container(
child: Container(
padding: EdgeInsets.only(left: 30, right: 30),
child: Column(
children: [
Row(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(15.0),
child: Container(
color: Colors.black12,
height: 100,
width: 100,
),
),
SizedBox(width: 10),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(15.0),
child: Container(
color: Colors.black12,
height: 20,
width: 240,
),
),
SizedBox(height: 5),
ClipRRect(
borderRadius: BorderRadius.circular(15.0),
child: Container(
color: Colors.black12,
height: 20,
width: 180,
),
),
SizedBox(height: 50),
],
)
],
),
SizedBox(height: 10),
],
)),
);
}
}
Below is how we use the sheet with a dummy UI.
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
backgroundColor: Colors.black,
body: MyDraggableSheet(
child: Column(
children: [
BottomSheetDummyUI(),
BottomSheetDummyUI(),
BottomSheetDummyUI(),
BottomSheetDummyUI(),
BottomSheetDummyUI(),
BottomSheetDummyUI(),
BottomSheetDummyUI(),
],
)),
),
);
}
}
That’s it. You should be able to see the sheet-like below
Once again Thanks for stopping by.
Do check out our YOUTUBE CHANNEL
Social Handles
Instagram : https://www.instagram.com/mobileappsacademy/
Twitter : https://twitter.com/MobileAppsAcdmy
LinkedIn : https://www.linkedin.com/company/mobile-apps-academy/