I have an AlertDialog that contains a TextField and a PopupMenuButton. When the TextField has focus and the keyboard is open, the AlertDialog is in a "raised" position, but if I press the PopupMenuButton, the TextField get unfocus, and the animations of the AlertDialog "going down" (because the keyboard disappeard) and the PopupMenuButton opening start togheter, and result in a wrong position of PopupMenuItems. How can I solve this?
I tried to edit the PopupMenuButton class but I don't know how to wait until the AlertDialog is repositioned to show the popup menu.
This is a sample of code that replicate the problem
return AlertDialog(
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(),
PopupMenuButton(itemBuilder: (BuildContext context)=>[PopupMenuItem(child: Text('123')),PopupMenuItem(child: Text('456'))])
],
),
);
This is a gif showing the bug:
https://i.stack.imgur.com/dNjq1.gif
after getting some hours for researching that was PopupMenuButton normal behaviour, when I tried to recreate the PopupMenuButton manually, it work as that because when clicked it get the first position of the below menubutton so when i recreate it, it look like this
class TestWidget extends StatefulWidget {
#override
_TestWidgetState createState() => _TestWidgetState();
}
class _TestWidgetState extends State<TestWidget> {
void _showPopupMenu(Offset offset) async {
double left = offset.dx;
double top = offset.dy;
await showMenu(
context: context,
position: RelativeRect.fromLTRB(left, top, 0, 0),
items: [
const PopupMenuItem<String>(value: 'Doge', child: Text('Doge')),
const PopupMenuItem<String>(value: 'Lion', child: Text('Lion')),
],
elevation: 8.0,
);
}
#override
Widget build(BuildContext context) {
return AlertDialog(
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
const TextField(),
GestureDetector(
behavior: HitTestBehavior.opaque,
onTapDown: (TapDownDetails details) {
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.unfocus();
}
Future.delayed(const Duration(milliseconds: 500), () {
setState(() {
_showPopupMenu(details.globalPosition);
});
});
},
child: (const Icon(Icons.blender_outlined)),
),
PopupMenuButton(
itemBuilder: (BuildContext context) => [
const PopupMenuItem(child: Text('123')),
const PopupMenuItem(child: Text('456'))
])
],
),
);
}
}
so i think there's no better option for it. prefer not using popup and just design the design in a singlechildscrollview
Related
I want to enable users to take actions outside the bottom sheet as well. For example in the image shown below, I want the user to make a tap on the play button. I cannot do that now since the barrier of the bottomsheet is blocking the click to the play button.
From my research, the bottom sheet is a full-screen component. It appears to be of height half screen is a deception. (By making barrier color transparent and adding height constraints).
showModalBottomSheet(
context: context,
isDismissible: false,
barrierColor: Colors.transparent,
builder: (_) {
return GestureDetector(
behavior: HitTestBehavior.translucent,
child: Container(
constraints: BoxConstraints(maxHeight: height),
color: Theme.of(context).cardColor,
child: child.....
),
);
},
isScrollControlled: true,
);
I solved it like bottom sheet but with a different method.
I hope I got the question right.
import 'package:flutter/material.dart';
class BottomSheetOutside extends StatefulWidget {
#override
_BottomSheetOutsideState createState() => _BottomSheetOutsideState();
}
class _BottomSheetOutsideState extends State<BottomSheetOutside> with SingleTickerProviderStateMixin {
AnimationController _controller;
Animation<Offset> offset;
#override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: Duration(seconds: 1));
offset = Tween<Offset>(begin: Offset(0.0, 1.0), end: Offset.zero).animate(_controller);
}
#override
void dispose() {
super.dispose();
_controller.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Expanded(flex: 4, child: buildTop),
Expanded(flex: 6, child: buildBottom),
],
),
);
}
Container get buildTop {
return Container(
width: double.infinity,
color: Colors.red,
child: IconButton(
icon: Icon(Icons.play_circle_fill_rounded),
onPressed: () {
switch (_controller.status) {
case AnimationStatus.dismissed:
_controller.forward();
break;
case AnimationStatus.forward:
break;
case AnimationStatus.reverse:
break;
case AnimationStatus.completed:
_controller.reverse();
break;
}
},
),
);
}
Stack get buildBottom {
return Stack(children: [
Container(color: Colors.blue),
buildBottomSlide,
]);
}
Widget get buildBottomSlide {
return SlideTransition(
position: offset,
child: Card(
child: ListView.builder(
itemCount: 10,
itemBuilder: (context, index) => ListTile(
leading: Icon(Icons.comment),
title: Text("Test $index"),
),
),
),
);
}
}
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();
Trying to make a TextFormField unfocus with a GestureDetector when user taps outside but I can't get it to work. onTap never fires.
class EditScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: new GestureDetector(
onTap: () {
print('this does not fire, why???????????');
// this is my attempt to unfocus textformfield when click away
FocusScope.of(context).requestFocus(new FocusNode());
},
child: SingleChildScrollView(
child: Column(
children: <Widget>[
TextFormField(
maxLines: null,
),
],
),
),
),
);
}
}
Try wrap your Scaffold with Gesture Detector and then onTap function:
onTap: () {
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.unfocus();
}
So it will fire everytime you tap the scaffold
I wrote a Widget that contains a Button. On tap on this Button, a small "box" is shown with some text information inside.
I solved this with an Overlay. Inside of this Overlay is a GestureDetector. When you tap on the Overlay the Overlay will be hidden.
Because the Overlay is not filling the whole screen it will not be closed if you tap behind this Overlay.
But I want that the Overlay will be hidden when you tap somewhere on the screen, maybe outside of the Overlay.
Here is the part of the Overlay:
class _OverlayHelpWidgetState extends State<OverlayHelpWidget> {
OverlayEntry _overlayEntry;
OverlayState _overlayState;
bool _isVisible = false;
#override
Widget build(BuildContext context) {
return Row(
children: [
Center(
child: GestureDetector(
child: Icon(
Icons.help,
color: Colors.green[800],
),
onTap: () {
showHelp();
},
),
)
],
mainAxisAlignment: MainAxisAlignment.end,
);
}
OverlayEntry _createOverlayEntry() {
return OverlayEntry(
builder: (context) => Align(
alignment: Alignment.center,
child: GestureDetector(
child: _createContent(),
onTap: () {
hideHelp();
},
),
),
);
}
showHelp() async {
if (!_isVisible) {
_overlayState = Overlay.of(context);
_overlayEntry = _createOverlayEntry();
_overlayState.insert(_overlayEntry);
_isVisible = true;
}
}
void hideHelp() {
_isVisible = false;
_overlayEntry.remove();
}
Can someone help me to solve it?
thanks, Bhuelse
Thanks pskink,
thats the solution. Changed to this and it works:
OverlayEntry _createOverlayEntry() {
return OverlayEntry(
builder: (context) => GestureDetector(
behavior: HitTestBehavior.translucent,
child: Align(
alignment: Alignment.center,
child: _createContent(),
),
onTap: () {
hideHelp();
},
),
);
}
In my application, I want to have a custom Navigation (only change part of the screen and keep an history of what I'm doing inside it).
For that purpose, I am using a Navigator and it's working fine for simple navigation.
However, I want to handle the back button of Android.
There is a problem with it in Flutter apparently which forces me to handle the backbutton in the parent widget of the Navigator :
https://github.com/flutter/flutter/issues/14083
Due to that, I need to retrieve the instance of my Navigator in the children and call pop() on it. I am trying to use a GlobalKey for this.
I am trying to make it work for a while now and made a sample project just to test this.
Here is my code :
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(title: 'Navigation Basics', home: MainWidget()));
}
class MainWidget extends StatelessWidget {
final GlobalKey<NavigatorState> navigatorKey = GlobalKey();
#override
Widget build(BuildContext context) {
return SafeArea(
child: WillPopScope(
onWillPop: () => navigatorKey.currentState.maybePop(),
child: Scaffold(
body: Padding(
child: Column(
children: <Widget>[
Text("Toto"),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Expanded(
child: RaisedButton(
child: Text('First'),
onPressed: () {
navigatorKey.currentState.pushNamed('/first');
// Navigator.push(
// context,
// MaterialPageRoute(builder: (context) => SecondRoute()),
// );
},
)),
Expanded(
child: RaisedButton(
child: Text('Second'),
onPressed: () {
navigatorKey.currentState.pushNamed('/second');
},
))
],
),
Expanded(
child: Stack(
children: <Widget>[
Container(
decoration: BoxDecoration(color: Colors.red),
),
ConstrainedBox(
constraints: BoxConstraints.expand(),
child: _getNavigator()),
],
)),
],
),
padding: EdgeInsets.only(bottom: 50),
))));
}
Navigator _getNavigator() {
return Navigator(
key: navigatorKey,
initialRoute: '/',
onGenerateRoute: (RouteSettings settings) {
WidgetBuilder builder;
switch (settings.name) {
case '/':
builder = (BuildContext _) => FirstRoute();
break;
case '/second':
builder = (BuildContext _) => SecondRoute();
break;
default:
throw new Exception('Invalid route: ${settings.name}');
}
return new MaterialPageRoute(builder: builder, settings: settings);
});
}
}
class FirstRoute extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: Column(
children: <Widget>[
RaisedButton(
child: Text("GO TO FRAGMENT TWO"),
onPressed: () => Navigator.of(context).pushNamed("/second"),
)
],
),
decoration: BoxDecoration(color: Colors.green),
);
}
}
class SecondRoute extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: Column(
children: <Widget>[
RaisedButton(
child: Text("GO TO FRAGMENT ONE"),
onPressed: () => Navigator.of(context).pop(),
)
],
),
decoration: BoxDecoration(color: Colors.blue),
);
}
}
This is however not working as I would like. The default Navigator seem to still be used : after opening the SecondRoute and pressing the Android back button, it just leaves the app instead of just going back to the first route.
How can I achieve what I want?
Following the documentation of onWillPop:
/// Called to veto attempts by the user to dismiss the enclosing [ModalRoute].
///
/// If the callback returns a Future that resolves to false, the enclosing
/// route will not be popped.
final WillPopCallback onWillPop;
your handler should indicate that the enclosing route should not be closed, hence returning false will resolve your issue.
changing your handler to this works:
onWillPop: () async {
navigatorKey.currentState.maybePop();
return false;
},