Difference in navigation transition in Flutter compared to "stock" Android - flutter

Compare the following transitions when navigating between routes.
Flutter: https://imgur.com/ZGspVO3
Android: https://imgur.com/piil7BO
You can see one clear difference with the transitions. The pages are pulled over and back on each other when navigated between them. The style I prefer is this one and not the one that Flutter uses.
Is this a bug or has this transition been changed deliberately?
I use the following code to navigate:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Navigation Transition'),
),
body: Center(
child: RaisedButton(
child: Text('Navigate'),
onPressed: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Scaffold(
appBar: AppBar(
title: Text('Navigation Transition'),
),
body: Center(
child: RaisedButton(
child: Text('Navigate'),
onPressed: () => Navigator.pop(context),
),
),
),
),
),
),
),
);
}

Modifying the apps theme data as follows solved the problem:
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Navigation transitions',
theme: ThemeData(
pageTransitionsTheme: PageTransitionsTheme(
builders: const {
TargetPlatform.android: OpenUpwardsPageTransitionsBuilder(),
},
),
),
....
}
More information regarding the transitions during route changes:
MaterialPageRoute.class Documentation
PageTransitionsTheme.class Documentation
Navigator.class Documentation

Related

Flutter - How can I change the background color of LicensePage?

I'd like to set the background colour of every screen except LicensePage to some colour, so I've specified the scaffoldBackbroundColor via the theme argument of MaterialApp as follows.
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(scaffoldBackgroundColor: Colors.blue.shade200),
home: HomeScreen(),
);
}
}
This changes the background colour of the licences page too, so in order to change it back to white, I tried overriding scaffoldBackbroundColor, but it did not work.
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Theme(
data: Theme.of(context).copyWith(scaffoldBackgroundColor: Colors.white),
child: Center(
child: RaisedButton(
child: const Text('Show licenses'),
onPressed: () => showLicensePage(context: context),
),
),
),
);
}
}
How can I do it?
In my case I found ThemeData(cardColor) was dictating the LicensePage background color. So,
showLicense(BuildContext context) {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (context) => Theme(
data: ThemeData(
cardColor: Colors.yellow,
),
child: LicensePage(
applicationVersion: '0.1',
applicationIcon: Icon(Icons.person),
applicationLegalese: 'Legal stuff',
),
),
),
);
}
I came up with this and it worked successfully.
Scaffold(
body: Center(
child: RaisedButton(
child: const Text('Show licenses'),
onPressed: () => Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (context) => Theme(
data: Theme.of(context).copyWith(
scaffoldBackgroundColor: Colors.white,
),
child: LicensePage(...),
),
),
),
),
),
)
This way you cannot set the theme, set color using Container
Scaffold(
body: Container(
color: Colors.white,
child: Center(
child: RaisedButton(
child: const Text('Show licenses'),
onPressed: () => showLicensePage(context: context),
),
),
),
),

Show a SnackBar Above Modal Bottom Sheet

