I want this type of bottom menu in flutter. I scroll anywhere on screen bottom menu will appear. And when I scroll vertically down it will disappear and on slide up it will appear
For that you can use showModalBottomSheet for this type of widgets.
Sample code:
class MyStatelessWidget extends StatelessWidget {
const MyStatelessWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Center(
child: ElevatedButton(
child: const Text('showModalBottomSheet'),
onPressed: () {
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
return Container(
height: 200,
color: Colors.amber,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Text('Modal BottomSheet'),
ElevatedButton(
child: const Text('Close BottomSheet'),
onPressed: () => Navigator.pop(context),
)
],
),
),
);
},
);
},
),
);
}
}
Here are the good package which is useful to get pages as you want:
modal_bottom_sheet
How you can use it:
showMaterialModalBottomSheet(
context: context,
builder: (context) => Container(),
)
For more attributes, you can visit documentation here
Example Link : Modal Bottom Sheet
Related
I'm struggling hard with Flutter's sizing constraints.
I'm trying to make a custom scaffold like screen with a custom appbar and a stack below it with the lowest layer being a listview, and then maybe floating buttons on top if that.
I can't figure out what the proper solution would be to make the listview not throw "vertical viewport was given unbounded height" error.
Here's my code:
import 'package:flutter/material.dart';
import 'package:flutter/src/widgets/container.dart';
import 'package:flutter/src/widgets/framework.dart';
import 'package:my/model.dart';
import 'package:my/widgets/menucard.dart';
import 'package:my/screens/my1.dart';
import 'package:my/screens/my2.dart';
import 'package:my/screens/my3.dart';
import 'package:my/provider.dart';
class myAppBar extends StatelessWidget {
myAppBar(
{super.key,
required double height,
required String title,
bool autoImplyBack = true})
: _height = height,
_title = title,
_autoback = autoImplyBack;
#override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
height: _height,
color: Colors.pink,
child: SizedBox(
width: double.infinity,
height: _height,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(
width: _height,
height: _height,
child: Navigator.canPop(context) && _autoback
? IconButton(
onPressed: () {
Navigator.pop(context);
},
icon: Icon(Icons.arrow_back),
)
: null,
),
Center(
child: Text(_title),
),
SizedBox(
width: _height,
height: _height,
child: IconButton(
onPressed: () {
Navigator.pop(context);
},
icon: Icon(Icons.close),
),
),
],
),
),
);
}
double _height;
String _title;
bool _autoback;
}
class myMenu extends StatelessWidget {
const myMenu({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
myAppBar(height: 100, title: 'Menu'),
Container(
color: Colors.teal,
child: Expanded(
child: //Stack(
//children: [
ListView(
children: [
MenuCard(
icondata: Icons.circle,
subMenu: 'my1',
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const my1()),
);
}),
MenuCard(
icondata: Icons.circle,
subMenu: 'my2',
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const my2()),
);
}),
MenuCard(
icondata: Icons.circle,
subMenu: 'my3',
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const my3()),
);
}),
MenuCard(
icondata: Icons.circle,
subMenu: 'log out',
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const my1()),
);
}),
],
),
//],
),
),
//),
],
),
);
}
}
I've tried setting the listview's shrinkwrap property to true, but that turned out to be ugly as well as not so much performant according to other threads regarding this topic.
I've also tried wrapping the listview in Expanded and Flexible, but to no avail.
You can shift the Expanded widget on top of Container.
body: Column(
children: [
myAppBar(height: 100, title: 'Menu'),
Expanded(
child: Container(
Also while you are trying to create custom AppBar, you can implements PreferredSizeWidget
class myAppBar extends StatelessWidget implements PreferredSizeWidget {
.... //get height using constructor.
#override
Widget build(BuildContext context) {
return Container(height:);
}
#override
Size get preferredSize => Size.fromHeight(height);
}
I have a page where the camera starts and I can record a video. The problem is when I rotate the camera. Then I end up in the main page. This also happens if I navigate to other pages and the change orientation.
Before adding the camera feature I enforced portrait mode so I did not discover this behaviour.
How can I avoid Navigator.pop when the orientation changes?
I am btw a rookie in Flutter.
EDIT: I have tried some different approaches, like adding WillPopScope. However, that is not triggered when rotating the camera.
It does work to navigate to the camera view using this approach:
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => CameraView()),
(Route<dynamic> route) => false,
);
But then there is no back button...
How can this be so complicated?
This is how I add navigation in the HomePage:
Widget startResultButton() {
return Padding(
padding: const EdgeInsets.all(5),
child: FractionallySizedBox(
widthFactor: _buttonWidthFactor,
heightFactor: _buttonHeightFactor,
child: TextButton(
style: getStyle(buttonColor),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return const ResultsMenuView();
},
),
).then((value) => _updateDisplay());
},
child: Row(
children: [
Flexible(child: getSvg("prev_sessions.svg")),
const Flexible(
child: Text(
"Analyse results",
textScaleFactor: textFactor,
)),
],
mainAxisAlignment: MainAxisAlignment.start,
),
),
),
);
}
And the ResultMainView looks like:
class ResultsMenuView extends StatelessWidget {
const ResultsMenuView({Key? key}) : super(key: key);
final double _buttonWidth = 0.9;
final double _heightFactor = 0.6;
Widget getBody() {
var sessionChart = Flexible(
child: NavigationButton(
"Session Overview",
buttonColor,
const SessionOverviewChart(),
_buttonWidth,
_heightFactor,
"overall_results.svg"),
);
var detailedChart = Flexible(
child: NavigationButton(
"Detailed Scores",
buttonColor,
const DetailedChartView(),
_buttonWidth,
_heightFactor,
"detailed_results.svg"),
);
var tableView = Flexible(
child: NavigationButton("History", buttonColor, const TableView(),
_buttonWidth, _heightFactor, "prev_sessions.svg"),
);
var trendCurveButton = Flexible(
child: NavigationButton(
"Trend Curve",
buttonColor,
const TrendCurveView(),
_buttonWidth,
_heightFactor,
"trend_curve.svg"),
);
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [sessionChart, detailedChart, trendCurveButton, tableView],
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: buttonColor,
title: const Text("Results Menu"),
),
body: SafeArea(
child: getBody(),
),
backgroundColor: backgroundColor,
);
}
}
How i pass data from showmodalbottomsheet to previous page. Below is the sample code, what i have tried is there is a button when i click it displays modalbottomsheet and when i click on Done button it should pass 1 value to previous page and also i have added setState on onTap press but still it is not changing on previous page unless i click on Flutter Hot Reload. How to solve this issue?
class TestPage extends StatefulWidget {
const TestPage({Key? key}) : super(key: key);
#override
_TestPageState createState() => _TestPageState();
}
class _TestPageState extends State<TestPage> {
String textResult = "";
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(textResult),
TextButton(
onPressed: () {
showModalBottomSheet(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topRight: Radius.circular(24.0),
topLeft: Radius.circular(24.0))),
context: context,
builder: (_) => StatefulBuilder(
builder: (BuildContext context, setState) {
return Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
onTap: () {
setState(() {
textResult = "1";
});
Navigator.pop(context);
},
child: Text(
'Done',
style: TextStyle(
color: Colors.red,
fontWeight: FontWeight.bold),
),
),
],
),
),
],
),
);
}),
);
},
child: Text('Press'))
],
),
),
),
),
);
}
}
#Canada2000's answer is ok. I'm just extending it.
The use StatefulBuilder inside showDialog() or showModalBottomSheet()+... is these widgets are separated from current context tree.
We use StatefulBuilder when we like to show changes on dialogs. StatefulBuilder has its own stateā¾ builder: (BuildContext context, setState) and calling setState is using StatefulBuilder's setstate.
Now let's say you want to change both UI, to do that you need to simply rename StatefulBuilder's state to something like SBsetState inside builder as #Canada2000 said.
to update the _TestPageState use setState((){})
to update on dialog UI, use StatefulBuilder's state like SBsetState((){})
You may can simple ignore StatefulBuilder if you don't want to show any changes on dialog.
Your widget
import 'package:flutter/material.dart';
class TestPage extends StatefulWidget {
const TestPage({Key? key}) : super(key: key);
#override
_TestPageState createState() => _TestPageState();
}
class _TestPageState extends State<TestPage> {
String textResult = "";
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("text Result ${textResult}"),
TextButton(
onPressed: () {
showModalBottomSheet(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topRight: Radius.circular(24.0),
topLeft: Radius.circular(24.0))),
context: context,
builder: (_) => StatefulBuilder(
builder: (BuildContext context, setStateBTS) {
return Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
onTap: () {
setState(() {
textResult = "1";
});
Navigator.pop(context);
},
child: Text(
'Done',
style: TextStyle(
color: Colors.red,
fontWeight: FontWeight.bold),
),
),
],
),
),
],
),
);
}),
);
},
child: Text('Press'))
],
),
),
),
),
);
}
}
Try to change this line:
builder: (BuildContext context, setState) {
with this line
builder: (BuildContext context, StateSetter setStateModal) {
Explanation:
The outer widget in the build (Scaffold and its content) and the widget inside the showModalBottomSheet have different setState( ). You have to make sure you call the outer one when you need to update parts of that widget. The idea is to setState( ) of widgets outside your showModalBottomSheet. To be able to do that, you can give the two setState() different names so you know which one you are calling: StateSetter setStateModal
I'm struggling in a Flutter situation with multiple navigators. Concept here is a page which triggers a modal with his own flow of multiple pages. Inside the modal everything is going swiftly (navigation pushes/pops are working), but if the modal is dismissed it removes every page of the lowest navigator. I've looked at the example of https://stackoverflow.com/a/51589338, but I'm probably missing something here.
There's a wrapper Widget inside a page which is the root of the application.
class Wrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: Padding(
padding: EdgeInsets.only(top: 10, bottom: 20),
child: FlatButton(
child: Padding(
padding: EdgeInsets.symmetric(vertical: 5),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
'Open modal',
),
],
),
),
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
settings: RouteSettings(name: '/modal'),
builder: (context) => Modal(),
),
);
},
),
),
);
}
}
The modal initiates a Scaffold with his own navigator to handle its own flow.
class Modal extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: new Navigator(
initialRoute: FirstModalPage.route().toString(),
onGenerateRoute: (routeSettings) {
final path = routeSettings.name;
if (path == '/secondmodalpage') {
return MaterialPageRoute(
settings: routeSettings,
builder: (_) => SecondModalPage(),
);
}
return new MaterialPageRoute(
settings: routeSettings,
builder: (_) => FirstModalPage(),
);
},
),
);
}
}
The pages inside the modal are below with a close button which should remove the modal from the root navigator. But for some reason if I call pop() it removes all pages from the widget tree. And popUntil((route) => route.isFirst) doesn't do anything at all. If I'm looking at the widget via the Widget Inspector it does say that the Wrapper and Modal are on the same level.
class FirstModalPage extends StatelessWidget {
static Route route() {
return MaterialPageRoute<void>(builder: (_) => FirstModalPage());
}
#override
Widget build(BuildContext context) {
return Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Padding(
padding: EdgeInsets.only(
top: 30,
right: 20,
bottom: 10,
left: 20,
),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
if (Navigator.of(context).canPop()) {
Navigator.of(context).pop();
}
},
child: Text('Back'),
),
Text(
'First modal page title'
),
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
Navigator.of(context, rootNavigator: true).popUntil(
(route) => route.isFirst,
);
},
child: Text('Close'),
),
],
),
),
FlatButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => SecondModalPage()),
);
},
child: Text(
'Second modal page'
),
),
],
),
);
}
}
You'd need to use a Navigator key to keep tab on which Navigator you need to do an action with.
final _mainNavigatorKey = GlobalKey<NavigatorState>();
Navigator(
key: _mainNavigatorKey,
...
),
To use the Navigator Key, you can call _mainNavigatorKey.currentState.pop();
In my flutter app I want to show the popup with two buttons when user presses a button, I'm doing it with the following code:
class ProfileScreen extends StatefulWidget {
#override
_ProfileScreenState createState() {
return _ProfileScreenState();
}
}
class _ProfileScreenState extends State<ProfileScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
padding: EdgeInsets.all(16),
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: 400),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
...[
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: ElevatedButton(
onPressed: () {
showAlertDialog(context);
},
child: Text('Remove account'),
),
),
and the code for showAlertDialog is as follows:
showAlertDialog(BuildContext context) {
// set up the buttons
Widget cancelButton = FlatButton(
child: Text("Cancel"),
onPressed: () {
Navigator.of(context).pop();
},
);
Widget continueButton = FlatButton(
child: Text("Continue"),
onPressed: () {},
);
// set up the AlertDialog
AlertDialog alert = AlertDialog(
title: Text("AlertDialog"),
content: Text("Would you like to continue learning how to use Flutter alerts?"),
actions: [
cancelButton,
continueButton,
],
);
// show the dialog
showDialog(
context: context,
builder: (BuildContext context) {
return alert;
},
);
}
It works, the popup shows correctly, but when I click cancel, popup stays up front, but the screen beneath it goes away (and it stays black). Why so? And how could I fix it? Thanks!
Navigator.of(context, rootNavigator: true).pop();