How to keep AppBar and back arrow stationary when navigating to a new screen - flutter

I want to have the top half of by screen appear static when navigating between pages in Flutter.
To try to make this happen I put use the Hero widget and use it on a column that contains an AppBar and some other content that I want to appear static when pushing a new page.
The App Bar itself remains static but the back arrow disappears when the animation starts and reappears when the animation is done.
How can I have the back arrow remain visible the entire time while the rest of the page is animating into place?
class FirstScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Hero(
tag: 'top',
child: Column(
children: <Widget>[
AppBar(
title: Text('First'),
backgroundColor: Color.fromARGB(255, 50, 64, 182),
),
Container(
height: 80.0,
)
],
),
),
RaisedButton(
child: Text('Next'),
onPressed: () {
Navigator.pushNamed(context, '/second');
},
),
],
),
);
}
}
class SecondScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Hero(
tag: 'top',
child: Column(
children: <Widget>[
AppBar(
title: Text('Second'),
),
Container(
height: 80.0,
// color: Colors.green,
),
],
),
),
RaisedButton(
child: Text('Back'),
onPressed: () {
Navigator.pop(context);
},
),
],
),
);
}
}

Things weren't quite set up right in your code. It should go Scaffold/Hero/your content. I've also used this simple fading page route when performing the navigation:
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First'),
leading: Icon(null),
backgroundColor: Color.fromARGB(255, 50, 64, 182)),
body: Hero(
tag: 'top',
child: Column(
children: <Widget>[
Container(height: 80.0),
RaisedButton(
child: Text('Next'),
onPressed: () {
Navigator.push(context, MyCustomRoute(builder: (context) {
return SecondScreen();
}));
},
),
],
),
),
);
}
}
class SecondScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Second'),
leading: IconButton(icon: Icon(Icons.arrow_back), onPressed: () {
Navigator.pop(context);
},),
backgroundColor: Color.fromARGB(255, 50, 64, 182)),
body: Hero(
tag: 'top',
child: Column(
children: <Widget>[
Container(height: 80.0),
RaisedButton(
child: Text('Back'),
onPressed: () {
Navigator.pop(context);
},
),
],
),
),
);
}
}
class MyCustomRoute<T> extends MaterialPageRoute<T> {
MyCustomRoute({ WidgetBuilder builder, RouteSettings settings })
: super(builder: builder, settings: settings);
#override
Widget buildTransitions(BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
if (settings.isInitialRoute)
return child;
// Fades between routes. (If you don't want any animation,
// just return child.)
return new FadeTransition(opacity: animation, child: child);
}
}

You could do automaticallyImplyLeading: false and then do
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () => Navigator.of(context).pop(),
),

I have it done this way, by adding automaticallyImplyLeading: true,
Hope this solves your problem!
appBar: AppBar(
automaticallyImplyLeading: true,
),

Related

Flutter: have drawer block bottom navigation bar