I tried to display a SnackBar above my Modal Bottom Sheet, but it doesn't work. Any idea how to achieve that?
P.S.: Setting the SnackBar Behavior to Floating doesnt work. It still appears below the modal bottom sheet
Thank you
Just wrap your child widget with Scaffold
await showModalBottomSheet(
context: context,
builder: (builder) => Scaffold(
body: YourModalContentWidget()
)
);
You will need to use GlobalKey<ScaffoldState> to show the Snackbar in desired Scaffold, for that you can added Scaffold in your showModalBottomSheet like below snip;
Define you GlobalKey in your Statefull or Stateless widget class
final GlobalKey<ScaffoldState> _modelScaffoldKey = GlobalKey<ScaffoldState>();
And on button press you can have;
showModalBottomSheet(
context: context,
builder: (_) => Scaffold(
extendBody: false,
key: _modelScaffoldKey,
resizeToAvoidBottomInset: true,
body: FlatButton(
child: Text("Snackbar"),
onPressed: () {
_modelScaffoldKey.currentState.showSnackBar(SnackBar(
content: Text("Snackbar"),
));
},
)),
);
DartPad
Try setting a margin to the Snackbar according to the BottomSheets height:
SnackBar(behavior: SnackBarBehavior.floating, margin: EdgeInsets.fromLTRB(0, 0, 0, 500),)
Thanks #alexey i solve mine this way
showMaterialModalBottomSheet(
context: context,
enableDrag: false,
isDismissible: false,
expand: false,
builder: (context) => Container(
//padding: EdgeInsets.only(bottom: 20),
height: MediaQuery.of(context).size.height * 0.9, //bottom sheet height
color: Colors.black.withOpacity(0.0),
child: Scaffold(
body: NewTripPopupDialog(kInitialPosition: kInitialPosition))),
);
If you are willing to use third-party package, you can easily do this with GetX.
Just import the package in your pubspec.yaml file:
dependencies:
get: ^3.17.0
Change your MaterialApp to GetMaterialApp.
GetMaterialApp is necessary for routes, snackbars, internationalization, bottomSheets, dialogs, and high-level apis related to routes and absence of context.
And use Get.bottomSheet() and Get.snackbar(). You have a bunch of properties to customize both methods. If a BottomModalSheet is open and you create a Snackbar it automatically shown above the sheet.
Simple example:
import 'package:flutter/material.dart';
import 'package:get/get.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return GetMaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: Home(),
);
}
}
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Snackbar above Bottom Modal Sheet'),
),
body: SafeArea(
child: Center(
child: RaisedButton(
child: Text('Open Modal Sheet'),
onPressed: () {
// Bottom Modal Sheet
Get.bottomSheet(
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('My bottom modal sheet'),
RaisedButton(
child: Text('Show SnackBar'),
onPressed: () {
// Snackbar
Get.snackbar(
'Hey',
'I am a snackbar',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.red,
);
},
),
],
),
backgroundColor: Colors.green,
);
},
),
),
),
);
}
}
Have you tried setting the behavior property to floating?
SnackBar(
behavior: SnackBarBehavior.floating
This should solve the issue

How can I change Drawer icon in flutter?

The drawer has this default three horizontal bars as default icon but I want to change it to something else.
I have checked the possible options under the Drawer(), but no property seems to be attached to that.
This should work.
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title:Text('hi'),
leading: IconButton(
icon: Icon(Icons.accessible),
onPressed: () => Scaffold.of(context).openDrawer(),
),
),
);
From the docs ->
{Widget leading} Type: Widget
A widget to display before the [title].
If this is null and [automaticallyImplyLeading] is set to true, the [AppBar] will imply an appropriate widget. For example, if the [AppBar] is in a [Scaffold] that also has a [Drawer], the [Scaffold] will fill this widget with an [IconButton] that opens the drawer (using [Icons.menu]). If there's no [Drawer] and the parent [Navigator] can go back, the [AppBar] will use a [BackButton] that calls [Navigator.maybePop].
The following code shows how the drawer button could be manually specified instead of relying on [automaticallyImplyLeading]:
import 'package:flutter/material.dart';
Widget build(context) {
return AppBar(
leading: Builder(
builder: (BuildContext context) {
return IconButton(
icon: const Icon(Icons.menu),
onPressed: () {
Scaffold.of(context).openDrawer();
},
tooltip: MaterialLocalizations.of(context).openAppDrawerTooltip,
);
},
),
);
}
The [Builder] is used in this example to ensure that the context refers to that part of the subtree. That way this code snippet can be used even inside the very code that is creating the [Scaffold] (in which case, without the [Builder], the context wouldn't be able to see the [Scaffold], since it would refer to an ancestor of that widget).
appBar: AppBar(
leading: Builder(
builder: (context) => IconButton(
icon: Icon(Icons.menu_rounded),
onPressed: () => Scaffold.of(context).openDrawer(),
),
),
title: Text(
"Track your Shipment",
),
),
You can open a drawer with a custom button like this too.
create this scaffold key.
var scaffoldKey = GlobalKey<ScaffoldState>();
now added a scaffolled in your state class like this
#override
Widget build(BuildContext context) {
return Scaffold(
key: scaffoldKey,
drawer: Drawer(
child: Text('create drawer widget tree here'),
),
appBar: AppBar(
backgroundColor: Colors.white,
title: Text(
'appName',
style: Theme.of(context).textTheme.headline2,
),
leading: IconButton(
onPressed: () {
scaffoldKey.currentState?.openDrawer();
},
icon: Image.asset(
'assets/images/menu.png',
fit: BoxFit.cover,
),
),
),
body: Container(),
);
}
Lets say you have: index.dart (where you want to use the appbar), drawer.dart (your drawer or navigation menu) and appbar.dart (your appbar)
you can do this in drawer:
Widget drawer(BuildContext context) {
return Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: <Widget>[
Container(
...
)
);
then your appbar.dart:
class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
#override
Widget build(BuildContext context) {
return AppBar(
backgroundColor: Colors.white,
leading: InkWell(
onTap: () => Scaffold.of(context).openDrawer(),
child: Image.asset("assets/images/imgAppBar.png"),
),
title: Container(...
then your index.dart:
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
drawer: drawer(context),
appBar: CustomAppBar(),
...
this is just a simple one. You can use IconButton in case you want to use an Icon etc.
You need to create the Global key of type ScaffoldKey the use that to open the drawer and change the icon too:
Widget build(BuildContext context) {
var scaffoldKey = GlobalKey<ScaffoldState>();
return Scaffold(
key: scaffoldKey,
appBar: AppBar(
title:Text('hi'),
leading: IconButton(
icon: Icon(Icons.accessible),
onPressed: () => scafoldKey.currentState.openDrawer(),
),
),
);
Actually, i tried the answer by cmd_prompter and it didn't work for me.
The better approach is described here
My working code is here:
return DefaultTabController(
key: Key("homePage"),
length: 2,
child: Scaffold(
endDrawer: Drawer(
),
appBar: AppBar(
leading: BackButton(
onPressed: () {
},
),
title: Text(profile.selectedCity!),
actions: [
Padding(
padding: EdgeInsets.symmetric(horizontal: baseUnit(3)),
child: Builder(
builder: (context) => IconButton(
icon: Icon(Icons.account_circle),
onPressed: () => Scaffold.of(context).openEndDrawer(),
)
)
)
It worked fine for me - especially this part regarding using Builder. This is important - otherwise it was not working for me.
class HomeOne extends StatefulWidget { const HomeOne({Key? key}) : super(key: key);
#override State createState() =>HomeOneState(); }
var scaffoldKey = GlobalKey();
class HomeOneState extends State { #override Widget build(BuildContext context) { var theme = Theme.of(context); return Directionality( textDirection: TextDirection.rtl, child: Scaffold( key: scaffoldKey, drawerEnableOpenDragGesture: true, // drawerScrimColor: Colors.red, appBar: AppBar( leading: IconButton( onPressed: () => scaffoldKey.currentState?.openDrawer(), icon: const Icon( Icons.add, color: Colors.red, )), ),
AppBar(
leading: IconButton(
onPressed: () {
// Code
},
icon: Icon(Icons.arrow_back),
),
),
To only change the icon color, it's easier to do by adding an iconTheme to the AppBar:
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: Drawer(),
appBar: AppBar(
title: Text("Navigation Drawer"),
iconTheme: IconThemeData(color: Colors.green),
),
);
}

Flutter - Detect Tap on the Screen that is filled with other Widgets

I am trying to detect a tap on the screen. I've tried multiple variation of using the GestureDetector but that just leads to the app detecting tap on the child element and not the screen.
Here is the code:
class QQHome extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primaryColor: Colors.blueGrey,
),
home: Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text('QuoteQuota'),
),
body: GestureDetector(
onTap: () => print('Tapped'),
child: QQBody(),
),
),
);
}
}
class QQBody extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(
child: Text(
'Hello, World!'
),
);
}
}
Output: "tapped" printed when I click "Hello, World!".
Expected Output: "tapped" printed when I click anywhere on the screen with Text in the Center.
How do I do this?
Use GestureDetector's behavior property and pass HitTestBehavior.opaque to it, which recognizes entire screen and detects the tap when you tap anywhere on the screen.
body: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => print('Tapped'),
child: QQBody(),
),
Hope this answers your question.
Like Darshan said, you can trigger Tap by wrapping the body inside a Gesture detector like this...
body: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => print('Tapped'),
child: QQBody(),
),
Also in some cases, you may need to avoid trigger clicking on other widgets while tapping anywhere in body. That can be solved by using IgnorePointer Widget
body: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
print('This will click');
},
// -------- No Widget below tree will be trigger click.
child: IgnorePointer(
ignoring: ClassLibrary.selected != null ? true : false,
child: TabBarView(
children: [
...
#Darshan is a correct answer but I want to add something to it. I wanted a trigger whenever the screen is touched and onTap only reacts to a tap (no slide). However that behaviour can be achieved bij adding onTapCancel
body: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => print('Tapped'),
onTapCancel: () => print('Touched but not tapped'),
child: QQBody(),
),
Please use below code--
class QQHome extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primaryColor: Colors.blueGrey,
),
home: Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text('QuoteQuota'),
),
body: GestureDetector(
onTap: () => print('Tapped'),
child: Container(
height : MediaQuery.of(context).size.height,
width : MediaQuery.of(context).size.width,
),
),
),
);
}
}
You can use Stack: put layers on top of each other.
The good thing about Stack is you can arrange stack order as you want.
If you want to do something as the screen is touched, and do something else if a button on the screen is touched, you can easily do it with stack by putting the button on top of GestureDetector.
The following is your example:
class QQHome extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primaryColor: Colors.blueGrey,
),
home: Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text('QuoteQuota'),
),
body: Stack(
// Bottom to Top order
children: <Widget> [
QQBody(),
GestureDetector(
onTap: () => print('Tapped'),
),
]
),
),
);
}
}
class QQBody extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(
child: Text(
'Hello, World!'
),
);
}
}

