I'm trying to use Navigator.push to navigate to a new page in Flutter. This is what I've got so far:
GestureDetector(
onTap: () {
print('Test');
// Navigator.push(context,
// MaterialPageRoute(builder: (context) => ResultsPage()));
},
child: Container(
color: Color(0xFFff474b),
child: Center(
child: Text('CALCULATE', style: kButtonText),
),
padding: EdgeInsets.only(bottom: 20.0),
width: double.infinity,
height: 80.0,
margin: EdgeInsets.only(top: 10.0),
),
),
The code that I want to implement is commented out, as I've been testing the onTap with a print statement.
Interestingly, the print statement runs but for some reason, I can't get the Navigator.push to work. It only navigates to a black screen.
For context, this is the Results page - just a simple scaffold with an App Bar. But the App Bar doesn't show up on the next page:
class ResultsPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Calculated Distance'),
),
);
}
}
The error may be caused due to two or more Floating Action buttons present in a scaffold. By default Floating Action button has a Hero property active. Make it deactivate by heroTag: null,.
For eg,
FloatingActionButton(
heroTag: null,
child: Icon(),
onPressed: () {},
),
Ahh it turns out that the issues is that I have two FloatingActionButtons on the page, and so it messes with the Navigator route.
This Medium article I found is a nice guide for the solution: https://medium.com/#kaendagger/test-cef30fcb5c54
add ResultsPage() to your routes add this to you MaterialApp(
MaterialApp(
initialRoute: '/',
routes: {
'/': (_) => HOME(),
'/home': (_) => ResultsPage(),
}, )
How can i change the position of my text "Sito Web" on the right of the appbar? I tryid with
alignment: Alignment.TopRight
but he move it the text on the top right corner down the appbar. Here a screen of what i mean https://ibb.co/X28TzNN change the position of "Sito Web" in the position of the red cirle. That's the codes, i tryid with this method too, but can't move in the appbar. Here the file with the appbar background_image_task-9.dart
import 'package:flutter/material.dart';
class BackgroundImage extends StatelessWidget{
final Widget body;
BackgroundImage({this.body});
#override
Widget build(BuildContext context){
return Scaffold(
appBar: AppBar(
elevation: 0,
title: Text('Blumax', style: TextStyle(
fontWeight: FontWeight.w500,
fontFamily: 'DancingScript',
fontSize: 40
),),
centerTitle: false,
),
body: Stack(
children: <Widget>[Container(
decoration: BoxDecoration(
image: DecorationImage(image: AssetImage("assets/blumax.jpg"), fit: BoxFit.cover),
),
),
body
]
)
);
}
}
And here the file with the container hyperlink.dart
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
class Hyperlink extends StatelessWidget {
final String _url;
final String _text;
Hyperlink(this._url, this._text);
_launchURL() async {
if (await canLaunch(_url)) {
await launch(_url);
} else {
throw 'Could not launch $_url';
}
}
#override
Widget build(BuildContext context) {
return InkWell(
child: Container(
alignment: Alignment(0.9, -1.0),
child: Text(
_text,
textAlign: TextAlign.right,
style: TextStyle(
fontFamily: 'RobotoMono',
fontSize: 20,
color: Colors.white,
decoration: TextDecoration.underline),
)),
onTap: _launchURL,
);
}
}
main.dart
import 'package:flutter/material.dart';
import 'background_image_task-9.dart';
import 'hyperlink.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Blumax',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: myColour
),
home: BackgroundImage(
body: Center(
child: Hyperlink('www.test.it', 'Sito Web',),
),
)
);
}
}
You can use Row widget and use mainAxisAlignment: MainAxisAlignment.spaceBetween to render BluMax and Sito Web elements on left and right of the appbar respectively. And since you want to open a url after tapping on sito web, you can wrap that element with GestureDetector and then call launchURL method that opens required url. A working sample code is as below:
appBar: AppBar(
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text('Blumax'),
GestureDetector(
child: Text('Sito Web'),
onTap: _openURL
)
],
),
centerTitle: false,
actions: <Widget>[
PopupMenuButton<int>(
itemBuilder: (context) =>
[
PopupMenuItem(
value: 1,
child: Text("First"),
),
PopupMenuItem(
value: 2,
child: Text("Second"),
),
],
)
],
),
This is rendered on screen as:
...
void _openURL() async {
const url = 'https://flutter.dev';
if (await canLaunch(url)) {
await launch(url);
} else {
throw 'Could not launch $url';
}
}
I just used a text Blumax for demo, but you may replace it per your need.
Hope this answers your question.
I have two screen A and B, screen A has a bottom navigation bar.
After i pushed screen A to screen B, bottom navigation bar of screen A still pin on screen B.
I want show full screen B without bottom navigation of screen A.
This is a screen A, it has a bottom naviagtion bar:
class Parent extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'TechOne',
theme: ThemeData(
primarySwatch: Colors.red,
),
home: MyParentPage(title: 'TechOne'),
);
}
}
/*StatefulWidget is Widget with mutable*/
class MyParentPage extends StatefulWidget {
MyParentPage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyParentPageState createState() => _MyParentPageState();
}
/*State is a manager of StatefulWidget*/
class _MyParentPageState extends State<MyParentPage>
with SingleTickerProviderStateMixin {
var _itemSelected = 0;
TabController _tabController;
final _bodyUI = [
HomeUI(),
SearchUI(),
Center(
child: Text('Notification'),
),
Center(
child: Text('Account'),
),
];
_onBottomNavigationBarTap(int index) {
print(_itemSelected);
setState(() {
_itemSelected = index;
});
}
#override
void initState() {
// TODO: implement initState
super.initState();
_tabController = TabController(length: 4, vsync: this);
}
#override
Widget build(BuildContext context) {
_tabController.animateTo(_itemSelected);
return Scaffold(
bottomNavigationBar: BottomNavigationBar(
items: Values.itemsBottomNavigationBar,
onTap: (index) {
_onBottomNavigationBarTap(index);
},
currentIndex: _itemSelected,
selectedItemColor: Colors.red,
backgroundColor: Colors.white,
type: BottomNavigationBarType.fixed,
),
body: TabBarView(
physics: NeverScrollableScrollPhysics(),
controller: _tabController,
children: <Widget>[
_bodyUI[0],
_bodyUI[0],
_bodyUI[2],
_bodyUI[3],
]));
}
}
Inside _bodyUI[0] widget, I push to screen B:
Navigator.push(context, MaterialPageRoute(builder: (context) => SearchUI()));
This is a screen B, bottom navigation bar still pin on here, i want hidden it:
class SearchUI extends StatelessWidget {
#override
Widget build(BuildContext context) {
// TODO: implement build
return MaterialApp(
title: 'Search',
theme: ThemeData(primarySwatch: Colors.red),
home: MySearchPage(),
);
}
}
class MySearchPage extends StatefulWidget {
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return _MySearchState();
}
}
class _MySearchState extends State<MySearchPage> {
final TextEditingController _textEditingController = TextEditingController();
final FocusNode _focusNode = FocusNode();
TextField _appBarTitle;
#override
void initState() {
// TODO: implement initState
super.initState();
_appBarTitle = TextField(
controller: _textEditingController,
focusNode: _focusNode,
autofocus: true,
textInputAction: TextInputAction.done,
textCapitalization: TextCapitalization.sentences,
cursorColor: Colors.white,
cursorRadius: Radius.circular(16),
maxLines: 1,
style: TextStyle(
color: Colors.white,
),
decoration: InputDecoration(
border: InputBorder.none,
prefixIcon: Icon(
Icons.search,
color: Colors.white,
),
suffixIcon: IconButton(icon: Icon(Icons.clear, color: Colors.white,), onPressed: (){
_textEditingController.clear();
}),
hintText: 'Search...',
hintStyle:
TextStyle(color: Colors.white.withOpacity(0.5), fontSize: 18)));
}
#override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: _appBarTitle,
),
body: Center(
child: Text('Search Screen'),
),
);
}
}
Your code is calling a SearchUI() class as one of the TabBarViews :
final _bodyUI = [
HomeUI(),
SearchUI(),
Center(
child: Text('Notification'),
),
Center(
child: Text('Account'),
),
];
Which means it's going to only change the view and keep the bar there.
EDIT : in a comment I deleted earlier I mentioned the nested MaterialApps may cause an issue. That seems to help correct the problem but in the comments below you now mention the addition of a back arrow. The quote below is taken from the Flutter documentation for AppBar.
If the leading widget is omitted, but the AppBar is in a Scaffold with
a Drawer, then a button will be inserted to open the drawer.
Otherwise, if the nearest Navigator has any previous routes, a
BackButton is inserted instead. This behavior can be turned off by
setting the automaticallyImplyLeading to false. In that case a null
leading widget will result in the middle/title widget stretching to
start.
Basically, that is something you can turn off using the property mentioned above.
I created a new project and wrote some code in visual studio code.
Here is my source code :
import 'package:flutter/material.dart';
class Raisedbutton extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
title: "Raised Button",
home: Scaffold(
appBar: AppBar(
title: Text("Latihan membuat Raised button"),
backgroundColor: Colors.green),
body: Center(
child: Column(
children: <Widget>[
Expanded(
child: Row(
children: <Widget>[
Text("1. Pizza"),
Text("2. Satay"),
Text("3. Spagethi"),
],
),
),
],
),
),
),
);
}
}
class Raised extends StatelessWidget {
Widget build(BuildContext context) {
var button = Container(
margin: EdgeInsets.only(top: 50.0),
child: RaisedButton(
child: Text("Click here"),
color: Colors.blue,
elevation: 5.0,
onPressed: () {
order(context);
}),
);
}
void order(BuildContext context) {
var alert = AlertDialog(title:Text("CONGRATULATION", style: TextStyle(color: Colors.white, fontSize: 25.0),),content: Text("You got ZONK!!!"),);
}
}
And yes, my source code which is RaisedButton got an error.
How can I fix that? thanks for your help!
RaisedButton is now deprecated and replaced by ElevatedButton. Based on the documentation:
FlatButton, RaisedButton, and OutlineButton have been replaced by
TextButton,
ElevatedButton,
and
OutlinedButton
respectively. ButtonTheme has been replaced by
TextButtonTheme,
ElevatedButtonTheme,
and
OutlinedButtonTheme.
The original classes will eventually be removed, please migrate code
that uses them. There's a detailed migration guide for the new button
and button theme classes in
flutter.dev/go/material-button-migration-guide.
RaisedButton is now deprecated.
use ElevatedButton instead of RaisedButton
RaisedButton is now deprecated. you can use ElevatedButton instead.
check more here https://educity.app/flutter/create-a-button-with-border-radius
RaisedButton and Flatbutton are now deprecated, but to use the same button configs change to MaterialButton, you wont have to redo your button styling
I would like to achieve the material design card behavior on tap. When I tap it, it should expand fullscreen and reveal additional content/new page. How do I achieve it?
https://material.io/design/components/cards.html#behavior
I tried with Navigator.of(context).push() to reveal new page and play with Hero animations to move the card background to new Scaffold, however it seems it is not the way to go since new page is not revealing from the card itself, or I cannot make it to. I am trying to achieve the same behavior as in the material.io that I presented above. Would you please guide me somehow?
Thank you
A while ago I tried replicating that exact page/transition and while I didn't get it to look perfectly like it, I did get fairly close. Keep in mind that this was put together quickly and doesn't really follow best practices or anything.
The important part is the Hero widgets, and especially the tags that go along with them - if they don't match, it won't do it.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
backgroundColor: Colors.deepPurple,
),
body: ListView.builder(
itemBuilder: (context, index) {
return TileItem(num: index);
},
),
),
);
}
}
class TileItem extends StatelessWidget {
final int num;
const TileItem({Key key, this.num}) : super(key: key);
#override
Widget build(BuildContext context) {
return Hero(
tag: "card$num",
child: Card(
shape: RoundedRectangleBorder(
borderRadius: const BorderRadius.all(
Radius.circular(8.0),
),
),
clipBehavior: Clip.antiAliasWithSaveLayer,
child: Stack(
children: <Widget>[
Column(
children: <Widget>[
AspectRatio(
aspectRatio: 485.0 / 384.0,
child: Image.network("https://picsum.photos/485/384?image=$num"),
),
Material(
child: ListTile(
title: Text("Item $num"),
subtitle: Text("This is item #$num"),
),
)
],
),
Positioned(
left: 0.0,
top: 0.0,
bottom: 0.0,
right: 0.0,
child: Material(
type: MaterialType.transparency,
child: InkWell(
onTap: () async {
await Future.delayed(Duration(milliseconds: 200));
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return new PageItem(num: num);
},
fullscreenDialog: true,
),
);
},
),
),
),
],
),
),
);
}
}
class PageItem extends StatelessWidget {
final int num;
const PageItem({Key key, this.num}) : super(key: key);
#override
Widget build(BuildContext context) {
AppBar appBar = new AppBar(
primary: false,
leading: IconTheme(data: IconThemeData(color: Colors.white), child: CloseButton()),
flexibleSpace: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.black.withOpacity(0.4),
Colors.black.withOpacity(0.1),
],
),
),
),
backgroundColor: Colors.transparent,
);
final MediaQueryData mediaQuery = MediaQuery.of(context);
return Stack(children: <Widget>[
Hero(
tag: "card$num",
child: Material(
child: Column(
children: <Widget>[
AspectRatio(
aspectRatio: 485.0 / 384.0,
child: Image.network("https://picsum.photos/485/384?image=$num"),
),
Material(
child: ListTile(
title: Text("Item $num"),
subtitle: Text("This is item #$num"),
),
),
Expanded(
child: Center(child: Text("Some more content goes here!")),
)
],
),
),
),
Column(
children: <Widget>[
Container(
height: mediaQuery.padding.top,
),
ConstrainedBox(
constraints: BoxConstraints(maxHeight: appBar.preferredSize.height),
child: appBar,
)
],
),
]);
}
}
EDIT: in response to a comment, I'm going to write an explanation of how Hero works (or at least how I think it works =D).
Basically, when a transition between pages is started, the underlying mechanism that performs the transition (part of the Navigator more or less) looks for any 'hero' widgets in the current page and the new page. If a hero is found, its size and position is calculated for each of the pages.
As the transition between the pages is performed, the hero from the new page is moved to an overlay in the same place as the old hero, and then its size and position is animated towards its final size and position in the new page. (Note that you can change if you want with a bit of work - see this blog for more information about that).
This is what the OP was trying to achieve:
When you tap on a Card, its background color expands and becomes a background color of a Scaffold with an Appbar.
The easiest way to do this is to simply put the scaffold itself in the hero. Anything else will obscure the AppBar during the transition, as while it's doing the hero transition it is in an overlay. See the code below. Note that I've added in a class to make the transition happen slower so you can see what's going on, so to see it at normal speed change the part where it pushes a SlowMaterialPageRoute back to a MaterialPageRoute.
That looks something like this:
import 'dart:math';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
backgroundColor: Colors.deepPurple,
),
body: ListView.builder(
itemBuilder: (context, index) {
return TileItem(num: index);
},
),
),
);
}
}
Color colorFromNum(int num) {
var random = Random(num);
var r = random.nextInt(256);
var g = random.nextInt(256);
var b = random.nextInt(256);
return Color.fromARGB(255, r, g, b);
}
class TileItem extends StatelessWidget {
final int num;
const TileItem({Key key, this.num}) : super(key: key);
#override
Widget build(BuildContext context) {
return Hero(
tag: "card$num",
child: Card(
color: colorFromNum(num),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(8.0),
),
),
clipBehavior: Clip.antiAliasWithSaveLayer,
child: Stack(
children: <Widget>[
Column(
children: <Widget>[
AspectRatio(
aspectRatio: 485.0 / 384.0,
child: Image.network("https://picsum.photos/485/384?image=$num"),
),
Material(
type: MaterialType.transparency,
child: ListTile(
title: Text("Item $num"),
subtitle: Text("This is item #$num"),
),
)
],
),
Positioned(
left: 0.0,
top: 0.0,
bottom: 0.0,
right: 0.0,
child: Material(
type: MaterialType.transparency,
child: InkWell(
onTap: () async {
await Future.delayed(Duration(milliseconds: 200));
Navigator.push(
context,
SlowMaterialPageRoute(
builder: (context) {
return new PageItem(num: num);
},
fullscreenDialog: true,
),
);
},
),
),
),
],
),
),
);
}
}
class PageItem extends StatelessWidget {
final int num;
const PageItem({Key key, this.num}) : super(key: key);
#override
Widget build(BuildContext context) {
return Hero(
tag: "card$num",
child: Scaffold(
backgroundColor: colorFromNum(num),
appBar: AppBar(
backgroundColor: Colors.white.withOpacity(0.2),
),
),
);
}
}
class SlowMaterialPageRoute<T> extends MaterialPageRoute<T> {
SlowMaterialPageRoute({
WidgetBuilder builder,
RouteSettings settings,
bool maintainState = true,
bool fullscreenDialog = false,
}) : super(builder: builder, settings: settings, fullscreenDialog: fullscreenDialog);
#override
Duration get transitionDuration => const Duration(seconds: 3);
}
However, there are situations in which it might not be optimal to have the entire scaffold doing the transition - maybe it has a lot of data, or is designed to fit in a specific amount of space. In that case, an option to make a version of whatever you want to do the hero transition that is essentially a 'fake' - i.e. have a stack with two layers, one which is the hero and has a background colour, scaffold, and whatever else you want to show up during the transition, and another layer on top which completely obscures the bottom layer (i.e. has a background with 100% opacity) that also has an app bar and whatever else you want.
There are probably better ways of doing it than that - for example, you could specify the hero separately using the method mentioned in the blog I linked to.
I achieved this by using the Flutter Hero Animation Widget. In order to do that you will need:
A source page where you start from and that contains the card you want to expand to full screen. Let's call it 'Home'
A destination page that will represent how your card will look like once expanded. Let's call it 'Details'.
(Optional) A data model to store data
Now let's take a look at this example below (You can find the full project code here):
First, let's make an Item class (i will put it in models/item.dart) to store our data. Each item will have its own id, title, subtitle, details and image url :
import 'package:flutter/material.dart';
class Item {
String title, subTitle, details, img;
int id;
Item({this.id, this.title, this.subTitle, this.details, this.img});
}
Now, let's initialize our material app in the main.dart file :
import 'package:flutter/material.dart';
import 'package:expanding_card_animation/home.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: Home(),
);
}
}
Next, we will make our home page. It'll be a simple stateless widget, and will contain a list of Items that will be displayed in a ListView of Cards. A gesture detector is used to expand the card when tapping it. The expansion is just a navigation to the details page, but with the Hero animation, it looks like it just expanded the Card.
import 'package:flutter/material.dart';
import 'package:expanding_card_animation/details.dart';
import 'package:expanding_card_animation/models/item.dart';
class Home extends StatelessWidget {
List<Item> listItems = [
Item(
id: 1,
title: 'Title 1',
subTitle: 'SubTitle 1',
details: 'Details 1',
img:
'https://d1fmx1rbmqrxrr.cloudfront.net/cnet/i/edit/2019/04/eso1644bsmall.jpg'),
Item(
id: 2,
title: 'Title 2',
subTitle: 'SubTitle 2',
details: 'Details 2',
img:
'https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885__340.jpg'),
Item(
id: 3,
title: 'Title 3',
subTitle: 'SubTitle 3',
details: 'Details 3',
img: 'https://miro.medium.com/max/1200/1*mk1-6aYaf_Bes1E3Imhc0A.jpeg'),
];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home screen'),
),
body: Container(
margin: EdgeInsets.fromLTRB(40, 10, 40, 0),
child: ListView.builder(
itemCount: listItems.length,
itemBuilder: (BuildContext c, int index) {
return GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Details(listItems[index])),
);
},
child: Card(
elevation: 7,
shape: RoundedRectangleBorder(
side: BorderSide(color: Colors.grey[400], width: 1.0),
borderRadius: BorderRadius.circular(10.0),
),
margin: EdgeInsets.fromLTRB(0, 0, 0, 20),
child: Column(
children: [
//Wrap the image widget inside a Hero widget
Hero(
//The tag must be unique for each element, so we used an id attribute
//in the item object for that
tag: '${listItems[index].id}',
child: Image.network(
"${listItems[index].img}",
scale: 1.0,
repeat: ImageRepeat.noRepeat,
fit: BoxFit.fill,
height: 250,
),
),
Divider(
height: 10,
),
Text(
listItems[index].title,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
SizedBox(
height: 20,
),
],
),
),
);
}),
),
);
}
}
Finally, let's make the details page. It's also a simple stateless widget that will take the item's info as an input, and display them on full screen. Note that we wrapped the image widget inside another Hero widget, and make sure that you use the same tags used in the source page(here, we used the id in the passed item for that) :
import 'package:flutter/material.dart';
import 'package:expanding_card_animation/models/item.dart';
class Details extends StatelessWidget {
final Item item;
Details(this.item);
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
),
extendBodyBehindAppBar: true,
body: Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Hero(
//Make sure you have the same id associated to each element in the
//source page's list
tag: '${item.id}',
child: Image.network(
"${item.img}",
scale: 1.0,
repeat: ImageRepeat.noRepeat,
fit: BoxFit.fitWidth,
height: MediaQuery.of(context).size.height / 3,
),
),
SizedBox(
height: 30,
),
ListTile(
title: Text(
item.title,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 20,
),
),
subtitle: Text(item.subTitle),
),
Divider(
height: 20,
thickness: 1,
),
Padding(
padding: EdgeInsets.only(left: 20),
child: Text(
item.details,
style: TextStyle(
fontSize: 25,
),
),
),
],
),
),
),
);
}
}
And that's it, now you can customize it as you wish. Hope i helped.