Flutter: Floating action button fixed location - flutter

I have the following code:
return Scaffold(
appBar: AppBar(
title: Text('Sample Code'),
),
body: ListView(
padding: const EdgeInsets.all(20.0),
children: <Widget>[
TextField(
decoration: InputDecoration(labelText: "Text"),
),
TextField(
decoration: InputDecoration(labelText: "Text"),
),
TextField(
decoration: InputDecoration(labelText: "Text"),
),
],
),
bottomNavigationBar: BottomAppBar(
child: Container(
height: 50.0,
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {},
child: Icon(Icons.add),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
);
Whenever the keyboard shows up to enter text into a TextField the FloatingActionButton moves up to the top of the keyboard which will look like this:
What I want is that the button stays in the bottom navigation bar and does not move when the keyboard shows up. I added resizeToAvoidBottomPadding: false, to the Scaffold, which prevents the button from moving but also stops my ListView from moving to stay visible when the keyboard shows up.

Wrapping the floating action button inside a positioned widget did not work for me. However, in combination to adding in your Scaffold the property
resizeToAvoidBottomPadding: false,
You can also use the SingleChildScrollView widget with padding using the MediaQuery.of method...
return Scaffold(
resizeToAvoidBottomInset: false,
backgroundColor: Colors.lightBlueAccent,
floatingActionButton: FloatingActionButton(
backgroundColor: Colors.lightBlueAccent,
child: Icon(Icons.add),
onPressed: () {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (context) => SingleChildScrollView(
child: Container(
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
child: AddTaskScreen())),
backgroundColor: Colors.transparent);
},
),

Maybe there's a more elegant way of doing this? but this will solve your immediate problem.
There's a nice little plugin to detect the keyboard visibility here.
All you need to do then is to listen to the keyboard visibility state and hide the FAB when the keyboard is visible.
Sample:
import 'package:flutter/material.dart';
import 'package:keyboard_visibility/keyboard_visibility.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'SO Demo',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool keyboardOpen = false;
#override
void initState() {
super.initState();
KeyboardVisibilityNotification().addNewListener(
onChange: (bool visible) {
setState(() => keyboardOpen = visible);
},
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Sample Code'),
),
body: ListView(
padding: const EdgeInsets.all(20.0),
children: <Widget>[
TextField(decoration: InputDecoration(labelText: "Text")),
TextField(decoration: InputDecoration(labelText: "Text")),
TextField(decoration: InputDecoration(labelText: "Text")),
],
),
bottomNavigationBar: BottomAppBar(
child: Container(height: 50.0),
),
floatingActionButton: keyboardOpen
? SizedBox()
: FloatingActionButton(
onPressed: () {},
child: Icon(Icons.add),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
);
}
}

Just use the property inside Scaffold
resizeToAvoidBottomInset: false
It will solve the problem immediately..

Related

How to scroll to active text field in bottom sheet?

Here is self contained code that demo's the issue I am seeing.
When i click to view bottom sheet, it shows correctly:
But when I click into a textfield, it doesn't scroll the sheet up.
How do I get the screen to ensure field is visible and scrolls to the active text field? From what I can tell, it seems like it's related to using the DefaultTabController. On other screens, I don't have the issue...but still need to find a solution to this specific use-case.
import 'package:flutter/material.dart';
class TestScreen extends StatefulWidget {
const TestScreen({Key? key}) : super(key: key);
#override
_TestScreenState createState() => _TestScreenState();
}
class _TestScreenState extends State<TestScreen> {
void showBottom() {
showModalBottomSheet(
context: context,
// isScrollControlled: true,
builder: (_) {
return Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom),
child: Column(
children: [
Text('testing bottom sheet.'),
TextFormField(),
TextFormField(),
TextFormField(),
],
),
);
});
}
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
title: Text('testing'),
bottom: TabBar(
indicatorColor: Colors.purple,
tabs: [
Tab(
child: Text(
'Zones',
style: TextStyle(color: Colors.black),
),
),
Text('My Profile', style: TextStyle(color: Colors.black))
],
),
),
body: TabBarView(
children: [
Container(
child: Text('Tab 1'),
),
Container(
child: Column(
children: [
Text('This is tab 2'),
TextButton(onPressed: (){
showBottom();
},child: Text('Show bottom'),)
],
),
)
],
),
));
}
}
Uncomment the line : isScrollControlled: true, this will allow the bottom sheet to take the space.