Show back button instead of drawer button - flutter

I have a Flutter project which uses material design, that as I go through routes the appbar will show the backbutton. Recently, I just implemented a drawer in my project, and the drawer icon overrides the back icon. I essentially want to undo this, showing the back button, for certain screens, and show the menu button for other screens, almost like when I define the drawer having a showIcon: false property? I understand this post is a similar question, but no code is shown for the question or the solution... My drawer looks like this:
return Scaffold(
//appbar is here
appBar: AppBar(
title: Text("Title"),
),
drawer: drawer,
body: _buildBody(),
);
And I define drawer here:
var drawer = Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: <Widget>[
//My listTiles and UserAccountsDrawerHeader are removed for simplicity
],
),
);
Thanks for any help.
Short answer:
AppBar(
leading: IconButton(
onPressed: () {}, // Handle your on tap here.
icon: Icon(Icons.arrow_back_ios),
),
)
Screenshot:
Full code:
void main() => runApp(MaterialApp(home: MyPage()));
class MyPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Column(
children: <Widget>[
ElevatedButton(
onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (_) => Page1())),
child: Text("Go to Drawer Page"),
),
ElevatedButton(
onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (_) => Page2())),
child: Text("Go to Back button Page"),
),
],
),
),
);
}
}
// This has drawer
class Page1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
drawer: Drawer(),
);
}
}
// This has back button and drawer
class Page2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () => Navigator.pop(context),
),
),
drawer: Drawer(),
);
}
}
AppBar(
leading: Builder(
builder: (BuildContext context) {
return IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () { Navigator.pop(context); },
);
},
),
)