I have a filters button. It's a container with a border, and inside are two icons in a row. All of this is wrapped in a gesture detector.
When the user taps the gesture detector, I am showing a bottom modal sheet. This all works. However, I want one of the icons to change depending on whether the user has the bottom modal sheet activated or not. How can I achieve this?
I think I can call setState once the button is tapped, BEFORE showing the modal bottom sheet. How can call setState again when the user taps out of it?
Thanks!
Is there an option to configure this in the showModalBottomSheet constructor?
As you did not post any code I'm guessing that you are using the method showModalBottomSheet.
This method has the following prototype:
Future<T?> showModalBottomSheet<T>({
required BuildContext context,
required WidgetBuilder builder,
Color? backgroundColor,
double? elevation,
ShapeBorder? shape,
Clip? clipBehavior,
BoxConstraints? constraints,
Color? barrierColor,
bool isScrollControlled = false,
bool useRootNavigator = false,
bool isDismissible = true,
bool enableDrag = true,
RouteSettings? routeSettings,
AnimationController? transitionAnimationController,
})
source
As you can see it returns a Future<T?> type which means that you can apply await, then or whenComplete to the returned future operation. In your case using whenComplete might be the better option if you don't need any value from the bottom sheet.
Example
showModalBottomSheet<void>(
context: context,
builder: (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),
)
],
),
),
);
},
).whenComplete(_onBottomSheetClosed);
void _onBottomSheetClosed() {
print("Closed bottomsheet");
}
Try the full example on DartPad
I was searching how to dismiss bottom sheet after navigation through it to a second screen , I found out that bottom sheet gets also added to navigation stack so ,
just use pushReplacement:
Navigator.pushReplacement(
context,PageTransition(type: PageTransitionType.leftToRight, child: Page()));
hope this help someone.
I am using IconButtons in my application for opening popups, deleting ListTiles from ListViews, navigating to other views etc.
To my surprise the onPressed callback always gets triggered before the animation starts to play resulting in that the animation will not play at all. This is confusing to the user because there is no feedback that he pushed the button.
The setup looks something like this.
This is the code for the add button in the top right corner:
IconButton(
onPressed: () {
showDialog(
context: context,
builder: (context) {
return AddDevicePopup(
...
).build(context);
});
},
icon: const Icon(
Icons.add_circle_outline_rounded,
size: 30.0,
)
)
When the user taps the button the popup immediately appears but the animation won't get played.
This is the same for the IconButtons on the tiles:
the edit button opens a popup immediately and the ripple or splash animation won't play.
the delete button removes the element from the list. In this case the ListTile element gets removed and so the IconButton disappears without the animation getting played.
In every case there is a Material widget that is an ancestor of the IconButton so wrapping it in a Material widget does not solve the problem. I have double checked it by removing the showDialog part from the callback, in these cases all of the IconButtons play the splash / ripple animation as expected.
How do I make the onPressed to wait for the animation to at least be started. I did not find any solution. Is is possible to trigger this animation via code? In that case I can add that to the beginning of the onPressed callback.
To add animations, you must use showGeneralDialog instead of showDialog.
The implementation looks like this:
showGeneralDialog(
barrierDismissible: true,
barrierColor: Colors.black.withOpacity(0.5),
transitionDuration: const Duration(milliseconds: 200),
context: context,
pageBuilder: (_, __, ___) {
return AddDevicePopup(
...
).build(context);
},
//Customize your animation here
transitionBuilder: (_, a1, __, child) {
return Transform.scale(
scale: a1.value,
child: Opacity(
opacity: a1.value,
child: child,
),
);
},
);
No code for this one but a question. I'm trying to create a custom animation which is a stack of 3 elements that slide in from the bottom of the screen. How would I go about positioning it above all other elements so that it isn't obscured from being viewed. Initially it is positioned off the screen, please advise.
To achieve the result you want I would use Stack in combination with AnimatedPositioned widgets
Stack(
children: [
AnimatedPositioned(
...
...
child: YourWidget()
),
AnimatedPositioned(
...
...
child: YourWidget()
),
],
)
Read more in the docs.
API docs about AnimatedPositioned
API docs about Stack
From what you said, I would suggest using a ModalBottomSheet.
You would use it like:
List<BottomSheetAction> actions = [];
actions.add(BottomSheetAction(
"My clickable Action",
null,
Icons.add, () {
_actionToTrigger();
}));
final _actionMenu = new WidgetBottomSheetMenu(
title: 'Header',
actions: actions);
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (context) {
return SingleChildScrollView(child: _actionMenu);
});
The showModalBottomSheet has a
transitionAnimationController
property which you could define your custom animation with an AnimationController
I hope I can make this kind of pop-up effect similar to dropDownMenuButton. Is there any plugin or example that can be used for reference or help?
If you want a floating window i would suggest using a dialog.
To use a dialog you use a function and provide it with a context
This example should show a rounded corned white dialog with "YAY!" in the middle of it.
You close the dialog calling Navigator.of(context).pop() , and You could also use the align widget combined with padding to make it appear to the bottom, top, left, and right.
Touching on the backdrop of the dialog will also dismiss it.
void _showDialog(context) {
showDialog(
context: context,
builder: (BuildContext context) {
// return my custom widgets
return Center(
child: SizedBox(
width: 250.0,
height: 250.0,
child: Material( //Use material to use FlatButton, IconButton, InkWell, etc.
borderRadius: BorderRadius.all(Radius.circular(12.0)),
color: Colors.white,
child: Text('YAY!')
)));
},
);
}
Feel free to experiment.
I have a home screen with a FAB and when it's pressed I want to display a dialog for user to input.
Currently I am using showDialog() with SimpleDialog.
showDialog(
context: context,
builder: (BuildContext context) {
return SimpleDialog(
title: NormalText('New Counter Item'),
contentPadding: EdgeInsets.fromLTRB(24.0, 0.0, 24.0, 24.0),
children: <Widget>[
Container(
...
)
],
);
}
);
But, I can't seem to customise almost anything with it (smaller, corner-curved and positioned lower on the screen). AlertDialog seems to be the same.
Is there anyway to customise those attributes?
return showDialog<void>(
barrierDismissible: true,
context: context,
builder: (BuildContext context) {
return new Column(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(0),
child: new Container(
height: 100,
width: MediaQuery.of(context).size.width,
color: Colors.purple,
child: new Column(
children: <Widget>[
new Text(
'custom dialog text',
style: new TextStyle(
fontSize: 14,
color: Colors.white,
),
),
],
),
),
)
],
);
},
);
hope this helps,
thanks
SimpleDialog and AlertDialog are meant to cater to a specific set of needs. They have certain levels of customization, but ultimately they are meant to just show simple pop-up dialogs that give the user some information and prompt for a dialog response (i.e. "Yes", "No", "Cancel", "Save", etc.).
If you want to have a more customizable pop-up dialog, you will need to create your own. On the plus side, though, it's really quite simple. Have the builder in showDialog return a Dialog widget with a child set to whatever you want:
showDialog(
context: context,
builder: (BuildContext cxt) {
return Dialog(
child: Container(
...
),
);
},
);
Obviously you are going to need to recreate things like the title bar and action bar, but you can either crack open the source code of SimpleDialog and AlertDialog and copy what they have there or you can roll your own solution.
Despite what the accepted answer says; there are ways to customise the SimpleDialog in the ways that you have requested.
Size
The SimpleDialog will grow in width/height depending on the child and how it is padded. The below code will generate a dialog of width 336 and height 300:
showDialog(
context: context,
builder: (BuildContext context) {
return SimpleDialog(
insetPadding: EdgeInsets.zero,
titlePadding: EdgeInsets.zero,
contentPadding: EdgeInsets.zero,
children: [
Container(
width: 300,
height: 300,
),
],
);
},
);
Not sure what the added 36 pixels to the width is; but this demonstrates that size can be customised.
Corners
Corners can be customised using the shape property. The below code shows a green border with a rounded edge of 4 pixels:
showDialog(
context: context,
builder: (BuildContext context) {
return SimpleDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4.0),
side: BorderSide(
color: Colors.green,
),
),
/* ... */
);
},
);
Position
I've found that I can nudge the dialog up and down the page by using the insetPadding property. This defines the minimum amount of space required between the edge of the screen and the dialog. Although it's a bit cumbersome, if you knew the size of the screen and the size of the dialog, you could nudge the dialog's position with some simple math.
Given a screen height of 1920px, and a dialog height of 300px, the below code should place your dialog 100px from the top edge of your screen, rather than bang in the centre:
showDialog(
context: context,
builder: (BuildContext context) {
return SimpleDialog(
insetPadding: EdgeInsets.only(bottom: 1520),
/* ... */
);
},
);
This is because I've requested a minimum padding between the bottom edge of the screen and the dialog; and the only position for the dialog to exist under this stipulation is nearer the top.
No. These are not designed to be customizable. They were made to respect Material Design principles in mind.
If you want a custom design, you can make your own modal based of Align and/or DecoratedBox
It's not as scary as you might expect.
You only need to clone Dialog.dart, and
replace the Center widget with an Align.
Of course also rename stuff; e.g. MyDialog, myShowDialog, MySimpleDialog.
Yep, it's that easy.
And if you're on a roll, how about adding the Align widget's alignment parameter as an extra...
You can actually modify it's position (only height) by adding a SingleChildScrollView() in the builder + a padding (for the offset from the top) as follows:
showDialog<String>(
context: context,
builder: (ctxDialog) =>
SingleChildScrollView(
child: Padding(
padding: EdgeInsets.only(top: 150.0),
child: AlertDialog()
)
)
);
Here is a clean solution, If for some reason you have a TextField/TextFormField and in there you have a onsubmit property then you can follow:
onSubmit: () {
showDialog(
context: context,
builder: (context){
return WillPopScope(
onWillPop: () { return Future(() => false); }, // this will prevent going back
child: AlertDialog(
content: Row(
children: [
progressWidget(),
Container(margin: EdgeInsets.only(left: 10.0),child:Text("Loading" )),
],),
)
);
}
);
yourCallToMethod.whenComplete((){
Navigator.of(context).pop();
});
},
BENEFIT?
When the showAlert is shown if someone tap on the screen randomly or aggressively then the screen goes back. This prevents that behavior and only closes the showAlert when your function completes.
:- we can change vertical position of dialog boxes by give bottom
insetPadding like
insetPadding: EdgeInsets.only(bottom: XX);
:- And horizontal position by Horizontal padding or left
insetPadding: EdgeInsets.symmetric(horizontal:XX);
or
insetPadding: EdgeInsets.only(left: XX,right:YY);
I found the answer to it here. Thanks to #CopsOnRoad
Using showGeneralDialog
Scaffold(
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
showGeneralDialog(
context: context,
barrierColor: Colors.black54,
barrierDismissible: true,
barrierLabel: 'Label',
pageBuilder: (_, __, ___) {
return Align(
alignment: Alignment.bottomLeft,
child: Container(
color: Colors.blue,
child: FlutterLogo(size: 150),
),
);
},
);
},
),
)
Solved that with wrapping dialog body in Stack and Positioned
Stack(
children: [
Positioned(
left: 100, // left coordinate
top: 100, // top coordinate
child: YOUR_DIALOG....