Flutter: How to implement floating SearchBar (Custom AppBar) with Drawer like Google Apps

Can somebody please tell me how can I integrate the menu drawer inside the Row widget instead of in a Scaffold widget? Something like Gmail's app (search with drawer icon).
It's very simple.
Screenshot of the output
Steps:
Step 1:
Define the scaffold with a custom Appbar widget
return Scaffold(
appBar: FloatAppBar(),
body: Center(
child: Text('Body'),
),
drawer: Drawer(
child: SafeArea(
right: false,
child: Center(
child: Text('Drawer content'),
),
),
),
);
Step 2:
Implement the PreferredSizeWidget to create a custom AppBar
class FloatAppBar extends StatelessWidget with PreferredSizeWidget {
step 3:
Use Scaffold.of(context).openDrawer(); to open the drawer when required.
Here is the complete snippet.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Playground',
home: TestPage(),
);
}
}
class TestPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: FloatAppBar(),
body: Center(
child: Text('Body'),
),
drawer: Drawer(
child: SafeArea(
right: false,
child: Center(
child: Text('Drawer content'),
),
),
),
);
}
}
class FloatAppBar extends StatelessWidget with PreferredSizeWidget {
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Positioned(
top: 10,
right: 15,
left: 15,
child: Container(
color: Colors.white,
child: Row(
children: <Widget>[
Material(
type: MaterialType.transparency,
child: IconButton(
splashColor: Colors.grey,
icon: Icon(Icons.menu),
onPressed: () {
Scaffold.of(context).openDrawer();
},
),
),
Expanded(
child: TextField(
cursorColor: Colors.black,
keyboardType: TextInputType.text,
textInputAction: TextInputAction.go,
decoration: InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(horizontal: 15),
hintText: "Search..."),
),
),
],
),
),
),
],
);
}
#override
Size get preferredSize => Size.fromHeight(kToolbarHeight);
}
See the live demo here.
the AppBar widget alredy has mechanisms for that,
AppBar(
drawaer: YourDrawer(),
actions: [
IconButton(
icon: Icon(Icons.search),
onPressed: (){}
)
]
);
it will create the Gmail appbar you want

Reorderable List glitches keyboard when not on home screen

I am working on a project with textfields that are members of a reorderable list. My problem is when the list is accessed from a Navigator push to a new screen the text fields within the ReorderableListView on the new screen glitches causing the keyboard to pop up then quickly disappear. The code is identical for both lists, does anyone know what may be causing this bug? I created a sample main.dart to demonstrate the effect:
import 'package:flutter/material.dart';
void main() => runApp(App());
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Reorderable List bug',
home: MainPage(),
);
}
}
class MainPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Main Page'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(
child: ReorderableListView(
onReorder: (old_index, new_index) {},
children: <Widget>[
textCard(),
textCard2()
],
),
),
Text('Keyboard behaves as expected here but will glitch on following page'),
RaisedButton(
textColor: Colors.white,
color: Colors.blue,
child: Text('Go to SubPage'),
onPressed: () {
navigateToSubPage(context);
},
)
],
),
),
);
}
Future navigateToSubPage(context) async {
Navigator.push(context, MaterialPageRoute(builder: (context) => SubPage()));
}
}
class SubPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: ReorderableListView(
onReorder: (old_index, new_index) {},
children: <Widget>[
textCard(),
textCard2(),
RaisedButton(
key: ValueKey('3'),
textColor: Colors.white,
color: Colors.redAccent,
child: Text('Back to Main Page'),
onPressed: () {
Navigator.pop(context);
},
)
],
),
);
}
}
Widget textCard() {
return Card(
key: ValueKey('1'),
child: Padding(
padding: EdgeInsets.all(15.0),
child: Row(
children: <Widget>[
Padding(
padding: EdgeInsets.only(right: 15.0),
child: Text(
'1.',
)),
Expanded(
child: TextFormField(
decoration: InputDecoration(labelText: 'Enter text'),
)
)
],
)));
}
Widget textCard2() {
return Card(
key: ValueKey('2'),
child: TextFormField(
decoration: InputDecoration(
labelText: 'Enter text'
),
),
);
}

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.

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

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,
),