I am trying to create a uniform drawer that is accessible across all pages in my app. How do I make it persist throughout all these pages without having to recreate my custom Drawer widget in every single dart file?
There are a few different options for this. The most basic is hopefully something you've already done, but I'll list it anyways:
1: Create a class for your drawer
Your widget should be its own stateful or stateless widget. This way, you just have to instantiate it each time.
class MyDrawer extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Drawer(...);
}
}
And then when using it in each page:
Scaffold(
drawer: MyDrawer(...),
...
)
I hope you're already doing this; if not you should be. A class's build function shouldn't be too large or it can lead to poor performance and harder to maintain code; splitting things into logical units will help you in the long run.
2: Create a class for your scaffold
If having to include the same drawer in a scaffold for each page is still too much code, you can instead use a class that encapsulates your scaffold. It would essentially take inputs for each of the scaffold inputs you actually use.
class MyScaffold extends StatelessWidget {
final Widget body;
MyScaffold({this.body});
#override
Widget build(BuildContext context) {
return Scaffold(
body: body,
drawer: MyDrawer(...),
);
}
}
And then instead of using Scaffold in your code, use MyScaffold (but please name it something better =D).
3: Multi level scaffold
I'm only including this way of doing it to be complete, and I don't recommend it. That being said, there are certain things you can't get to work within flutter's normal workflow that you could do by doing this - for example if you want a custom animation for when the user taps on different items in the drawer.
Basically, what you'd do in this case is to have a Scaffold outside of your MaterialApp or Navigator (which I believe would also mean you'd have to have another Navigator outside that, but I'm not 100% sure). You would have the scaffold that's outside your navigation show the drawer while the other one (on each page within the navigation) would do whatever else you need it to do. There's a few caveats - you'd have to make sure you get the right scaffold (i.e. Scaffold.of(context) by itself wouldn't cut it - you'd have to get the context of the first scaffold and use it to find the higher-level one), and you'd probably need to pass a GlobalKey (of the lower-level scaffold) to the Drawer so that it could actually change pages within it.
As I said, I don't recommend this approach, so I'm not going to go into any more detail than that but rather leave it as an exercise for the reader if they want to go down that rabbit hole!
rmtmckenzie is very correct.
Although if you are curious about the multi scaffold solution, this can be more elegant than you think.
To share a drawer between all pages we could add a builder in our MaterialApp instance.
This will instantiate a Scaffold under Navigator but above all routes.
MaterialApp(
title: 'Flutter Demo',
builder: (context, child) {
return Scaffold(
drawer: MyDrawer(),
body: child,
);
},
home: MyHome()
)
Inside your page, you can instantiate another Scaffold without restriction as you'd usually do.
You can then show the shared drawer by doing the following in any widget under MaterialApp :
final ScaffoldState scaffoldState = context.rootAncestorStateOfType(TypeMatcher<ScaffoldState>());
scaffoldState.openDrawer();
Code which you can extract into a nice helper :
class RootScaffold {
static openDrawer(BuildContext context) {
final ScaffoldState scaffoldState =
context.rootAncestorStateOfType(TypeMatcher<ScaffoldState>());
scaffoldState.openDrawer();
}
}
Then reuse using RootScaffold.openDrawer(context)
In Addition to #Rémi Rousselet Answer
MaterialApp(
title: 'Flutter Demo',
builder: (context, child) {
return Scaffold(
drawer: MyDrawer(),
body: child,
);
},
home: MyHome()
)
For Navigation in root drawer if you use Navigator.of(context) // push or pop that will throw error and for that you must use child widget to navigate to different pages
Like that
(child.key as GlobalKey<NavigatorState>).currentState // push or pop
Demo project in Github
if somebody looking for fancy stuff while navigating look here. What I use as a drawer for my project is flutter_inner_drawer package.
I created a stateful class named CustomDrawer.
class CustomDrawer extends StatefulWidget {
final Widget scaffold;
final GlobalKey<InnerDrawerState> innerDrawerKey;
CustomDrawer({
Key key,
this.scaffold,
this.innerDrawerKey,
}) : super(key: key);
#override
_CustomDrawerState createState() => _CustomDrawerState();
}
class _CustomDrawerState extends State<CustomDrawer> {
MainPageIcons assets = MainPageIcons();//From my actual code dont care it
final vars = GlobalVars.shared; //From my actual code dont care it
#override
Widget build(BuildContext context) {
return InnerDrawer(
key: widget.innerDrawerKey,
onTapClose: true, // default false
tapScaffoldEnabled: true,
swipe: true, // default true
colorTransition: Colors.teal, // default Color.black54
//innerDrawerCallback: (a) => print(a ),// return bool
leftOffset: 0.2, // default 0.4
leftScale: 1,// default 1
boxShadow: [
BoxShadow(color: Colors.teal,blurRadius: 20.0, // has the effect of softening the shadow
spreadRadius: 10.0, // has the effect of extending the shadow
offset: Offset(
10.0, // horizontal, move right 10
10.0, // vertical, move down 10
),)],
borderRadius: 20, // default 0
leftAnimationType: InnerDrawerAnimation.quadratic, // default static
//when a pointer that is in contact with the screen and moves to the right or left
onDragUpdate: (double val, InnerDrawerDirection direction) =>
setState(() => _dragUpdate = val),
//innerDrawerCallback: (a) => print(a),
// innerDrawerCallback: (a) => print(a), // return true (open) or false (close)
leftChild: menus(), // required if rightChild is not set
scaffold:widget.scaffold
);
}
double _dragUpdate = 0;
Widget menus(){
return
Material(
child: Stack(
children: <Widget>[
Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topRight,
end: Alignment.bottomLeft,
colors: [
ColorTween(
begin: Colors.blueAccent,
end: Colors.blueGrey[400].withRed(100),
).lerp(_dragUpdate),
ColorTween(
begin: Colors.green,
end: Colors.blueGrey[800].withGreen(80),
).lerp(_dragUpdate),
],
),
),
child: Stack(
children: <Widget>[
Padding(
padding: EdgeInsets.only(left: 30),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Column(
children: <Widget>[
Container(
margin: EdgeInsets.only(left: 10, bottom: 15),
width: 80,
child: ClipRRect(
child: Image.network(
"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSrWfWLnxIT5TnuE-JViLzLuro9IID2d7QEc2sRPTRoGWpgJV75",
),
borderRadius: BorderRadius.circular(60),
),
),
Text(
"User",
style: TextStyle(color: Colors.white, fontSize: 18),
)
],
//mainAxisAlignment: MainAxisAlignment.center,
),
Padding(
padding: EdgeInsets.all(10),
),
ListTile(
onTap: ()=>navigate(Profile.tag),
title: Text(
"Profile",
style: TextStyle(color: Colors.white, fontSize: 14),
),
leading: Icon(
Icons.dashboard,
color: Colors.white,
size: 22,
),
),
ListTile(
title: Text(
"Camera",
style: TextStyle(fontSize: 14,color:Colors.white),
),
leading: Icon(
Icons.camera,
size: 22,
color: Colors.white,
),
onTap: ()=>navigate(Camera.tag)
),
ListTile(
title: Text(
"Pharmacies",
style: TextStyle(fontSize: 14,color:Colors.white),
),
leading: Icon(
Icons.add_to_photos,
size: 22,
color: Colors.white,
),
onTap: ()=>navigate(Pharmacies.tag)
),
],
),
),
Positioned(
bottom: 20,
child: Container(
alignment: Alignment.bottomLeft,
margin: EdgeInsets.only(top: 50),
padding: EdgeInsets.symmetric(vertical: 15, horizontal: 25),
width: double.maxFinite,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Icon(
Icons.all_out,
size: 18,
color: Colors.grey,
),
Text(
" LogOut",
style: TextStyle(
fontSize: 16,
color: Colors.grey,
),
),
],
),
),
)
],
),
),
_dragUpdate < 1
? BackdropFilter(
filter: ImageFilter.blur(
sigmaX: (10 - _dragUpdate * 10),
sigmaY: (10 - _dragUpdate * 10)),
child: Container(
decoration: BoxDecoration(
color: Colors.black.withOpacity(0),
),
),
)
: null,
].where((a) => a != null).toList(),
));
}
navigate(String route) async{
await navigatorKey.currentState.pushNamed(route).then((_){
Timer(Duration(milliseconds: 500),()=>widget.innerDrawerKey.currentState.toggle() );
});
}
}
I copied example from package and didnt touch original much. only aded a function to toggle after turn back.
navigate(String route) async{
await navigatorKey.currentState.pushNamed(route).then((_){
Timer(Duration(milliseconds: 500),()=>widget.innerDrawerKey.currentState.toggle() );
});
}
to navigate from all over pages aded GlobalKey globally so that reachable from every class
final GlobalKey<NavigatorState> navigatorKey = GlobalKey(debugLabel: "Main Navigator");
inner_drawer also needs a globalkey for state to toogle but if you create only one when navigate between pages it gives duplicate global key error. to avoid I created a global variable named innerKeys
Map<String,GlobalKey<InnerDrawerState>>innerKeys={
'main':GlobalKey<InnerDrawerState>(),
'profile':GlobalKey<InnerDrawerState>(),
'pharmacies':GlobalKey<InnerDrawerState>(),
};
finally I added this CustomDrawer to every pages
#override
Widget build(BuildContext context) {
return CustomDrawer(
innerDrawerKey: vars.innerKeys['profile'],
scaffold:Scaffold(
appBar: CustomAppBar(
title: 'Profile',
actions: <Widget>[
],),
body: Stack(
children: <Widget>[
Background(),
])));
}
I hope it will helps to someone.
NOTE: please check original flutter pack if anything updated. Be avare that this example is not perfect and needs to taken care that if many navigation over this drawer then widget tree will have many pages and performance will be impacted. any tuning suggestion will be appriciated.
My Solution Navigation Drawer with Multiple Fragments using bloc package
First, add below dependencies in your pubspec.yaml file
flutter_bloc: ^4.0.0
Now create below files
drawer_event.dart
import 'nav_drawer_state.dart';
abstract class NavDrawerEvent {
const NavDrawerEvent();
}
class NavigateTo extends NavDrawerEvent {
final NavItem destination;
const NavigateTo(this.destination);
}
nav_drawer_bloc.dart
import 'package:bloc/bloc.dart';
import 'drawer_event.dart';
import 'nav_drawer_state.dart';
class NavDrawerBloc extends Bloc<NavDrawerEvent, NavDrawerState> {
#override
NavDrawerState get initialState => NavDrawerState(NavItem.homePage);
#override
Stream<NavDrawerState> mapEventToState(NavDrawerEvent event) async* {
if (event is NavigateTo) {
if (event.destination != state.selectedItem) {
yield NavDrawerState(event.destination);
}
}
}
}
nav_drawer_state.dart
class NavDrawerState {
final NavItem selectedItem;
const NavDrawerState(this.selectedItem);
}
enum NavItem {
homePage,
profilePage,
orderPage,
myCart,
}
drawer_widget.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutterdrawerwithbloc/bloc/drawer_event.dart';
import 'package:flutterdrawerwithbloc/bloc/nav_drawer_bloc.dart';
import 'package:flutterdrawerwithbloc/bloc/nav_drawer_state.dart';
class NavDrawerWidget extends StatelessWidget {
final String accountName;
final String accountEmail;
final List<_NavigationItem> _listItems = [
_NavigationItem(true, null, null, null),
_NavigationItem(false, NavItem.homePage, "Home", Icons.home),
_NavigationItem(false, NavItem.profilePage, "Profile Page", Icons.person),
_NavigationItem(false, NavItem.orderPage, "My Orders", Icons.list),
_NavigationItem(false, NavItem.myCart, "My Cart", Icons.shopping_cart),
];
NavDrawerWidget(this.accountName, this.accountEmail);
#override
Widget build(BuildContext context) => Drawer(
child: Container(
child: ListView.builder(
padding: EdgeInsets.zero,
itemCount: _listItems.length,
itemBuilder: (BuildContext context, int index) =>
BlocBuilder<NavDrawerBloc, NavDrawerState>(
builder: (BuildContext context, NavDrawerState state) =>
_buildItem(_listItems[index], state),
)),
));
Widget _buildItem(_NavigationItem data, NavDrawerState state) =>
data.header ? _makeHeaderItem() : _makeListItem(data, state);
Widget _makeHeaderItem() => UserAccountsDrawerHeader(
accountName: Text(accountName, style: TextStyle(color: Colors.white)),
accountEmail: Text(accountEmail, style: TextStyle(color: Colors.white)),
decoration: BoxDecoration(color: Colors.indigo),
currentAccountPicture: CircleAvatar(
backgroundColor: Colors.white,
foregroundColor: Colors.amber,
child: Icon(
Icons.person,
size: 54,
),
),
);
Widget _makeListItem(_NavigationItem data, NavDrawerState state) => Card(
shape: ContinuousRectangleBorder(borderRadius: BorderRadius.zero),
borderOnForeground: true,
elevation: 0,
margin: EdgeInsets.zero,
child: Builder(
builder: (BuildContext context) => ListTile(
title: Text(
data.title,
style: TextStyle(
color: data.item == state.selectedItem ? Colors.green : Colors.blueGrey,
),
),
leading: Icon(
data.icon,
color: data.item == state.selectedItem ? Colors.green : Colors.blueGrey,
),
onTap: () => _handleItemClick(context, data.item),
),
),
);
void _handleItemClick(BuildContext context, NavItem item) {
BlocProvider.of<NavDrawerBloc>(context).add(NavigateTo(item));
Navigator.pop(context);
}
}
class _NavigationItem {
final bool header;
final NavItem item;
final String title;
final IconData icon;
_NavigationItem(this.header, this.item, this.title, this.icon);
}
main.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutterdrawerwithbloc/bloc/nav_drawer_bloc.dart';
import 'package:flutterdrawerwithbloc/bloc/nav_drawer_state.dart';
import 'package:flutterdrawerwithbloc/drawer_widget.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Navigation Drawer Demo',
theme: ThemeData(primarySwatch: Colors.blue, scaffoldBackgroundColor: Colors.white),
home: MyHomePage(),
);
;
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
NavDrawerBloc _bloc;
Widget _content;
#override
void initState() {
super.initState();
_bloc = NavDrawerBloc();
_content = _getContentForState(_bloc.state.selectedItem);
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) => BlocProvider<NavDrawerBloc>(
create: (BuildContext context) => _bloc,
child: BlocListener<NavDrawerBloc, NavDrawerState>(
listener: (BuildContext context, NavDrawerState state) {
setState(() {
_content = _getContentForState(state.selectedItem);
});
},
child: BlocBuilder<NavDrawerBloc, NavDrawerState>(
builder: (BuildContext context, NavDrawerState state) => Scaffold(
drawer: NavDrawerWidget("AskNilesh", "rathodnilsrk#gmail.com"),
appBar: AppBar(
title: Text(_getAppbarTitle(state.selectedItem)),
centerTitle: false,
brightness: Brightness.light,
backgroundColor: Colors.indigo,
),
body: AnimatedSwitcher(
switchInCurve: Curves.easeInExpo,
switchOutCurve: Curves.easeOutExpo,
duration: Duration(milliseconds: 300),
child: _content,
),
),
),
));
_getAppbarTitle(NavItem state) {
switch (state) {
case NavItem.homePage:
return 'Home';
case NavItem.profilePage:
return 'Profile Page';
case NavItem.orderPage:
return 'My Orders';
case NavItem.myCart:
return 'My Cart';
default:
return '';
}
}
_getContentForState(NavItem state) {
switch (state) {
case NavItem.homePage:
return Center(
child: Text(
'Home Page',
style: TextStyle(fontWeight: FontWeight.bold),
),
);
case NavItem.profilePage:
return Center(
child: Text(
'Profile Page',
style: TextStyle(fontWeight: FontWeight.bold),
),
);
case NavItem.orderPage:
return Center(
child: Text(
'My Orders',
style: TextStyle(fontWeight: FontWeight.bold),
),
);
case NavItem.myCart:
return Center(
child: Text(
'My Cart',
style: TextStyle(fontWeight: FontWeight.bold),
),
);
default:
return Center(
child: Text(
'Home Page',
style: TextStyle(fontWeight: FontWeight.bold),
),
);
}
}
}
You can find complete project here Navigation Drawer with Multiple Fragments using bloc
In Addition to #Rémi Rousselet Answer, the code has slightly changed (2022) - due to null safety amends.
Replace this:
class RootScaffold {
static openDrawer(BuildContext context) {
final ScaffoldState scaffoldState =
context.rootAncestorStateOfType(TypeMatcher<ScaffoldState>());
scaffoldState.openDrawer();
}
}
...with...
class RootScaffold {
static openDrawer(BuildContext context) {
final ScaffoldState? scaffoldState = context.findRootAncestorStateOfType<ScaffoldState>();
scaffoldState?.openDrawer();
}
}
You can create the ScaffoldCustom when you ensure that all pages have only the body differently.
But I feel that this approach is too restrictive.
So, I am using this.
For the AppBar:
class AppBarPattern1 extends StatelessWidget implements PreferredSizeWidget {
const AppBarPattern1({Key? key}) : super(key: key);
#override
// TODO: implement preferredSize
Size get preferredSize => const Size.fromHeight(kToolbarHeight); // You can change it.
/*
/// The height of the toolbar component of the [AppBar].
const double kToolbarHeight = 56.0;
*/
#override
Widget build(BuildContext context) {
return AppBar();
}
}
For the Drawer:
class DrawerPattern1 extends StatelessWidget {
const DrawerPattern1({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const Drawer();
}
}
Using like this:
class ExamplePage extends StatelessWidget {
const ExamplePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: const AppBarPattern1(),
endDrawer: const DrawerPattern1(),
body: SafeArea(child: Container()),
);
}
}
As you see these custom widgets can be const.
Related
I am trying to implement Flutter's Tab Bar with 3 tabs and an AnimatedList inside those tabs. I want to use the same list and filter the list according to each tab (past tasks, today's tasks, and future tasks), however during my implementation of the tab bar together with the animatedlist I am getting an error regarding a duplicate global key in the widget tree. https://pastebin.com/iAW6DH9m . What would be the best way to deal with this error? Thank you for any help.
edit: I tried using this method to fix this. Multiple widgets used the same GlobalKey while it did fix my error I was then unable to access "currentstate" method on the key to be able to add more items to the list. I then tried a similar method using using GlobalKey and it resulted in a similar error of duplicate global keys.
This is my tab bar implementation
import 'package:flutter/material.dart';
import 'search_widget.dart';
import 'animatedlist_widget.dart';
class Dashboard extends StatefulWidget {
#override
_DashboardState createState() => _DashboardState();
}
class _DashboardState extends State<Dashboard> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: new AppBar(
centerTitle: true,
actions: <Widget>[
new IconButton(icon: new Icon(Icons.grid_on), onPressed: null)
],
title: new Text('Dashboard'),
elevation: 0,
),
floatingActionButton: new FloatingActionButton(
onPressed: () {
_onFabPress(context);
},
child: new Icon(Icons.add)),
body: Scaffold(
appBar: new SearchWidget(
onPressed: () => print('implement search'),
icon: Icons.search,
title: 'Search',
preferredSize: Size.fromHeight(50.0),
),
body: DefaultTabController(
length: 3,
child: Scaffold(
appBar: PreferredSize(
preferredSize: Size.fromHeight(kToolbarHeight),
child: Container(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: new TabBar(
unselectedLabelColor: Colors.black45,
labelColor: Colors.white,
indicator: CustomTabIndicator(),
tabs: <Widget>[
new Tab(text: "Past"),
new Tab(text: "Today"),
new Tab(text: "Future")
]),
),
),
),
body: new TabBarView(
children: <Widget>[
AnimatedTaskList(),
AnimatedTaskList(),
AnimatedTaskList()
],
)
),
),
),
);
}
void _onFabPress(context) {
AnimatedTaskList().addUser();
}
/*showModalBottomSheet(
context: context,
builder: (BuildContext bc) {
return Container(
child: new Wrap(children: <Widget>[
new TextField(
decoration: InputDecoration(
border: OutlineInputBorder(),
hintText: 'Enter Task Title')),
new TextField(
decoration: InputDecoration(
border: OutlineInputBorder(),
hintText: 'Enter Task Details',
)),
]));
});
}*/
}
class CustomTabIndicator extends Decoration {
#override
BoxPainter createBoxPainter([onChanged]) {
// TODO: implement createBoxPainter
return new _CustomPainter(this, onChanged);
}
}
class _CustomPainter extends BoxPainter {
final CustomTabIndicator decoration;
_CustomPainter(this.decoration, VoidCallback onChanged)
: assert(decoration != null),
super(onChanged);
#override
void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
// TODO: implement paint
assert(configuration != null);
assert(configuration.size != null);
final indicatorHeight = 30.0;
final Rect rect = Offset(
offset.dx, (configuration.size.height / 2) - indicatorHeight / 2) &
Size(configuration.size.width, indicatorHeight);
final Paint paint = Paint();
paint.color = Colors.blueAccent;
paint.style = PaintingStyle.fill;
canvas.drawRRect(RRect.fromRectAndRadius(rect, Radius.circular(30)), paint);
}
}
This is my animatedlist class:
import 'package:flutter/material.dart';
final GlobalKey<AnimatedListState> _listKey = GlobalKey();
class AnimatedTaskList extends StatefulWidget {
void addUser() {
int index = listData.length;
listData.add(
TaskModel(
taskTitle: "Grocery Shopping",
taskDetails: "Costco",
),
);
_listKey.currentState
.insertItem(index, duration: Duration(milliseconds: 500));
}
#override
_AnimatedTaskListState createState() => _AnimatedTaskListState();
}
class _AnimatedTaskListState extends State<AnimatedTaskList> {
#override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
body: SafeArea(
child: AnimatedList(
key: _listKey,
initialItemCount: listData.length,
itemBuilder:
(BuildContext context, int index, Animation animation) {
return Card(
child: FadeTransition(
opacity: animation,
child: ListTile(
title: Text(listData[index].taskTitle),
subtitle: Text(listData[index].taskDetails),
onLongPress: () {
//todo delete user
},
)));
})),
);
}
}
class TaskModel {
TaskModel({this.taskTitle, this.taskDetails});
String taskTitle;
String taskDetails;
}
List<TaskModel> listData = [
TaskModel(
taskTitle: "Linear Algebra",
taskDetails: "Chapter 4",
),
TaskModel(
taskTitle: "Physics",
taskDetails: "Chapter 9",
),
TaskModel(
taskTitle: "Software Construction",
taskDetails: "Architecture",
),
];
I fixed my issue by moving
final GlobalKey<AnimatedListState> _listKey = GlobalKey();
into my _AnimatedTaskListState class, and adding a constructor and private key to my AnimatedTaskList class
final GlobalKey<AnimatedListState> _key;
AnimatedTaskList(this._key);
#override
_AnimatedTaskListState createState() => _AnimatedTaskListState(_key);
then in my tab bar implementation I changed it to reflect my new constructor
AnimatedTaskList(GlobalKey<AnimatedListState>(debugLabel: "key 1"));
AnimatedTaskList(GlobalKey<AnimatedListState>(debugLabel: "key 2"));
AnimatedTaskList(GlobalKey<AnimatedListState>(debugLabel: "key 3"));
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 have a scene (collections.dart) that takes an index of several other scenes/files in a PageView.builder. You can swipe between scenes from the collections.dart file. Also in collections.dart is a button.
I want it to be the case that if you click on the button, and the current scene being shown through collections.dart is, for example, FirstScreen, then I can route to a table I have built specifically for first.dart, with the same being true for all other scenes in the index.
I have tried to accomplish this by a conditional statement in the onPressed argument, but no success yet. There is no error, it just takes no action. Here is the code in its entirety for collections.dart (including the unsuccessful conditional statement for onPressed):
import 'package:flutter/material.dart';
import 'package:circle_indicator/circle_indicator.dart';
import 'first.dart';
import 'second.dart';
import 'third.dart';
import 'fourth.dart';
import 'fifth.dart';
import 'sixth.dart';
import 'seventh.dart';
import 'eighth.dart';
import 'ninth.dart';
import 'tenth.dart';
class CollectionsScreen extends StatelessWidget {
#override
Widget build(BuildContext context){
return Collections();
}
}
class Collections extends StatefulWidget {
#override
CollectionsState createState() => CollectionsState();
}
class CollectionsState extends State<Collections> {
FirstScreen one;
SecondScreen two;
ThirdScreen three;
FourthScreen four;
FifthScreen five;
SixthScreen six;
SeventhScreen seven;
EighthScreen eight;
NinthScreen nine;
TenthScreen ten;
List<Widget> pages;
#override
void initState() {
one = FirstScreen();
two = SecondScreen();
three = ThirdScreen();
four = FourthScreen();
five = FifthScreen();
six = SixthScreen();
seven = SeventhScreen();
eight = EighthScreen();
nine = NinthScreen();
ten = TenthScreen();
pages = [one, two, three, four, five, six, seven, eight, nine, ten];
super.initState();
}
final PageController controller = new PageController();
#override
Widget build(BuildContext context){
return new Stack(
children: <Widget>[
new Scaffold(
body: new Container(
child: new PageView.builder( //Swipe Between Pages
controller: controller,
itemCount: 10,
itemBuilder: (context, index){
return pages[index];
}
),
),
),
new Container( //CircleIndicator
child: new CircleIndicator(controller, 10, 8.0, Colors.white70, Colors.white,),
alignment: Alignment(0.0, 0.9),
),
new Container( //Button
alignment: Alignment(0.0, 0.65),
child: new Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
new Container(
child: new RaisedButton(
elevation: 4.0,
child: new Text(
'SHOW ME',
style: new TextStyle(
fontWeight: FontWeight.w900,
fontSize: 22.0,
),
),
color: Color(0xFF70E0EF),
shape: new RoundedRectangleBorder(
borderRadius: new BorderRadius.circular(7.5)
),
//This is the conditional statement I'm talking about
onPressed: () {
new PageView.builder(
controller: controller,
itemBuilder: (context, index) {
if (pages[index] == one){
Navigator.push(
context,
new MaterialPageRoute(builder: (context) => new FirstTable()),
);
}
else if (pages[index] == two){
Navigator.push(
context,
new MaterialPageRoute(builder: (context) => new SecondTable()),
);
}
else {
Navigator.push(
context,
new MaterialPageRoute(builder: (context) => new ThirdTable()),
);
}
}
);
},
),
width: 150.0,
height: 60.0,
),
],
),
),
],
);
}
}
The "Table" classes I'm referring to in the conditional statement are in the files for first.dart, second.dart, etc. Here is the file for first.dart. For the moment, the code is identical between all these files (first.dart, second.dart, etc.):
import 'package:flutter/material.dart';
class FirstScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new First();
}
}
class First extends StatefulWidget {
#override
FirstState createState() => FirstState();
}
class FirstState extends State<First>{
#override
Widget build(BuildContext context) {
double fontSize = MediaQuery.of(context).size.height;
double fontSizeFractional = fontSize * 0.07;
return Scaffold(
body: new Stack(
fit: StackFit.passthrough,
children: [
new Container( //Background
decoration: new BoxDecoration(
image: new DecorationImage(
image: new AssetImage('assets/FirstBG.png'),
fit: BoxFit.cover
),
),
),
new Container( //Title
margin: EdgeInsets.all(40.0),
alignment: new Alignment(0.0, -0.70),
child: new Text(
'FIRST',
style: new TextStyle(
fontWeight: FontWeight.bold,
fontSize: fontSizeFractional,
color: Colors.white,
fontFamily: 'baron neue',
),
),
),
],
),
);
}
}
class FirstTable extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Table();
}
}
class Table extends StatefulWidget {
#override
TableState createState() => TableState();
}
class TableState extends State<Table>{
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: new RaisedButton(
onPressed: () {
Navigator.pop(context);
},
child: new Text(
'Go Back',
),
),
),
);
}
}
I have a theory that the reason it isn't working is that collections.dart isn't actually caching any data as to what page of the index it is on (that could be totally wrong, though). Curious to hear your ideas!
Your problem is that you should directly use controller.page inside the onPressed of your button. instead of instantiating a widget.
Although ultimately you should hide an abstract layer between your gallery class and the list of items.
To do that you can create a custom class which will hols all informations about a gallery item :
#immutable
class GalleryItem {
final Widget content;
final Widget details;
GalleryItem({#required this.content, this.details}) : assert(content != null);
}
Your gallery will then take a list of such class as parameter. And do it's job with these.
Ideally you want to use your gallery like this :
Gallery(
items: [
GalleryItem(
content: Container(
color: Colors.red,
),
details: Text("red"),
),
GalleryItem(
content: Container(
color: Colors.blue,
),
details: Text("blue"),
),
],
),
The code of such gallery would be :
class Gallery extends StatefulWidget {
final List<GalleryItem> items;
Gallery({#required this.items, Key key})
: assert(items != null),
super(key: key);
#override
_GalleryState createState() => _GalleryState();
}
class _GalleryState extends State<Gallery> {
final PageController pageController = PageController();
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Expanded(
child: PageView(
children: widget.items.map((item) => item.content).toList(),
controller: pageController,
),
),
RaisedButton(
onPressed: showContentDetails,
child: Text("More info"),
)
],
);
}
void showContentDetails() {
final index = pageController.page.round();
if (widget.items[index]?.details != null) {
showDialog(
context: context,
builder: (_) =>
GalleryItemDetails(details: widget.items[index].details),
);
}
}
}
class GalleryItemDetails extends StatelessWidget {
final Widget details;
GalleryItemDetails({#required this.details, Key key})
: assert(details != null),
super(key: key);
#override
Widget build(BuildContext context) {
return Dialog(
child: details,
);
}
}
I have looked through the Flutter documentation to try and find an event, callback or even a state that I could hook into when the FlexibleSpaceBar is collapsed or expanded.
return new FlexibleSpaceBar(
title: new Column(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
new Text(_name, style: textTheme.headline),
new Text(_caption, style: textTheme.caption)
]),
centerTitle: false,
background: getImage());`
When the FlexibleSpaceBar is snapped in (collapsed), I want to hide the _caption text and only display the _name text. When it is expanded fully, I obviously want to display both _name & _caption.
How do I go about doing that?
Im new to flutter, so I am somewhat lost on this.
Also reported at https://github.com/flutter/flutter/issues/18567
It's not hard to create your own FlexibleSpaceBar.
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
body: SafeArea(
child: MyHomePage(),
),
),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
ScrollController controller = ScrollController();
#override
Widget build(BuildContext context) {
return CustomScrollView(
physics: ClampingScrollPhysics(),
controller: controller,
slivers: [
SliverAppBar(
expandedHeight: 220.0,
floating: true,
pinned: true,
elevation: 50,
backgroundColor: Colors.pink,
leading: IconButton(
icon: Icon(Icons.menu),
onPressed: () {},
),
flexibleSpace: _MyAppSpace(),
),
SliverList(
delegate: SliverChildListDelegate(
List.generate(
200,
(index) => Card(
child: Padding(
padding: EdgeInsets.all(10),
child: Text('text $index'),
),
),
),
),
)
],
);
}
}
class _MyAppSpace extends StatelessWidget {
const _MyAppSpace({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, c) {
final settings = context
.dependOnInheritedWidgetOfExactType<FlexibleSpaceBarSettings>();
final deltaExtent = settings.maxExtent - settings.minExtent;
final t =
(1.0 - (settings.currentExtent - settings.minExtent) / deltaExtent)
.clamp(0.0, 1.0) as double;
final fadeStart = math.max(0.0, 1.0 - kToolbarHeight / deltaExtent);
const fadeEnd = 1.0;
final opacity = 1.0 - Interval(fadeStart, fadeEnd).transform(t);
return Stack(
children: [
Center(
child: Opacity(
opacity: 1 - opacity,
child: getTitle(
'Collapsed Title',
)),
),
Opacity(
opacity: opacity,
child: Stack(
alignment: Alignment.bottomCenter,
children: [
getImage(),
getTitle(
'Expended Title',
)
],
),
),
],
);
},
);
}
Widget getImage() {
return Container(
width: double.infinity,
child: Image.network(
'https://source.unsplash.com/daily?code',
fit: BoxFit.cover,
),
);
}
Widget getTitle(String text) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
text,
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontSize: 26.0,
fontWeight: FontWeight.bold,
),
),
);
}
}
You can use AnimatedOpacity class.
flexibleSpace: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
var top = constraints.biggest.height;
return FlexibleSpaceBar(
title: AnimatedOpacity(
duration: Duration(milliseconds: 300),
//opacity: top > 71 && top < 91 ? 1.0 : 0.0,
child: Text(
top > 71 && top < 91 ? "Collapse" : "Expanded",
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
)),
background: Image.network(
"https://images.ctfassets.net/pjshm78m9jt4/383122_header/d79a41045d07d114941f7641c83eea6d/importedImage383122_header",
fit: BoxFit.cover,
));
}),
Can check original answer from this link
https://stackoverflow.com/a/53380630/9719695
It can be done like this :
inside your initState method add the scroll listener like that :
ScrollController _controller;
bool silverCollapsed = false;
String myTitle = "default title";
#override
void initState() {
super.initState();
_controller = ScrollController();
_controller.addListener(() {
if (_controller.offset > 220 && !_controller.position.outOfRange) {
if(!silverCollapsed){
// do what ever you want when silver is collapsing !
myTitle = "silver collapsed !";
silverCollapsed = true;
setState(() {});
}
}
if (_controller.offset <= 220 && !_controller.position.outOfRange) {
if(silverCollapsed){
// do what ever you want when silver is expanding !
myTitle = "silver expanded !";
silverCollapsed = false;
setState(() {});
}
}
});
}
then wrap your silverAppBar inside CustomScrollView and add the controller to this CustomScrollView like that :
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: CustomScrollView(
controller: _controller,
slivers: <Widget>[
SliverAppBar(
expandedHeight: 300,
title: myTitle,
flexibleSpace: FlexibleSpaceBar(),
),
SliverList(
delegate: SliverChildListDelegate(<Widget>[
// your widgets inside here !
]),
),
],
),
);
}
finally change the condition value _controller.offset > 220 to fit your need !
FlexibleSpaceBar per se won't be enough. You need to wrap it into CustomScrollView and SliverAppBar. These widgets must be controller by a ScrollController, which will fire an event whenever scroll offset changes. Based on it, you can find out if app bar is collapsed or expanded, and change the content accordingly. Here you will find a working example.
Give an height in padding in FlexibleSpaceBar
flexibleSpace: FlexibleSpaceBar(
titlePadding: EdgeInsets.only(
top: 100, // give the value
title: Text(
"Test"
),
Follow up to Vishnu Suresh answer:
flexibleSpace: FlexibleSpaceBar(
titlePadding: EdgeInsets.only(
top: kToolbarHeight, // give the value
title: Text(
"Test"
),
This will use the appbar height for the padding.
I would like to display a large FlutterLogo in my app:
https://docs.flutter.io/flutter/material/FlutterLogo-class.html
In order to account for varying screen sizes I would like to make it stretch-to fill. Is that possible? Or do I need to use a MediaQuery to determine the parent's size and pass that into FlutterLogo(size:)?
My current code:
Widget build(BuildContext context) {
return new Center(
child: new FlutterLogo(size: 800.0, style: FlutterLogoStyle.horizontal, textColor: Colors.white),
);
}
You can accomplish this with a ConstrainedBox:
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
theme: new ThemeData.dark(),
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(title: new Text('Example App')),
body: new ConstrainedBox(
constraints: new BoxConstraints.expand(),
child: new FlutterLogo(
style: FlutterLogoStyle.horizontal,
textColor: Colors.white,
),
),
);
}
}
I believe I have answered a similar question
How to stretch an icon to fill parent?
https://docs.flutter.io/flutter/widgets/Expanded-class.html
https://groups.google.com/forum/#!msg/flutter-dev/lsgdU1yl7xc/0pYS2qrzBQAJ
https://docs.flutter.io/flutter/widgets/FittedBox-class.html
https://docs.flutter.io/flutter/painting/BoxFit-class.html
new Expanded(
child: new FittedBox(
fit: BoxFit.fill,
child: new FlutterLogo( style: FlutterLogoStyle.horizontal, textColor: Colors.white),
),
),
I feel kinda strange. Looking at the OP profile ID, I wonder if I answer the question correctly.
I hope this helps.
used this code to run it
import 'package:flutter/material.dart';
class MyAppBar extends StatelessWidget {
MyAppBar({this.title});
// Fields in a Widget subclass are always marked "final".
final Widget title;
#override
Widget build(BuildContext context) {
return new Container(
height: 56.0, // in logical pixels
padding: const EdgeInsets.symmetric(horizontal: 8.0),
decoration: new BoxDecoration(color: Colors.blue[500]),
// Row is a horizontal, linear layout.
child: new Row(
// <Widget> is the type of items in the list.
children: <Widget>[
new IconButton(
icon: new Icon(Icons.menu),
tooltip: 'Navigation menu',
onPressed: null, // null disables the button
),
// Expanded expands its child to fill the available space.
new Expanded(
child: title,
),
new IconButton(
icon: new Icon(Icons.search),
tooltip: 'Search',
onPressed: null,
),
],
),
);
}
}
class MyScaffold extends StatelessWidget {
#override
Widget build(BuildContext context) {
// Material is a conceptual piece of paper on which the UI appears.
return new Material(
// Column is a vertical, linear layout.
child: new Column(
children: <Widget>[
new MyAppBar(
title: new Text(
'Example title',
style: Theme.of(context).primaryTextTheme.title,
),
),
new Expanded(
child: new FittedBox(
fit: BoxFit.fill,
child: new FlutterLogo( style: FlutterLogoStyle.horizontal, textColor: Colors.white),
),
),
],
),
);
}
}
void main() {
runApp(new MaterialApp(
title: 'My app', // used by the OS task switcher
home: new MyScaffold(),
));
}
edit: I posted complete code just for darky, since I forgot to mention that expanded needs to be wrapped into row, column, or flex container to expand