I have my files as follows:
homepage.dart
return Scaffold{
appBar: CustomAppBar(),
drawer: CustomDrawer(),
}
CustomAppBar.dart
class HomePageAppBar extends StatelessWidget with PreferredSizeWidget {
return AppBar{
leading: InkWell(
onTap: () {
// Need to open drawer when clicked
},
child: FaIcon(
FontAwesomeIcons.bars,
color: Theme.of(context).iconTheme.color,
size: Theme.of(context).iconTheme.size,
),
),
}
#override
Size get preferredSize => Size.fromHeight(kToolbarHeight);
}
CustomDrawer.dart
return Drawer{
}
Now since my AppBar and Drawer are in separate files, I cannot use Scaffold.of(context).openDrawer() to open the drawer. I tried setting a GlobalKey to my Drawer but was now able to access it from my CustomAppBar file.
I tried converting the CustomAppBar.dart file to a Scaffold so that it contains my appbar and my drawer and used Scaffold.of(context).openDrawer(). This caused the drawer to open and display only in the appbar area (Probably because of using PreferredSizeWidget).
How can I make the drawer open on clicking the AppBar's icon given that they are placed in separate files? I want to keep my AppBar and Drawer in separate files.
See the documentation https://api.flutter.dev/flutter/material/showModalBottomSheet.html
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),
)
],
),
),
);
},
Declare globalkey outside the class (Global) or create a static memeber
GlobalKey<ScaffoldState> _globalkey = new GlobalKey<ScaffoldState>();
opend Drawer using globalkey.
_globalkey.currentState?.openDrawer();
Related
So I have a drawer and I can open it with the hamburger icon on top-left corner. If I change the screen to another page, this hamburger icon become a back button (<--). How to make this into a hamburger icon again? (So no back button but open the drawer on every screen)
In App Bar of next page put
leading: BurgerIcon(),
The Burger icon is from having a Drawer on your Scaffold.
If your next page/route has a Scaffold with a drawer: specified, you'll get the "burger" icon button again.
Without a drawer: specified on Scaffold Flutter will default to the back arrow to go to the previous route.
Here's an example:
import 'package:flutter/material.dart';
class DrawerDirectoryPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Drawer Directories'),
),
body: Center(
child: InkWell(
child: Text('Page One. Click to go to Page Two.'),
onTap: () => Navigator.of(context).push(
MaterialPageRoute(builder: (context) => PageTwo())
),
),
),
drawer: MyDrawerDirectory(),
);
}
}
class PageTwo extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Burger Time!'),
),
body: Center(
child: Text('Page TWO /flex'),
),
drawer: MyDrawerDirectory(), // ← Drawer Directory a.k.a. burger icon
);
}
}
class MyDrawerDirectory extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Drawer(
child: ListView(
children: [
ListTile(title: Text('Page One'), onTap: () => _navPush(context, DrawerDirectoryPage())),
ListTile(title: Text('Page Two'), onTap: () => _navPush(context, PageTwo()))
],
),
);
}
Future<dynamic> _navPush(BuildContext context, Widget page) {
return Navigator.push(context, MaterialPageRoute(
builder: (context) => page,
));
}
}
In the AppBar widget, you can specify a leading widget. After you specify a leading widget (in your case, an Icon Button), you should set AppBar's automaticallyImplyLeading = false.
If you use the methods Navigator.of(context).push or Navigator.of(context).pushNamed you will also experience this issue.
You can use other method to navigate between screens so it doesn't show the hamburger button
Put automaticallyImplyLeading: false in appBar of next page then set leading icon like this:
automaticallyImplyLeading: false,
leading: Padding(
padding: const EdgeInsets.only(right: 20.0),
child: GestureDetector(
onTap: () {
if (scaffoldKey.currentState.isDrawerOpen) {
Navigator.of(context).pop();
} else {
scaffoldKey.currentState.openDrawer();
}
},
child: Padding(
padding: const EdgeInsets.only(top: 15.0),
child: Icon(
Icons.menu,
size: 30,
color: appColor,
),
),
),
),
I'm trying to add a functionality to each question, represented as a ListTile, so that it can upvote or downvote a question, and show the net votes, just like the one that is used on stack overflow. My current implementation does a bottom overflow for each ListTile.
Card(
child: new Column(
children: <Widget>[
new ListTile(
leading: Column(
children: <Widget>[
FlatButton(
child: Icon(Icons.arrow_drop_up),
onPressed: () {},
),
StreamBuilder<DocumentSnapshot>(
stream: RoomDbService(widget.roomName, widget.roomID)
.getQuestionVotes(widget.questionID),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator());
} else {
print(snapshot.data.data["votes"]);
return Text("${snapshot.data.data["votes"]}");
}
},
),
FlatButton(
child: Icon(Icons.arrow_drop_down),
onPressed: () {},
),
],
), // shows votes of this qn on the left of the tile
title: Text(text),
trailing: FlatButton(
child: Icon(Icons.expand_more),
onPressed: toggleExpansion,
),
)
],
),
);
My previous implementation (which I forgot how it looked like) made it look like a row of an up button, the vote count, and the down button. How do I do it properly?
Check out this example Taking you example I have made some modifications in the code
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(home: HomePage());
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
child: Card(
child: new Column(
children: <Widget>[
SizedBox(
height: 10,
),
Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Column(
children: <Widget>[
InkWell(onTap: () {}, child: Icon(Icons.arrow_drop_up)),
Text(
"your Text,
style: TextStyle(fontSize: 10),
),
InkWell(onTap: () {}, child: Icon(Icons.arrow_drop_down)),
],
),
Text('You stream text'),
FlatButton(
child: Icon(Icons.expand_more),
onPressed: () {},
),
],
),
)
],
),
)),
);
}
}
Let me know if it works.
You can make custom widget for your desired layout using Row....
But if you still want to use ListTile, then you have to make somechanges in your code,
ListTile's height we can't set as we want, it's depends on subtitle and isThreeLine property.
So you can get some more height if you add subtitle, and with isThreeLine : true, gives your subtitle more height to fit in ListTile....
For your case you need change leading widget....Use InkWell instead of FlatButton....
Make some changes in CircularProgressIndicator.
use small sized icon for upvote/downvote and use small Text for count, otherwise it will overflow again.
See the code below or play with it at DartPad ListTile_StackOverFlow.
Card(
child: ListTile(
leading: Column(
children:[
InkWell(
child: Icon(Icons.arrow_drop_up),
onTap: () {}
),
Container(
height: 8,
width:8,
child: Center(child: CircularProgressIndicator(strokeWidth :2))
),
InkWell(
child: Icon(Icons.arrow_drop_down),
onTap: () {}
),
]
),
title: Text('Titled text'),
trailing: Icon(Icons.more_vert),
),
);
Better solution is use Row and column and make your own custom Widget that looklike ListTile.... see the official document, here you can see an example which has CustomListTile class which creates the custom looking ListTile( which is not directly using ListTile )....
My advise : You should make your custom class as like above Documentation's CustomListTile class
I have a drawer class in common for 3 pages. Here my problem is if i open any list item page in drawer while in main screen its working fine. if i goto some page(say page2 screen) other than main screen, now if i open any list item in drawer it opens the screen but problem is if i press back button it goes to main screen instead of page2 screen.
Drawer class:
class CustomDrawer extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Theme(
data: Theme.of(context).copyWith(
canvasColor: Colors.deepOrangeAccent.withOpacity(0.9), //This will change the drawer background to blue.//other styles
),
child: Drawer(
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(left:8.0),
child: ListView(
shrinkWrap: true,
children: <Widget>[
ListTile(
leading: Icon(Icons.event_note,color: Colors.white),
title: Text("Menu",style: TextStyle(color:Colors.white),),
onTap: () {
Navigator.push (
context, MaterialPageRoute(builder(BuildContextcontext)=> Menu()));
},
),
ListTile(
leading: Icon(Icons.person,color: Colors.white),
title: Text("My Profile",style: TextStyle(color:Colors.white),),
onTap: () {
Navigator.push (
context, MaterialPageRoute(builder: (BuildContext context) => MyProfile()));
},
),
],
),
)
],
),
),
);
}
}
I have issue with the Raised button click. The method inside OnPressed() is not getting called. Ideally on the OnPressed() method i would like to have the pop up or a slider shown. I have created the example to show the problem currently faced.
The main.dart file calls Screen2()
import 'package:flutter/material.dart';
//import 'package:flutter_app/main1.dart';
import 'screen2.dart';
void main() => runApp(Lesson1());
class Lesson1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Screen2(),
);
}
}
and in Screen2()i have just have a RaisedButton and OnPressed() it needs to call the function ButtonPressed().
import 'package:flutter/material.dart';
class Screen2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.blue,
title: Text('Screen 2'),
),
body: Center(
child: RaisedButton(
color: Colors.blue,
child: Text('Go Back To Screen 1'),
onPressed: () {
print('centrebutton');
ButtonPressed();
},
),
),
);
}
}
class ButtonPressed extends StatefulWidget {
#override
_ButtonPressedState createState() => _ButtonPressedState();
}
class _ButtonPressedState extends State<ButtonPressed> {
#override
Widget build(BuildContext context) {
print ('inside button press');
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.red,
title: Text('Screen 3'),
),
// create a popup to show some msg.
);
}
}
On clicking the Raised button the print statement ('centerbutton') gets printed.
But the ButtonPressed() method is not getting called .
I am not able to see the print msg('inside button press') in the console. Pl. let me what could be the reason for ButtonPressed method not getting called. Attached the snapshot for your reference.
You are calling a Widget on your RaisedButton's onPressed method. Your widget is getting called but will not render anywhere in the screen.
You should call a function for processing your data in a tap event. But you are calling a widget or say a UI view.
If you want to navigate to the respective screen then you should use navigator.
For ex :
onPressed: () {
print('centrebutton');
Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) => ButtonPressed()));
},
This could be a cause: If you have an asset as a child of your button, and that asset does not exist, the button onpressed will not work.
Solution: remove the asset.
Example:
return RaisedButton(
splashColor: Colors.grey,
onPressed: () {
},
child: Padding(
padding: const EdgeInsets.fromLTRB(0, 10, 0, 10),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Image(image: AssetImage("assets/google_logo.png"), height: 35.0), //remove this line
Padding(
padding: const EdgeInsets.only(left: 10),
child: Text(
'Sign in with Google',
style: TextStyle(
fontSize: 20,
color: Colors.grey,
),
),
)
],
),
),
);
I want to open a drawer after pushing on the custom button in BottomMenu I have trouble with Scaffold.of(context).openDrawer(), it doesn't work. My BottomMenu is a separate widget class. As I understand, it doesn't work because it's a separate context. How can I get the right context? Or perhaps someone knows another solution.
Here my code reproducer:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(title: 'Flutter Drawer'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
bottomNavigationBar: BottomMenu(),
endDrawer: SizedBox(
width: double.infinity,
child: Drawer(
elevation: 16,
child: Container(
color: Colors.black,
child: ListView(
padding: EdgeInsets.zero,
children: <Widget>[
ListTile(
title: Text('Some context here',
style: TextStyle(color: Colors.white))),
ListTile(
title: Text('Some context here',
style: TextStyle(color: Colors.white))),
],
),
),
),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Call Drawer form menu reproducer',
)
],
),
),
);
}
}
class BottomMenu extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: Wrap(
alignment: WrapAlignment.center,
children: <Widget>[
Divider(color: Colors.black, height: 1),
Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
InkWell(
borderRadius: new BorderRadius.circular(20.0),
customBorder: Border.all(color: Colors.black),
child: Container(
padding: EdgeInsets.only(
left: 3, right: 6, bottom: 15, top: 11),
child: Row(
children: <Widget>[
Icon(Icons.menu),
Text('Show menu', style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold)),
],
),
),
onTap: () {
Scaffold.of(context).openDrawer();
},
),
],
),
),
],
),
);
}
}
In my case, this worked.
return Scaffold(
key: _scaffoldKey,
endDrawerEnableOpenDragGesture: false, // This!
appBar: AppBar(
iconTheme: IconThemeData(color: Colors.white),
leading: IconButton(
icon: Icon(Icons.menu, size: 36),
onPressed: () => _scaffoldKey.currentState.openDrawer(), // And this!
),
),
drawer: DrawerHome(),
....
and _scaffoldKey must be initialized as,
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
under the class.
The problem is that you specified endDrawer on Scaffold yet you're calling Scaffold.of(context).openDrawer().
openDrawer() documentation states:
If the scaffold has a non-null Scaffold.drawer, this function will cause the drawer to begin its entrance animation.
Since your drawer is null, nothing happens.
In contrast, openEndDrawer() informs us:
If the scaffold has a non-null Scaffold.endDrawer, this function will cause the end side drawer to begin its entrance animation.
Since your endDrawer is not null you should use openEndDrawer() method. Alternatively, if you don't care which side the drawer slides in from, you can use drawer instead of endDrawer when building Scaffold.
My problem solved that instead of
Scaffold.of(context).openEndDrawer()
I give key to Scaffold and then I call by state like below
_scaffoldkey.currentState.openEndDrawer()
It solved my problem I hope It also works for you
Scaffold.of(context).openEndDrawer()
The Problem
This issue can occur when you do not use the correct BuildContext when calling Scaffold.of(context).openDrawer() (or openEndDrawer()).
Easiest Solution
Simply wrap whatever calls openDrawer() (or openEndDrawer()) with a Builder widget. This will give it a working context.
Minimal Working Example
// your build method
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: Builder(builder: (context) { // this uses the new context to open the drawer properly provided by the Builder
return FloatingActionButton(onPressed: (() => Scaffold.of(context).openDrawer()));
}),
drawer: const Drawer(
child: Text("MY DRAWER"),
),
);
}
Similar problem here. Clicked on button and nothing happened. The problem is I was using the context of the widget that instantiated Scaffold. Not the context of a child of Scaffold.
Here is how I solved it:
// body: Column(
// children: <Widget>[
// Row(
// children: <Widget>[
// IconButton(
// icon: Icon(Icons.filter_list),
// onPressed: () => Scaffold.of(context).openEndDrawer(), (wrong context)
// ),
// ],
// ),
// ],
// )
To:
body: Builder(
builder: (context) => Column(
children: <Widget>[
Row(
children: <Widget>[
IconButton(
icon: Icon(Icons.filter_list),
onPressed: () => Scaffold.of(context).openEndDrawer(),
),
],
),
],
)),
),
Assign Drawer to drawer property in scaffold. Wrap your specific Widget/Button(where you want to open drawer on its click method) with Builder. Use below method on click property:
enter image description here
Scaffold.of(context).openDrawer();
If you have the appbar widget with an action button to launch the drawer and the drawer is never pushed please remember that you need to define after appbar: ... the endDrawer: YOURAppDrawerWIDGET(), or else using the Scaffold.of(context).openEndDrawer() will not work.
Scaffold(
appBar: AppBar(title: Text(_title)),
endDrawer: AppDrawer(), // <-- this is required or else it will not know what is opening
body: SingleChildScrollView(
///...