I am trying to implement this side panel view in flutter web. I don't understand how will it work. I am using AutoRoute to route to a new page. Now this isn't a new page since I want the previous page in the back as well. How can I build it. I request you to provide some hint or idea behind the implementation
I don't want this to be a drawer for my scaffold as this is currently a separate page in mobile code. I want to convert the route to the page as a side panel now.
EDIT: Understand one main thing, I want to know if there is a way to create this side panel as a route ( just like any other page ) using which I can open this side panel by simply routing to it.
I am trying to implement this side panel view in flutter web. I don't understand how will it work. I am using AutoRoute to route to a new page. Now this isn't a new page since I want the previous page in the back as well. How can I build it. I request you to provide some hint or idea behind the implementation
I don't want this to be a drawer for my scaffold as this is currently a separate page in mobile code. I want to convert the route to the page as a side panel now.
EDIT: Understand one main thing, I want to know if there is a way to create this side panel as a route ( just like any other page ) using which I can open this side panel by simply routing to it.
Share edited Mar 4 at 5:18 Arshpreet Singh asked Mar 3 at 10:55 Arshpreet SinghArshpreet Singh 14111 bronze badges2 Answers
Reset to default 0I think you can take a look to the Drawer widget : https://docs.flutter.dev/cookbook/design/drawer.
You can tweak it easilly to get the same design you want ;)
Custom Side Panel in Flutter Without Using Drawer
=> Demo Video for the provided Solution: side_panel_widget
You can achieve this UI programmatically using bool
showPanel
for logic and Stack
widget for the UI. Here's how you can do it step by step:
Step 1: Create a bool showPanel
Property to Control the Panel State
bool showPanel = false; // initial panel state is colsed
Step 2: Create Methods to Open and Close the Panel
void get _openPanel {
setState(
() {
showPanel = true;
},
);
}
void get _closepanel {
setState(
() {
showPanel = false;
},
);
}
Step 3: Build the UI Using a Stack
Widget
The UI will consist of three layers:
1. Main Screen: The primary content of the page
2. Black Overlay: semi-transparent layer that appears when the panel is open Tapping it will close the panel.
3. Panel Layer: The side panel that slides in from the right
4. Open panel button: this button will open the panel
Scaffold(
body: Stack(
children: <Widget>[
const Positioned.fill(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
FlutterLogo(
size: 200,
),
Text(
"Page Content",
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
),
),
],
),
),
Positioned(
top: 0.0,
right: 15.0,
child: IconButton(
onPressed: () {
_openPanel;
},
icon: const Icon(Icons.menu),
),
),
if (showPanel) ...{
GestureDetector(
onTap: () {
_closepanel;
},
child: Positioned.fill(
child: Container(
decoration: const BoxDecoration(
color: Colors.black38,
),
),
),
),
},
if (showPanel) ...{
AnimatedPositioned(
duration: const Duration(milliseconds: 500),
right: showPanel ? 0.0 : -90,
top: 0.0,
bottom: 0.0,
child: AnimatedOpacity(
duration: const Duration(milliseconds: 500),
opacity: showPanel ? 1.0 : 0.0,
child: PanelWidget(
onCloseTapped: () {
_closepanel;
},
),
),
),
},
],
),
);
As you can see:
- Panel will be opened when the user tap on the menu button
- Panel will be closed when the user tap the close button or when the user tap outside the panel
- When
showPanel
true
,SidePanelWidget
and thesimi-transparent
will be visible, when it isfalse
they will disappear
Step 4: Create the PanelWidget
Component:
class PanelWidget extends StatelessWidget {
const PanelWidget({
super.key,
required this.onCloseTapped,
});
final void Function() onCloseTapped;
@override
Widget build(BuildContext context) {
return Container(
width: MediaQuery.sizeOf(context).width * .3,
decoration: const BoxDecoration(
color: Colors.white,
),
child: Column(
children: <Widget>[
SizedBox(
height: MediaQuery.sizeOf(context).height * .1,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 15.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
const Text(
"Goal Sammury",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
IconButton(
onPressed: onCloseTapped,
icon: const Icon(Icons.clear),
),
],
),
),
),
Expanded(
child: Container(
decoration: const BoxDecoration(
color: Color(0xFFD1F8EF),
),
child: const Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Icon(Icons.home),
Text(
"Home Goal",
style: TextStyle(
fontSize: 19,
fontWeight: FontWeight.bold,
),
),
],
),
CaseSammuryWidget(),
LinkedAccountWidget(),
DeleteWidget(),
],
),
),
),
],
),
);
}
}
Step 5: Add Additional Components(if you need it)
1. Delete Container(DeleteWidget
):
class DeleteWidget extends StatelessWidget {
const DeleteWidget({super.key});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(10.0),
margin: const EdgeInsets.all(10.0),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(15.0),
),
child: const Row(
children: <Widget>[
CircleAvatar(
backgroundColor: Colors.redAccent,
child: Icon(
Icons.delete,
),
),
Text(
"Delete case",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
)
],
),
);
}
}
2. Linked Account Widget(LinkedAccountWidget
):
class LinkedAccountWidget extends StatelessWidget {
const LinkedAccountWidget({super.key});
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.all(10),
padding: const EdgeInsets.all(20.0),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(15.0),
),
child: Column(
children: <Widget>[
const Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Text(
"Linked Account",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
],
),
Row(
children: <Widget>[
const CircleAvatar(
backgroundColor: Colors.yellow,
child: Icon(Icons.person),
),
gapW1,
const Text(
"Paid Saving*1111",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
],
),
gapH1,
Row(
children: <Widget>[
const CircleAvatar(
backgroundColor: Colors.yellow,
child: Icon(Icons.person),
),
gapW1,
const Text(
"Paid Saving*1111",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
],
)
],
),
);
}
}
3. The CaseSammuryWidget
, with the data model that has the shown data
class CaseSammuryWidget extends StatelessWidget {
const CaseSammuryWidget({super.key});
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.all(10),
padding: const EdgeInsets.all(20.0),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(15.0),
),
child: Column(
children: <Widget>[
const Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
"Case Summary",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
CircleAvatar(
backgroundColor: Color(0xFFCCDF92),
child: Padding(
padding: EdgeInsets.all(6.0),
child: Icon(
Icons.edit_square,
color: Colors.blue,
),
),
),
],
),
for (int i = 0; i < data.length; i++) ...{
SingleRow(
subTitle: data[i].subTitle,
title: data[i].title,
isLast: i == data.length - 1,
),
},
],
),
);
}
}
class SingleRow extends StatelessWidget {
const SingleRow({
super.key,
required this.subTitle,
required this.title,
this.isLast = false,
});
final String title;
final String subTitle;
final bool isLast;
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(title),
Text(subTitle),
],
),
if (!isLast) ...{
const Divider(),
}
],
);
}
}
/// Showen Data
class DataModel {
final String title;
final String subTitle;
const DataModel({
required this.title,
required this.subTitle,
});
}
List<DataModel> data = const <DataModel>[
DataModel(
title: "Estimated cost",
subTitle: "\$1050000",
),
DataModel(
title: "Target Year",
subTitle: "3 Year",
),
DataModel(
title: "Starting Amount",
subTitle: "\$5000",
),
DataModel(
title: "Monthly deposit",
subTitle: "\$500",
),
DataModel(
title: "Stratigy",
subTitle: "Core Protifolio",
),
DataModel(
title: "Risk level",
subTitle: "Conservative",
),
];
Side Panel Widget Without Drawer Full Code:
class SidePanelWidget extends StatefulWidget {
const SidePanelWidget({super.key});
@override
State<SidePanelWidget> createState() => _SidePanelWidgetState();
}
class _SidePanelWidgetState extends State<SidePanelWidget> {
bool showPanel = false;
void get _openPanel {
setState(
() {
showPanel = true;
},
);
}
void get _closepanel {
setState(
() {
showPanel = false;
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: <Widget>[
const Positioned.fill(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
FlutterLogo(
size: 200,
),
Text(
"Page Content",
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
),
),
],
),
),
Positioned(
top: 0.0,
right: 15.0,
child: IconButton(
onPressed: () {
_openPanel;
},
icon: const Icon(Icons.menu),
),
),
if (showPanel) ...{
GestureDetector(
onTap: () {
_closepanel;
},
child: Positioned.fill(
child: Container(
decoration: const BoxDecoration(
color: Colors.black38,
),
),
),
),
},
if (showPanel) ...{
AnimatedPositioned(
duration: const Duration(milliseconds: 1500),
right: showPanel ? 0.0 : -900,
top: 0.0,
bottom: 0.0,
child: AnimatedOpacity(
duration: const Duration(milliseconds: 1500),
opacity: showPanel ? 1.0 : 0.0,
child: PanelWidget(
onCloseTapped: () {
_closepanel;
},
),
),
),
},
],
),
);
}
}
class PanelWidget extends StatelessWidget {
const PanelWidget({
super.key,
required this.onCloseTapped,
});
final void Function() onCloseTapped;
@override
Widget build(BuildContext context) {
return Container(
width: MediaQuery.sizeOf(context).width * .3,
decoration: const BoxDecoration(
color: Colors.white,
),
child: Column(
children: <Widget>[
SizedBox(
height: MediaQuery.sizeOf(context).height * .1,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 15.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
const Text(
"Goal Sammury",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
IconButton(
onPressed: onCloseTapped,
icon: const Icon(Icons.clear),
),
],
),
),
),
Expanded(
child: Container(
decoration: const BoxDecoration(
color: Color(0xFFD1F8EF),
),
child: const Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Icon(Icons.home),
Text(
"Home Goal",
style: TextStyle(
fontSize: 19,
fontWeight: FontWeight.bold,
),
),
],
),
CaseSammuryWidget(),
LinkedAccountWidget(),
DeleteWidget(),
],
),
),
),
],
),
);
}
}
class DeleteWidget extends StatelessWidget {
const DeleteWidget({super.key});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(10.0),
margin: const EdgeInsets.all(10.0),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(15.0),
),
child: const Row(
children: <Widget>[
CircleAvatar(
backgroundColor: Colors.redAccent,
child: Icon(
Icons.delete,
),
),
Text(
"Delete case",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
)
],
),
);
}
}
// Linked Account Widget
class LinkedAccountWidget extends StatelessWidget {
const LinkedAccountWidget({super.key});
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.all(10),
padding: const EdgeInsets.all(20.0),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(15.0),
),
child: Column(
children: <Widget>[
const Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Text(
"Linked Account",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
],
),
Row(
children: <Widget>[
const CircleAvatar(
backgroundColor: Colors.yellow,
child: Icon(Icons.person),
),
gapW1,
const Text(
"Paid Saving*1111",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
],
),
gapH1,
Row(
children: <Widget>[
const CircleAvatar(
backgroundColor: Colors.yellow,
child: Icon(Icons.person),
),
gapW1,
const Text(
"Paid Saving*1111",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
],
)
],
),
);
}
}
// Case Sammuty Widget
class CaseSammuryWidget extends StatelessWidget {
const CaseSammuryWidget({super.key});
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.all(10),
padding: const EdgeInsets.all(20.0),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(15.0),
),
child: Column(
children: <Widget>[
const Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
"Case Summary",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
CircleAvatar(
backgroundColor: Color(0xFFCCDF92),
child: Padding(
padding: EdgeInsets.all(6.0),
child: Icon(
Icons.edit_square,
color: Colors.blue,
),
),
),
],
),
for (int i = 0; i < data.length; i++) ...{
SingleRow(
subTitle: data[i].subTitle,
title: data[i].title,
isLast: i == data.length - 1,
),
},
],
),
);
}
}
class SingleRow extends StatelessWidget {
const SingleRow({
super.key,
required this.subTitle,
required this.title,
this.isLast = false,
});
final String title;
final String subTitle;
final bool isLast;
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(title),
Text(subTitle),
],
),
if (!isLast) ...{
const Divider(),
}
],
);
}
}
/// Showen Data
class DataModel {
final String title;
final String subTitle;
const DataModel({
required this.title,
required this.subTitle,
});
}
List<DataModel> data = const <DataModel>[
DataModel(
title: "Estimated cost",
subTitle: "\$1050000",
),
DataModel(
title: "Target Year",
subTitle: "3 Year",
),
DataModel(
title: "Starting Amount",
subTitle: "\$5000",
),
DataModel(
title: "Monthly deposit",
subTitle: "\$500",
),
DataModel(
title: "Stratigy",
subTitle: "Core Protifolio",
),
DataModel(
title: "Risk level",
subTitle: "Conservative",
),
];
Hope that was helpful!!