How do I disable / escape drag down gesture within the Bottom Sheet Modal so the user can interact within the modal without accidentally closing the modal?
Updated below with the actual modal bottom sheet.
return showModalBottomSheet(
context: context,
builder: (BuildContext context) {
...
}
}
Set enableDrag to false
bool enableDrag
If true, the bottom sheet can dragged up and down and dismissed by swiping downwards.
https://docs.flutter.io/flutter/material/BottomSheet/enableDrag.html
you can try to wrap builder's result with GestureDetector with onVerticalDragStart = (_) {}
showModalBottomSheet(
context: context,
builder: (context) => GestureDetector(
child: **any_widget_here**,
onVerticalDragStart: (_) {},
),
isDismissible: false,
isScrollControlled: true,
);
If you still want to have the scroll inside the modal without the user drag and close it, you can use this:
showModalBottomSheet(
context: context,
enableDrag: false,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(20),
),
),
clipBehavior: Clip.antiAliasWithSaveLayer,
builder: (context) => DraggableScrollableSheet(
expand: false,
initialChildSize: 0.9,
minChildSize: 0.5,
maxChildSize: 0.9,
builder: (context, scrollController) {
return SingleChildScrollView(
child: new Container(
color: Colors.white,
child: buildTitleWidget(),
),
);
},
),
isDismissible: false,
isScrollControlled: true,
);
The trick is not to add scrollController to the SingleChildScrollView
builder: (context, scrollController) {
return SingleChildScrollView(
controller: scrollController <-- HERE
child: new Container(
color: Colors.white,
child: buildTitleWidget(),
),
);
},
I wanted a bottomsheet that is draggable up and down, but does not close on dragging down. My thought was what if as soon as it closes, I call it again? Here, recursion comes to rescue. So, first of all I created a function for my modalBottomSheet.
Future modalBottomSheetShow(BuildContext context) {
return showModalBottomSheet(
backgroundColor: Colors.transparent,
context: context,
builder: (context) => buildSheet(),
isDismissible: false,
elevation: 0,
);
}
Next, I used .whenComplete() method of showModalBottomSheet() to recursively call the modalBottomSheetShow() function.
Future modalBottomSheetShow(BuildContext context) {
return showModalBottomSheet(
backgroundColor: Colors.transparent,
context: context,
builder: (context) => buildSheet(),
isDismissible: false,
elevation: 0,
).whenComplete(() => modalBottomSheetShow(context));
}
Next, I simply call the modalBottomSheetShow() whenever I wanted a bottomsheet. It cannot be closed, until the recursion ends. Here is the entire code for reference:
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
static const idScreen = "HomePage";
#override
State<HomePage> createState() => _HomePageState();
}
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
modalBottomSheetShow(context);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
toolbarHeight: 0,
elevation: 0,
backgroundColor: Colors.black,
),
);
}
Widget buildSheet() {
return DraggableScrollableSheet(
initialChildSize: 0.6,
builder: (BuildContext context, ScrollController scrollController) {
return Container(
decoration: BoxDecoration(color: Colors.white, boxShadow: [
BoxShadow(
color: Color(0x6C000000),
spreadRadius: 5,
blurRadius: 20,
offset: Offset(0, 0),
)
]),
padding: EdgeInsets.all(16),
);
},
);
}
Future modalBottomSheetShow(BuildContext context) {
return showModalBottomSheet(
backgroundColor: Colors.transparent,
context: context,
builder: (context) => buildSheet(),
isDismissible: false,
elevation: 0,
).whenComplete(() => modalBottomSheetShow(context));
}
}
Related
I have a flutter app and I do experience a strange behaviour when the keyboard get activated in my iPhone. As you can see from the picture below there is a white background which appears under the keyboard while it is popping up...
Does anyone know how to set that background color???
EDIT:
Thanks for the suggestion to use resizeToAvoidBottomPadding. That command is deprecated and I have replaced with resizeToAvoidBottomInset: false. This has resolved the issue (yup!) and there is no more white background under the keyboard BUT it has created another strange effect. There is now a dark-blue bar above the keyboard. Check the next image... I have set an orange background color so you can see it better. You can also see that the floating button is shifted up which means the dark-blu bar is now between the keyboard and the scaffold. Do you know how I can get rid of that bar? this seems to be worst Thant the white background since it is taking precious screen space..
For reference the scaffold is nested in a CupertinoTabScaffold
Here is the most relevant part of my code
void main() async {
Crashlytics.instance.enableInDevMode = true;
FlutterError.onError = Crashlytics.instance.recordFlutterError;
runApp(ChangeNotifierProvider<Data>(
builder: (context) => Data(),
child: new CupertinoApp(
theme: CupertinoThemeData(
barBackgroundColor: kColorPrimary,
primaryColor:
kColorText,
textTheme: CupertinoTextThemeData(
primaryColor:
kColorText,
navLargeTitleTextStyle: TextStyle(
fontWeight: FontWeight.bold, fontSize: 70.0, color: kColorText),
),
),
home: new CheckIfFirstTime(),
),
));
}
class CheckIfFirstTime extends StatefulWidget {
#override
_CheckIfFirstTimeState createState() => _CheckIfFirstTimeState();
}
class _CheckIfFirstTimeState extends State<CheckIfFirstTime> {
Future checkFirstSeen() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
bool _seen = (prefs.getBool('seen') ?? false);
if (_seen) {
Navigator.of(context).pushReplacement(
new MaterialPageRoute(builder: (context) => new HomeScreen()));
}
}
#override
void initState() {
super.initState();
checkFirstSeen();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: kColorPrimary,
body: Text(
'loading...',
style: kSendButtonTextStyle,
),
);
}
}
class HomeScreen extends StatefulWidget {
static const String id = 'home';
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
int currentIndex = 0;
void confirmPlatform() {
if (Platform.isIOS)
appData.isIOS = true;
else
appData.isIOS = false;
}
#override
void initState() {
// TODO: implement initState
super.initState();
confirmPlatform();
}
#override
Widget build(BuildContext context) {
return CupertinoTabScaffold(
resizeToAvoidBottomInset: false,
tabBar: CupertinoTabBar(
backgroundColor: kColorPrimaryLight,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.search),
title: Text('Discover', style: TextStyle(fontFamily: kFontFamily)),
),
BottomNavigationBarItem(
icon: Badge(
showBadge: Provider.of<Data>(context).filterCounter == 0
? false
: true,
badgeContent: Text(
Provider.of<Data>(context).filterCounter.toString(),
style: TextStyle(color: kColorText),
),
child: Icon(Icons.filter_list)),
title: Text('Filters', style: TextStyle(fontFamily: kFontFamily)),
),
BottomNavigationBarItem(
icon: Icon(Icons.account_circle),
title: Text('Me', style: TextStyle(fontFamily: kFontFamily)),
),
BottomNavigationBarItem(
icon: Icon(Icons.assignment),
title: Text('Stories', style: TextStyle(fontFamily: kFontFamily)),
),
//with badge
BottomNavigationBarItem(
icon: Badge(
showBadge: Provider.of<Data>(context).basketCounter == '0'
? false
: true,
badgeContent: Text(
Provider.of<Data>(context).basketCounter,
style: TextStyle(color: kColorText),
),
child: Icon(Icons.shopping_cart)),
title: Text('Basket', style: TextStyle(fontFamily: kFontFamily)),
),
],
),
tabBuilder: (context, index) {
if (index == 0) {
return CupertinoTabView(
navigatorKey: firstTabNavKey,
builder: (BuildContext context) => DiscoverScreenFinal(),
);
} else if (index == 1) {
return CupertinoTabView(
navigatorKey: secondTabNavKey,
builder: (BuildContext context) => FilterScreen(),
);
} else if (index == 2) {
return CupertinoTabView(
navigatorKey: thirdTabNavKey,
builder: (BuildContext context) => WelcomeScreen(),
);
} else if (index == 3) {
return CupertinoTabView(
navigatorKey: forthTabNavKey,
builder: (BuildContext context) => Stories(),
);
}
{
return CupertinoTabView(
navigatorKey: fifthTabNavKey,
builder: (BuildContext context) => Basket(),
);
}
},
);
}
}
class DiscoverScreenFinal extends StatefulWidget {
#override
_DiscoverScreenFinalState createState() => _DiscoverScreenFinalState();
}
class _DiscoverScreenFinalState extends State<DiscoverScreenFinal> {
#override
Widget build(BuildContext context) {
return TopBarAgnostic(
text: 'Discover',
style: kSendButtonTextStyle,
firstIcon: Icon(Icons.blur_on),
firstIconDestination: CameraApp(),
scannerOn: true,
secondtIcon: Icon(Icons.location_on),
secondIconDestination: MapPage(),
uniqueHeroTag: 'discover001a',
child: Scaffold(
resizeToAvoidBottomInset: false,
backgroundColor: kColorPrimary,
floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
floatingActionButton: FloatingActionButton.extended(
backgroundColor: kColorAccent,
onPressed: () {
//…
},
label: Text(
'To Favorites',
style: kDescriptionTextStyle.copyWith(
fontSize: kFontSizeSmall, fontWeight: FontWeight.bold),
),
icon: Icon(Icons.favorite),
),
body: Container()
),
);
}
}
You have to use this line in Scaffold, it will adjust your view automatically when keyboard is appear and disappear.
resizeToAvoidBottomPadding: false
You could set a backgroundColor to the Scaffold to replace that white background.
This happens because the body of the Scaffold gets resized when you open the keyboard. If you want to avoid the resizing you could set resizeToAvoidBottomInset: false to the Scaffold.
after flutter 2 use this in the Scaffold
resizeToAvoidBottomInset: false,
Best solution is creating a Stack widget with two children: a Container for the background (color, gradient, background image...) and a Scaffold for the app content.
Widget build(BuildContext context) {
return Stack(
children: [
Container(
decoration: /* ... Background styles ... */,
),
Scaffold(
extendBodyBehindAppBar: true,
appBar: AppBar(backgroundColor: Colors.transparent, elevation: 0),
body: Container(
padding: const EdgeInsets.only(top: 16, left: 16, right: 16),
child: SafeArea(bottom: false, child: /* ... App content ... */))
)
]);
}
Basically you can solve this problem by keeping Scaffold inside of Container, like this:
Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topRight,
end: Alignment(0.5, 0.3),
colors: [
Colors.red,
Colors.green,
],
),
),
child: Scaffold(/* ... rest of your code ... */)
)
if you are in develop app in java solve this by just adding android:windowSoftInputMode="adjustPan" in your activity present in the manifest file.
Thanks
you can use
child: Scaffold(
backgroundColor: Color(0xff53ccb2),
I want to have a scrollbar view like the one in the picture.
So how can I have it using flutter?
I've tried
SingleChildScrollView(
....
),
But no scroll bars appeared and I don't know to make them
You can use this project
The pubspec.yaml
dev_dependencies:
flutter_test:
sdk: flutter
draggable_scrollbar: 0.0.4
The code:
import 'package:flutter/material.dart';
import 'package:draggable_scrollbar/draggable_scrollbar.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
ScrollController _rrectController = ScrollController();
#override
Widget build(BuildContext context) {
return MaterialApp(
title: "Test",
home: Scaffold(
body: Center(
child: DraggableScrollbar.rrect(
controller: _rrectController,
backgroundColor: Colors.blue,
child: ListView.builder(
controller: _rrectController,
itemCount: 100,
itemExtent: 100.0,
itemBuilder: (context, index) {
return Container(
padding: EdgeInsets.all(8.0),
child: Material(
elevation: 4.0,
borderRadius: BorderRadius.circular(4.0),
color: Colors.green[index % 9 * 100],
child: Center(
child: Text(index.toString()),
),
),
);
},
),
),
),
),
);
}
}
The result:
Flutter now has Scrollbar widget.
Just wrap SingleChildScrollView or any ScrollView widget with Scrollbar.
Code sample:
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Scrollbar(
// Child can also be SingleChildScrollView, GridView, etc.
child: ListView.builder(
itemCount: 20,
itemBuilder: (context, index) {
return ListTile(
title: Text('Index $index'),
);
},
),
),
);
}
}
Scrollbar (Widget of the week) : https://youtu.be/DbkIQSvwnZc
Wrap the scrollable widget with Scrollbar widget.
child: Scrollbar(
isAlwaysShown: true,
child: ListView(
....)
)
You can make it always shown.
watch this for further explanation.
https://www.youtube.com/watch?v=DbkIQSvwnZc
Scrollbar(
isAlwaysShown: true,
showTrackOnHover: true,
radius: Radius.circular(5),
interactive: true,
child: ListView.builder(shrinkWrap: true, itemCount: data.productionReportModel!.data!.length, itemBuilder: (context, index) {
return Container();
}),
),
I want to make a full screen dialog box. Dialog box background must be opaque.
Here is an example:
How to make like this in Flutter?
You can use the Navigator to push a semi-transparent ModalRoute:
import 'package:flutter/material.dart';
class TutorialOverlay extends ModalRoute<void> {
#override
Duration get transitionDuration => Duration(milliseconds: 500);
#override
bool get opaque => false;
#override
bool get barrierDismissible => false;
#override
Color get barrierColor => Colors.black.withOpacity(0.5);
#override
String get barrierLabel => null;
#override
bool get maintainState => true;
#override
Widget buildPage(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) {
// This makes sure that text and other content follows the material style
return Material(
type: MaterialType.transparency,
// make sure that the overlay content is not cut off
child: SafeArea(
child: _buildOverlayContent(context),
),
);
}
Widget _buildOverlayContent(BuildContext context) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
'This is a nice overlay',
style: TextStyle(color: Colors.white, fontSize: 30.0),
),
RaisedButton(
onPressed: () => Navigator.pop(context),
child: Text('Dismiss'),
)
],
),
);
}
#override
Widget buildTransitions(
BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
// You can add your own animations for the overlay content
return FadeTransition(
opacity: animation,
child: ScaleTransition(
scale: animation,
child: child,
),
);
}
}
// Example application:
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Playground',
home: TestPage(),
);
}
}
class TestPage extends StatelessWidget {
void _showOverlay(BuildContext context) {
Navigator.of(context).push(TutorialOverlay());
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Test')),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Center(
child: RaisedButton(
onPressed: () => _showOverlay(context),
child: Text('Show Overlay'),
),
),
),
);
}
}
Well here is my implementation which is quite straightforward.
from first screen
Navigator.of(context).push(PageRouteBuilder(
opaque: false,
pageBuilder: (BuildContext context, _, __) =>
RedeemConfirmationScreen()));
at 2nd screen
class RedeemConfirmationScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white.withOpacity(0.85), // this is the main reason of transparency at next screen. I am ignoring rest implementation but what i have achieved is you can see.
.....
);
}
}
and here are the results.
Screenshot (Flutter's native dialog)
Call this method to show the dialog in fullscreen.
showGeneralDialog(
context: context,
barrierColor: Colors.black12.withOpacity(0.6), // Background color
barrierDismissible: false,
barrierLabel: 'Dialog',
transitionDuration: Duration(milliseconds: 400),
pageBuilder: (_, __, ___) {
return Column(
children: <Widget>[
Expanded(
flex: 5,
child: SizedBox.expand(child: FlutterLogo()),
),
Expanded(
flex: 1,
child: SizedBox.expand(
child: ElevatedButton(
onPressed: () => Navigator.pop(context),
child: Text('Dismiss'),
),
),
),
],
);
},
);
Note: This answer does not discuss making the modal transparent, but is an answer is for the stated question of "How to make a full screen dialog in flutter?". Hopefully this helps other that find this question through a search like I did, that don't need a transparent modal.
Create your modal dialog class:
class SomeDialog extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: const Text('Dialog Magic'),
),
body: new Text("It's a Dialog!"),
);
}
}
In the class that needs to open the dialog, add something like this:
void openDialog() {
Navigator.of(context).push(new MaterialPageRoute<Null>(
builder: (BuildContext context) {
return new SomeDialog();
},
fullscreenDialog: true));
}
If fullscreenDialog above is true, then the app bar will have an "x" close button. If false, it will have a "<-" back arrow.
If you need to get the result of a dialog action, add a button to your dialog that returns a value when popping the navigation stack. Something like this:
onPressed: () {
Navigator
.of(context)
.pop(new MyReturnObject("some value");
}
then in your class opening the dialog, do capture the results with something like this:
void openDialog() async {
MyReturnObject results = await Navigator.of(context).push(new MaterialPageRoute<MyReturnObject>(
builder: (BuildContext context) {
return new SomeDialog();
},
fullscreenDialog: true));
}
You can use showGeneralDialog method with any widget extends from Material like Scaffold, Card, ..etc.
For example I am going to it with Scaffold like this:
showGeneralDialog(
context: context,
pageBuilder: (context, animation, secondaryAnimation) => Scaffold(
backgroundColor: Colors.black87,
body: //Put your screen design here!
),
);
And now you can set your design as a normal screen by using Scaffold.
Note: if you want to go back you can Navigator like this:
Navigator.of(context).pop(null)
Different ways to show fullscreen dialog
A. Material Dialog
showDialog<void>(
context: context,
useSafeArea: false,
builder: (BuildContext context) {
return const SomeScaffoldView();
},
);
B. Cupertino Dialog
showCupertinoDialog<void>(
context: context,
builder: (BuildContext context) {
return const SomeScaffoldView();
},
);
C. Custom Dialog
Flutter uses this under-the-hood when displaying dialogs.
Can customize transition animation with transitionBuilder, here's a random guide with example animations.
showGeneralDialog(
context: context,
pageBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) {
return const SomeScaffoldView();
},
);
Sample Scaffold View used in above snippets.
class SomeScaffoldView extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Sample Fullscreen Dialog'),
),
body: const Center(child: Text('Dialog Body')),
);
}
}
You can use AlertDialog with zero insetPadding like below:
showDialog(
barrierDismissible: false,
context: context,
builder: (BuildContext context) {
return StatefulBuilder(builder: (context, setState) {
return AlertDialog(
insetPadding: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10.0))),
content: SizedBox.expand(
child: Column(
children: <Widget>[
SingleChildScrollView(
physics: BouncingScrollPhysics(),
child: Wrap(
children: <Widget>[
Row(
children: <Widget>[
Expanded(
flex: 1,
child: Text(
"Sample type",
style: TextStyle(fontWeight: FontWeight.w700),
),
),
Expanded(flex: 1, child: Text(""))
],
),
],
)),
],
),
));
});
},
);
RFlutter Alert is super customizable and easy-to-use alert/popup dialogs for Flutter. You may create reusable alert styles or add buttons as much as you want with ease.
Alert(context: context, title: "RFLUTTER", desc: "Flutter is awesome.").show();
RFlutter
It's easy to use! :)
you can do like this if you use popular flutter library getx
getx link
void showAlertDialogg(
String body,
String? confirmButtonText,
String? cancelButtonText,
Function(bool onConfirm, bool onCancel) clickEvent,
{barrierDismissible = false}) {
Get.dialog(
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextComponent(
body,
textAlign: TextAlign.center,
fontSize: textSmallFontSize,
fontWeight: titleFontWeight,
color: Colors.white,
),
Row(
//crossAxisAlignment : CrossAxisAlignment.center,
children: [
Expanded(
flex: 1,
child: OutlineButtonComponent(
text: cancelButtonText,
borderColor: kPrimaryColor,
onPressed: () {
Get.back();
clickEvent(false, true);
},
textColor: kPrimaryColor,
padding: EdgeInsets.fromLTRB(16, 16, 8, 16),
),
),
Expanded(
flex: 1,
child: ButtonComponent(
text: confirmButtonText,
buttonColor: kPrimaryColor,
onPressed: () {
Get.back();
clickEvent(true, false);
},
textColor: Colors.white,
padding: EdgeInsets.fromLTRB(8, 16,16, 16),
),
),
],
)
],
),
barrierColor: Colors.black12.withOpacity(0.8),
useSafeArea: true
);
}
you can pas params as you want & call this method where you need it. it supports widget so you can setup the widget as you want.
Wrap your top-level widget with Navigator widget like so:
return Navigator(
pages: [
MaterialPage(
child: MainScreen(
child: widgets...
then call showDialog and because useRootNavigator is set to true in default it will use the root navigator that we added above the MainScreen
I am trying to create a transparent full screen dialog on top of activity. I have tried following this thread but solution doesn't work.
In short , what I need is:
full screen dialog.
transparent background except for the widget I use for the dialog
here's my code:
To open dialog
void onNextBtnClick(){
var route = new MaterialPageRoute(
builder: (BuildContext context) =>
new GenreDialogUI(),fullscreenDialog: true
);
Navigator.of(context).push(route);
}
For Dialog view
import 'package:flutter/widgets.dart';
class HolePuncherPainter extends CustomPainter {
static final clearPaint = new Paint()
..color = Colors.transparent,
..blendMode = BlendMode.clear;
const HolePuncherPainter();
#override
void paint(Canvas canvas, Size size) {
canvas.drawRect(
new Rect.fromLTWH(0.0, 0.0, size.width, size.height), clearPaint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
class GenreDialogUI extends StatefulWidget {
#override
_GenreDialogUI createState() => new _GenreDialogUI();
}
class _GenreDialogUI extends State<GenreDialogUI> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: addAppbar(),
body: new CustomPaint(
painter: HolePuncherPainter(),
child: new Container(
color: Colors.transparent,
alignment: Alignment.center,
child: UtilCommonWidget.addText('Transparent Dialog', Colors.red, 22.0, 1),
),
),
);
}
}
Navigator.of(context).push(PageRouteBuilder(
opaque: false,
pageBuilder: (BuildContext context, _, __) {
return YourFullScreenAlertDialog()
}
));
YourFullScreenAlertDialog could be a widget that has a background color, Colors.transparent, like #creativecreatorormaybenot mentioned earlier.
For me the following worked:
showDialog(
context: context,
builder: (_) => Material(
type: MaterialType.transparency,
child: Center(
// Aligns the container to center
child: Container(
// A simplified version of dialog.
width: 100.0,
height: 56.0,
color: Colors.green,
child: Text('jojo'),
),
),
),
);
Screenshot (showGeneralDialog):
Code:
#override
Widget build(BuildContext context) {
return Scaffold(
body: SizedBox.expand(child: FlutterLogo()),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.open_in_new),
onPressed: () {
showGeneralDialog(
context: context,
barrierColor: Colors.black38,
barrierLabel: 'Label',
barrierDismissible: true,
pageBuilder: (_, __, ___) => Center(
child: Container(
color: Colors.white,
child: Material(
color: Colors.transparent,
child: Text('Dialog', style: TextStyle(color: Colors.black, fontSize: 40),),
),
),
),
);
},
),
);
}
This is my implementation.
From first screen call this.
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (_, __, ___) => const NoInternetScreen(),
opaque: false, ---->> opacity should be false
fullscreenDialog: true));
And in next screen set background color with opacity.
return Scaffold(
backgroundColor: white.withOpacity(0.85),
......
result
How do I open a popup menu from a second widget?
final button = new PopupMenuButton(
itemBuilder: (_) => <PopupMenuItem<String>>[
new PopupMenuItem<String>(
child: const Text('Doge'), value: 'Doge'),
new PopupMenuItem<String>(
child: const Text('Lion'), value: 'Lion'),
],
onSelected: _doSomething);
final tile = new ListTile(title: new Text('Doge or lion?'), trailing: button);
I want to open the button's menu by tapping on tile.
I think it would be better do it in this way, rather than showing a PopupMenuButton
void _showPopupMenu() async {
await showMenu(
context: context,
position: RelativeRect.fromLTRB(100, 100, 100, 100),
items: [
PopupMenuItem<String>(
child: const Text('Doge'), value: 'Doge'),
PopupMenuItem<String>(
child: const Text('Lion'), value: 'Lion'),
],
elevation: 8.0,
);
}
There will be times when you would want to display _showPopupMenu at the location where you pressed on the button
Use GestureDetector for that
final tile = new ListTile(
title: new Text('Doge or lion?'),
trailing: GestureDetector(
onTapDown: (TapDownDetails details) {
_showPopupMenu(details.globalPosition);
},
child: Container(child: Text("Press Me")),
),
);
and then _showPopupMenu will be like
_showPopupMenu(Offset offset) async {
double left = offset.dx;
double top = offset.dy;
await showMenu(
context: context,
position: RelativeRect.fromLTRB(left, top, 0, 0),
items: [
...,
elevation: 8.0,
);
}
This works, but is inelegant (and has the same display problem as Rainer's solution above:
class _MyHomePageState extends State<MyHomePage> {
final GlobalKey _menuKey = GlobalKey();
#override
Widget build(BuildContext context) {
final button = PopupMenuButton(
key: _menuKey,
itemBuilder: (_) => const<PopupMenuItem<String>>[
PopupMenuItem<String>(
child: Text('Doge'), value: 'Doge'),
PopupMenuItem<String>(
child: Text('Lion'), value: 'Lion'),
],
onSelected: (_) {});
final tile =
ListTile(title: Text('Doge or lion?'), trailing: button, onTap: () {
// This is a hack because _PopupMenuButtonState is private.
dynamic state = _menuKey.currentState;
state.showButtonMenu();
});
return Scaffold(
body: Center(
child: tile,
),
);
}
}
I suspect what you're actually asking for is something like what is tracked by https://github.com/flutter/flutter/issues/254 or https://github.com/flutter/flutter/issues/8277 -- the ability to associated a label with a control and have the label be clickable -- and is a missing feature from the Flutter framework.
Screenshot:
Full code:
class MyPage extends StatelessWidget {
final GlobalKey<PopupMenuButtonState<int>> _key = GlobalKey();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: [
PopupMenuButton<int>(
key: _key,
itemBuilder: (context) {
return <PopupMenuEntry<int>>[
PopupMenuItem(child: Text('0'), value: 0),
PopupMenuItem(child: Text('1'), value: 1),
];
},
),
],
),
body: RaisedButton(
onPressed: () => _key.currentState.showButtonMenu(),
child: Text('Open/Close menu'),
),
);
}
}
I found a solution to your question. You can provide a child to PopupMenuButton which can be any Widget including a ListTile (see code below). Only problem is that the PopupMenu opens on the left side of the ListTile.
final popupMenu = new PopupMenuButton(
child: new ListTile(
title: new Text('Doge or lion?'),
trailing: const Icon(Icons.more_vert),
),
itemBuilder: (_) => <PopupMenuItem<String>>[
new PopupMenuItem<String>(
child: new Text('Doge'), value: 'Doge'),
new PopupMenuItem<String>(
child: new Text('Lion'), value: 'Lion'),
],
onSelected: _doSomething,
)
I don't think there is a way to achieve this behaviour. Although you can attach an onTap attribute to the tile, you can't access the MenuButton from the 'outside'
An approach you could take is to use ExpansionPanels because they look like ListTiles and are intended to allow easy modification and editing.
if you are using Material showMenu but you menu doesn't work properly or opens in wrong place follow my answer.
this answer is based on answer of Vishal Singh.
in GestureDetector
use onLongPressStart or onTapUp for sending offset to function.
onLongPressStart: (detail){
_showPopupMenu(detail.globalPosition);
},
onLongPress is equivalent to (and is called immediately after) onLongPressStart.
onTapUp, which is called at the same time (with onTap) but includes details regarding the pointer position.
and for menu position do some thing like below
position: RelativeRect.fromDirectional(textDirection: Directionality.of(context), start: left, top: top, end: left+2, bottom: top+2)
full code
_showPopupMenu(Offset offset) async {
double left = offset.dx;
double top = offset.dy;
await showMenu(
context: context,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(AppConst.borderRadiusSmall))),
position: RelativeRect.fromDirectional(textDirection: Directionality.of(context), start: left, top: top, end: left+2, bottom: top+2),
items: _getMenuItems(menu),
elevation: 8.0,
).then((value) {
value?.onTap.call();
});
}
class MyPage extends StatefulWidget {
#override
State<MyPage> createState() => _MyPageState();
}
class _MyPageState extends State<MyPage> {
final GlobalKey<PopupMenuButtonState<int>> _key = GlobalKey();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: [
PopupMenuButton<int>(
key: _key,
itemBuilder: (context) {
return <PopupMenuEntry<int>>[
PopupMenuItem(child: Text('0'), value: 0),
PopupMenuItem(child: Text('1'), value: 1),
];
},
),
],
),
body: RaisedButton(
onPressed: () => _key.currentState.showButtonMenu(),
child: Text('Open/Close menu'),
),
);
}
}