I have a simple app that has 4 main pages as mentioned on the bottom navigation bar, and also a subpage (a page you can access from the main page but its not a page you can access from the bottom navigation bar.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
class Page extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: const Text('Home'),
),
body: Container(
child: const Center(
child: Text("Random Page"),
),
),
);
}
}
class PageDrawer extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
endDrawer: const Drawer(),
backgroundColor: Colors.white,
appBar: AppBar(
title: const Text('drawer'),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text("I want the drawer to cover the nav bar here"),
Center(
child: TextButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (_) => SubPage()),
);
},
child: const Text('Go to sub page'),
),
),
],
),
),
);
}
}
class SubPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
backgroundColor: Colors.green,
title: const Text('subpage'),
),
body: const Padding(
padding: EdgeInsets.all(15.0),
child: Center(
child: Text(
"The navbar is active here and has a different appbar from the previous page"),
),
));
}
}
class CustomTab extends StatelessWidget {
CustomTab({required this.child});
final Widget child;
late BuildContext tabContext;
#override
Widget build(BuildContext context) {
return CupertinoTabView(
builder: (BuildContext context) {
tabContext = context;
return child;
},
);
}
}
class Tabbed extends StatefulWidget {
#override
_TabbedState createState() => _TabbedState();
}
class _TabbedState extends State<Tabbed> {
int _currentTab = 0;
final List<CustomTab> tabs = <CustomTab>[
CustomTab(
child: Page(),
),
CustomTab(
child: PageDrawer(),
),
CustomTab(
child: Page(),
),
CustomTab(
child: Page(),
),
];
Future<void> _setTab(int index) async {
if (_currentTab == index) {
if (Navigator.of(tabs[index].tabContext).canPop()) {
Navigator.of(tabs[index].tabContext)
.popUntil((Route<dynamic> r) => r.isFirst);
}
return;
}
setState(() {
_currentTab = index;
});
}
#override
Widget build(BuildContext context) {
return Material(
child: Column(
children: <Widget>[
_buildStack(),
_buildTabs(),
],
),
);
}
Widget _buildStack() {
return Expanded(
child: Container(
decoration: const BoxDecoration(
color: Colors.white,
),
child: IndexedStack(
sizing: StackFit.expand,
index: _currentTab,
children: tabs,
),
),
);
}
Widget _buildTabs() {
return Container(
color: Colors.blue,
child: SafeArea(
top: false,
child: SizedBox(
height: 55.0,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
IconButton(
iconSize: 30.0,
color: _currentTab == 0
? const Color.fromRGBO(58, 66, 86, 1.0)
: const Color.fromRGBO(58, 66, 86, 0.3),
icon: const Icon(Icons.home),
onPressed: () {
_setTab(0);
},
),
IconButton(
iconSize: 30.0,
color: _currentTab == 1
? const Color.fromRGBO(58, 66, 86, 1.0)
: const Color.fromRGBO(58, 66, 86, 0.3),
icon: const Icon(Icons.search),
onPressed: () {
_setTab(1);
},
),
IconButton(
iconSize: 30.0,
color: _currentTab == 2
? const Color.fromRGBO(58, 66, 86, 1.0)
: const Color.fromRGBO(58, 66, 86, 0.3),
icon: const Icon(Icons.notifications),
onPressed: () {
_setTab(2);
},
),
IconButton(
iconSize: 30.0,
color: _currentTab == 3
? const Color.fromRGBO(58, 66, 86, 1.0)
: const Color.fromRGBO(58, 66, 86, 0.3),
icon: const Icon(Icons.settings),
onPressed: () {
_setTab(3);
},
)
],
),
),
),
);
}
}
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Tabbed(),
);
}
}
void main() => runApp(App());
The issue I have with it currently is that I want the drawer (on the second page) to open over the navigational bar. I understand that to do so I must have the drawer on the same level or above the navbar, but the only way I can think of doing that is to have a predefined appbar in the scaffold. The reason this doesnt work for me is that I would like the sub page to have a different appbar style but I have no known way of differentiating between them since the page index for the subpage and the main page is the same.
So this would be an invalid solution:
appbar: (_index == 1) ? Appbar() : null,
At this point im out of ideas and would appreciate any suggestions :)

How to have a drawer with 2 separate colors in Flutter

I'm trying to figure out how to have the green fill up the entire drawer space under the yellow header. Right now I have my ListTiles wrapped in a Column, in a Container, with the Container color set to green. All help is appreciated.
What I have so far
One of the ways could be wrapping the ListView that you are probably using with a Container that has color green. Please see the code below :
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 StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Flutter Demo"),
),
body: Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
color: Colors.white,
),
drawer: Container(
color: Colors.green,
child: ListView(
children: <Widget>[
DrawerHeader(
padding: const EdgeInsets.all(0),
child: Container(
child: const Center(child: Text("Profile")),
color: Colors.yellow,
),
),
ListTile(
title: const Text('Item 1'),
tileColor: Colors.green,
onTap: () {
Navigator.pop(context);
},
),
ListTile(
title: const Text('Item 2'),
tileColor: Colors.green,
onTap: () {
Navigator.pop(context);
},
),
],
),
),
);
}
}

How to display alert bar below of AppBar in flutter

