Flutter Tutorial - How to Implement a Persistent Bottom Sheet
This tutorial will teach you how to implement a persistent bottom sheet in Flutter, which is a sheet that remains visible at the bottom of the screen even when the user scrolls.
We will use the built-in BottomSheet widget in combination with the Scaffold widget to achieve this.
Material : Persistent Bottom sheet
import 'package:flutter/material.dart';
class BottomSheetFabRoute extends StatefulWidget {
const BottomSheetFabRoute({super.key});
@override
BottomSheetFabRouteState createState() => BottomSheetFabRouteState();
}
class BottomSheetFabRouteState extends State<BottomSheetFabRoute> {
// This variable will hold a reference to the bottom sheet controller.
late PersistentBottomSheetController sheetController;
// This variable will hold a reference to the scaffold context.
late BuildContext _scaffoldCtx;
// This boolean variable will determine whether the bottom sheet is currently visible or not.
bool showSheet = false;
@override
Widget build(BuildContext context) {
// Get the current text theme and apply the surface color to it
final textTheme = Theme.of(context)
.textTheme
.apply(displayColor: Theme.of(context).colorScheme.onSurface);
// Return a scaffold with a centered text widget that displays a message and a floating action button
return Scaffold(
// Use the builder function to get access to the scaffold context
body: Builder(builder: (BuildContext ctx) {
_scaffoldCtx = ctx; // Assign the scaffold context to a variable for later use
return Center(
child:
TextStyleExample(name: "Tap button \nbelow", style: textTheme.headlineMedium!.copyWith(color: Theme.of(context).colorScheme.primary)),
);
}),
// Add a floating action button that toggles a bottom sheet
floatingActionButton: FloatingActionButton(
heroTag: "fab",
backgroundColor:Theme.of(context).colorScheme.primary,
elevation: 3,
child: Icon(showSheet ? Icons.arrow_downward : Icons.arrow_upward, color: Theme.of(context).colorScheme.onPrimary),
onPressed: () {
setState(() {
showSheet = !showSheet; // Toggle the sheet visibility
if(showSheet) {
_showSheet(); // Show the sheet if it's not already showing
} else {
Navigator.pop(_scaffoldCtx); // Close the sheet if it's already showing
}
});
}
),
);
}
void _showSheet() {
// get the current text theme with updated display color
final textTheme = Theme.of(context)
.textTheme
.apply(displayColor: Theme.of(context).colorScheme.onSurface);
// show a bottom sheet and store the returned controller object
sheetController = showBottomSheet(context: _scaffoldCtx, builder: (BuildContext bc){
// create a card with elevation and no margin
return Card(
elevation: 10,
margin: const EdgeInsets.fromLTRB(0, 0, 0, 0),
child: Container(
padding: const EdgeInsets.all(10),
width: double.infinity,
color: Theme.of(context).colorScheme.background,
// add a container to hold the content of the sheet
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Center(
child: Container(width: 30, height: 5, decoration: BoxDecoration(
color: Theme.of(context).colorScheme.background,
borderRadius: const BorderRadius.all(Radius.circular(5)),
)),
),
Container(height: 10),
Row(
children: <Widget>[
Container(width: 50),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
TextStyleExample(name: "Dairy milk Chocolate", style: textTheme.titleMedium!.copyWith(color: Theme.of(context).colorScheme.primary)),
Container(height: 20),
Container(height: 5),
const Divider(),
Container(
padding: const EdgeInsets.symmetric(vertical: 10),
child:
TextStyleExample(name: "10 min away", style: textTheme.titleMedium!.copyWith(color: Theme.of(context).colorScheme.primary))
),
const Divider(),
],
),
)
],
),
SizedBox(height: 50,
child: Row(
children: <Widget>[
Container(width: 10), Icon(Icons.location_on, color: Theme.of(context).colorScheme.primary), Container(width: 20),
TextStyleExample(name: "740 Valencia St, San Francisco, CA", style: textTheme.titleMedium!)
],
),
),
SizedBox(height: 50,
child: Row(
children: <Widget>[
Container(width: 10), Icon(Icons.phone, color: Theme.of(context).colorScheme.primary), Container(width: 20),
TextStyleExample(name: "(415) 349-0942", style: textTheme.titleMedium!)
],
),
),
SizedBox(height: 50,
child: Row(
children: <Widget>[
Container(width: 10), Icon(Icons.location_on, color: Theme.of(context).colorScheme.primary), Container(width: 20),
TextStyleExample(name: "741 Valencia St, San Francisco, CA", style: textTheme.titleMedium!)
],
),
),
SizedBox(height: 50,
child: Row(
children: <Widget>[
Container(width: 10), Icon(Icons.phone, color: Theme.of(context).colorScheme.primary), Container(width: 20),
TextStyleExample(name: "(416) 349-0942", style: textTheme.titleMedium!)
],
),
),
SizedBox(height: 50,
child: Row(
children: <Widget>[
Container(width: 10), Icon(Icons.schedule, color: Theme.of(context).colorScheme.primary), Container(width: 20),
TextStyleExample(name: "Wed, 10 AM - 9 PM", style: textTheme.titleMedium!)
],
),
),
],
)
),
);
});
// set a callback function to be called when the bottom sheet is closed
sheetController.closed.then((value) {
// update state to indicate that the sheet is no longer being shown
setState(() {
showSheet = false;
});
});
}
}
// Typography widget
class TextStyleExample extends StatelessWidget {
// Constructor for the TextStyleExample class
const TextStyleExample({
super.key, // Call the constructor of the superclass
required this.name, // Declare a required String property called name
required this.style, // Declare a required TextStyle property called style
});
final String name; // Store the name property as a final String
final TextStyle style; // Store the style property as a final TextStyle
@override
Widget build(BuildContext context) {
return Padding(
// Set the padding for the widget to 1.0 pixels on all sides
padding: const EdgeInsets.all(1.0),
child: Text(
name, // Display the name property as the text content
style: style.copyWith(letterSpacing: 1.0), // Apply the style property to the text, with an additional letterSpacing of 1.0
),
);
}
}
..
- Import the necessary packages.
- Create a stateful widget called BottomSheetFabRoute.
- Override the createState method to return a BottomSheetFabRouteState object.
- Create a state class called BottomSheetFabRouteState that extends the State class.
- Declare three variables:
- sheetController, a PersistentBottomSheetController that will hold a reference to the bottom sheet controller.
- _scaffoldCtx, a BuildContext that will hold a reference to the scaffold context.
- showSheet, a boolean that will determine whether the bottom sheet is currently visible or not.
- Override the build method to return a Scaffold widget with a centered text widget that displays a message and a floating action button.
- Use the Builder widget to get access to the scaffold context and assign it to the _scaffoldCtx variable for later use.
- Add a floating action button that toggles a bottom sheet. The button's onPressed method toggles the showSheet boolean and either shows the sheet if it's not already showing or closes the sheet if it's already showing.
floatingActionButton: FloatingActionButton(
...
onPressed: () {
setState(() {
showSheet = !showSheet; // Toggle the sheet visibility
if(showSheet) {
_showSheet(); // Show the sheet if it's not already showing
} else {
Navigator.pop(_scaffoldCtx); // Close the sheet if it's already showing
}
});
}
),
- Define the _showSheet method that displays the bottom sheet. The method uses the showBottomSheet function to display a card with information about a chocolate store.
void _showSheet() {
// show a bottom sheet and store the returned controller object
sheetController = showBottomSheet(context: _scaffoldCtx, builder: (BuildContext bc){
// create a card with elevation and no margin
return Card(
elevation: 10,
margin: const EdgeInsets.fromLTRB(0, 0, 0, 0),
child: Container(
padding: const EdgeInsets.all(10),
width: double.infinity,
color: Theme.of(context).colorScheme.background,
// add a container to hold the content of the sheet
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
...
],
),
),
);
});
}
- Create a custom widget called TextStyleExample that displays a name and a style.
..
Comments
Post a Comment