The form are inside CustomScrollView and I want alertbar always pinned below appbar and disappear when tab X.
Currently code
import 'package:flutter/material.dart';
class BaseAppBar extends StatelessWidget {
final Widget title;
final bool innerBoxIsScrolled;
BaseAppBar({this.title, this.innerBoxIsScrolled=false});
#override
Widget build(BuildContext context) {
return SliverAppBar(
backgroundColor: Colors.amber,
pinned: true,
floating: false,
forceElevated: innerBoxIsScrolled,
title: title,
leading: FlatButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(50),
),
child: Icon(
Icons.arrow_back_ios,
color: Colors.white,
size: 20,
),
onPressed: () {
Navigator.of(context).pop();
},
)
);
}
}
class BaseLayout extends StatelessWidget {
final Widget appBar;
final Widget alertBar;
final Widget child;
BaseLayout({this.appBar, this.alertBar, this.child});
#override
Widget build(BuildContext context) {
return CustomScrollView(
slivers: <Widget>[
BaseAppBar(
title: Text(
'test'
),
),
SliverToBoxAdapter(
child: alertBar,
),
SliverToBoxAdapter(
child: child,
)
],
);
}
}
I think it is better to do it this way. Don't overcomplicate things with useless Widget inheritance for the AlertBar.
class _BaseLayoutState extends State<BaseLayout> {
bool _showAlert = false;
#override
Widget build(BuildContext context) {
return Scaffold(
body: SizedBox(
height: MediaQuery.of(context).size.height,
child: CustomScrollView(
slivers: <Widget>[
BaseAppBar(
title: Text('test'),
),
_showAlert
? SliverToBoxAdapter(
child: SizedBox(
height: 80.0,
child: ListTile(
leading: Icon(Icons.error_outline),
title: Text("Please correct form data."),
trailing: IconButton(
onPressed: () {
_showAlert = false;
setState(() {});
},
icon: Icon(Icons.clear),
),
),
),
)
: SliverToBoxAdapter(
child: SizedBox(),
),
/// The rest of the screen where the form and text fields are
SliverFillRemaining(
child: ListView(
children: <Widget>[
Form(
child: Column(
children: <Widget>[
TextFormField(),
TextFormField(),
TextFormField()
],
),
),
/// alert button
Center(
child: RaisedButton(
child: Text('ALERT!'),
onPressed: () {
_showAlert = true;
/// make it go away after a few seconds
Future.delayed(Duration(seconds: 3), () {
_showAlert = false;
setState(() {});
});
setState(() {});
},
),
),
],
),
),
],
),
),
);
}
}

bottom navigation bar with Floating Action Button flutter

I want to make a bottom navigation bar, with a Floating Action Button, in Flutter
when I click the Floating Action Button, the window will appear on the screen,
the photo in below will help to understand my request:
You can copy paste run full code below
You can use OverlayEntry and set color
entry = OverlayEntry(
builder: (context) {
return Center(
child: Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
color: Colors.black.withOpacity(0.5),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
RaisedButton(
onPressed: () {
entry.remove();
},
child: Text("abc"),
working demo
full code
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class BottomAppBarPage extends StatefulWidget {
#override
_BottomAppBarPageState createState() => _BottomAppBarPageState();
}
class _BottomAppBarPageState extends State<BottomAppBarPage> {
OverlayEntry entry;
#override
void initState() {
super.initState();
entry = OverlayEntry(
builder: (context) {
return Center(
child: Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
color: Colors.black.withOpacity(0.5),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
RaisedButton(
onPressed: () {
entry.remove();
},
child: Text("abc"),
),
RaisedButton(
onPressed: () {
entry.remove();
},
child: Text("def"),
),
RaisedButton(
onPressed: () {
entry.remove();
},
child: Text("123"),
),
],
),
),
);
},
);
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Bottom App Bar')),
body: ListView(
padding: const EdgeInsets.all(8),
children: <Widget>[
Container(
height: 50,
color: Colors.amber[600],
child: const Center(child: Text('Entry A')),
),
Container(
height: 50,
color: Colors.amber[500],
child: const Center(child: Text('Entry B')),
),
Container(
height: 50,
color: Colors.amber[100],
child: const Center(child: Text('Entry C')),
),
],
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () {
Overlay.of(context).insert(entry);
},
),
bottomNavigationBar: BottomAppBar(
shape: CircularNotchedRectangle(),
notchMargin: 4.0,
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
IconButton(
icon: Icon(Icons.menu),
onPressed: () {},
),
IconButton(
icon: Icon(Icons.search),
onPressed: () {},
)
],
),
),
);
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: BottomAppBarPage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}

Scaffold Drawer to showModalBottomSheet

At the homescreen of myApp() I have a stateless widget, it contains a MaterialApp and a Scaffold. Scaffold have a property of drawer and I passed I created a drawer, and one of the item in my drawer needs to open the showModalBottomSheet while closing the drawer. How can I achieve this? I've tried passing the context itself, and as globalKey.currentContext (after GlobalKey<ScaffoldState> globalKey = GlobalKey();) but the drawer sometimes closes, other time gives me a NoMethodFoundException (or something like that)
In short, how to have a Scaffold drawer that have one of the item, when tapped closes the drawer and showModalBottomSheet?
Current code:
class Timeline extends StatelessWidget {
#override
Widget build(BuildContext context) {
GlobalKey<ScaffoldState> homeScaffoldKey = GlobalKey();
return MaterialApp(
title: "Test",
theme: ThemeData(
appBarTheme: AppBarTheme(iconTheme: IconThemeData(color: Colors.black)),
),
home: Scaffold(
key: homeScaffoldKey,
drawer: showDrawer(homeScaffoldKey.currentContext),
backgroundColor: Colors.grey[100],
body: Stack(
children: <Widget>[
HomePageView(),
AppBar(
elevation: 0,
backgroundColor: Colors.transparent,
),
],
),
),
);
}
}
Drawer showDrawer(BuildContext context) {
void showCalendarsModalBottom() {
showModalBottomSheet(
context: context,
builder: (BuildContext builder) {
return ListView.builder(
itemCount: repo.calendars.length,
itemBuilder: (builder, index) {
return StatefulBuilder(
builder: (builder, StateSetter setState) => ListTile(
leading: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Checkbox(
value: repo.getIsEnabledCal(repo.getCal(index)),
onChanged: (value) {
setState(() {
repo.toggleCalendar(repo.getCal(index));
});
},
),
Container(
height: 14,
width: 14,
margin: EdgeInsets.only(left: 2, right: 6),
decoration: BoxDecoration(
color: Colors.redAccent,
shape: BoxShape.circle,
),
),
Text(
repo.getCal(index).name,
style: TextStyle(
fontSize: 16,
),
),
],
),
onTap: () {
setState(() {
repo.toggleCalendar(repo.getCal(index));
});
},
),
);
},
);
},
);
}
return Drawer(
child: ListView(
children: <Widget>[
DrawerHeader(
child: Align(
child: Text('Timeline', textScaleFactor: 2),
alignment: Alignment.bottomLeft,
),
),
ListTile(
title: Text('Dark Mode'),
onTap: () => Navigator.pop(context),
),
ListTile(
title: Text('Calenders'),
onTap: () {
Navigator.pop(context);
showCalendarsModalBottom();
},
)
],
),
);
}
Updated working code based on your code snippet:
You'll need to have statefulwidget that will help to pass the context from drawer to bottomsheet and pass the context as an argument in showCalendarModalBottomSheet() method.
void main() {
runApp(new MaterialApp(home: Timeline(), debugShowCheckedModeBanner: false));
}
class Timeline extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: "Test",
theme: ThemeData(
appBarTheme: AppBarTheme(iconTheme: IconThemeData(color: Colors.black)),
),
home: MyHomePage()
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: AppDrawer(),
backgroundColor: Colors.grey[100],
body: Stack(
children: <Widget>[
//HomePageView(),
AppBar(
elevation: 0,
backgroundColor: Colors.transparent,
)
],
)
);
}
Widget AppDrawer() {
return Drawer(
child: ListView(
children: <Widget>[
DrawerHeader(
child: Align(
child: Text('Timeline', textScaleFactor: 2),
alignment: Alignment.bottomLeft,
),
),
ListTile(
title: Text('Dark Mode'),
onTap: () => Navigator.pop(context),
),
ListTile(
title: Text('Calenders'),
onTap: () {
Navigator.of(context).pop();
showCalendarsModalBottom(context);
},
)
],
),
);
}
Future<Null> showCalendarsModalBottom(context) {
return showModalBottomSheet(context: context, builder: (context) => Container(
color: Colors.red,
// your code here
));
}
}
And the output is: When app drawer menu Calendar is tapped, it closes and opens the bottomsheet seamlessly. If you tap on app drawer again and repeat steps, you see smooth transition between drawer and bottomsheet. Hope this answers your question.