Related
I'm building a Flutter app, mainly for iOS.
One of my views has a text field, and iOS keyboard appears when you tap on it. The problem is - layout does not change smoothly like it does in native iOS apps. Instead, it instantly jumps to the final available screen height even before keyboard opening animation finishes.
I tried wrapping my SafeArea element in AnimatedSize and AnimatedContainer - it didn't help.
My layout code:
SafeArea(child:
Column(children:[
TextField(...)
])
)
How can I make the layout resize smoothly when the keyboard appears?
Expected:
Actual
I use something like that:
AnimatedPadding(
padding: MediaQuery.of(context).viewInsets,
duration: const Duration(milliseconds: 100),
curve: Curves.decelerate,
child: ....
)
This animates the padding based on the viewInsets(software keyboard height).
The desired output can be achieved using AnimatedPadding Widget, though this is not perfect, but better than nothing :d
Open issue as of 15/03/21, for reference
import 'dart:math';
import 'package:flutter/material.dart';
final Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return SafeArea(
bottom: false,
child: Scaffold(
// !!! Important part > to disable default scaffold insets
resizeToAvoidBottomInset: false,
appBar: AppBar(
title: Text("Appbar Title"),
),
body: Stack(
children: [
Scrollbar(
child: ListView.builder(
padding: EdgeInsets.all(0.0),
itemCount: 30,
itemBuilder: (context, i) {
return Container(
height: 100,
width: double.infinity,
color: Colors
.primaries[Random().nextInt(Colors.primaries.length)],
);
},
),
),
Align(
alignment: Alignment.bottomLeft,
child: AnimatedPadding(
padding: MediaQuery.of(context).viewInsets,
// You can change the duration and curve as per your requirement:
duration: const Duration(milliseconds: 200),
curve: Curves.decelerate,
child: InputField()),
)
],
)),
);
}
}
class InputField extends StatefulWidget {
InputField({Key key}) : super(key: key);
#override
_InputFieldState createState() => _InputFieldState();
}
class _InputFieldState extends State<InputField> {
#override
Widget build(BuildContext context) {
return Container(
color: Colors.grey[100],
padding: const EdgeInsets.symmetric(vertical: 6),
child: Row(
children: [
SizedBox(
width: 60,
child: Icon(Icons.add_a_photo),
),
Flexible(
child: TextField(
style: Theme.of(context).textTheme.bodyText1,
decoration: InputDecoration(
border: InputBorder.none,
hintText: 'Enter text...',
),
),
),
SizedBox(
width: 60,
child: Icon(Icons.send),
),
],
),
);
}
}
Output ->
You can use this package keyboard_visibility and listen to keyboard visibility. Then you can give your logic to implement your feature like you can shorten the home container height. That's not perfect . But I think it's the only way right now.
You need to use keyboard_visibility package and use it to trigger your AnimatedContainer or AnimatedPadding
bool _isKeyboardActive = false;
#override
void initState() {
super.initState();
//add keyboard visibility Listener
KeyboardVisibility.onChange.listen((event) {
setState(() {
_isKeyboardActive = event;
});
});
}
Widget build(BuildContext context){
return AnimatedContainer(
width: _isKeyboardActive ? 200 : MediaQuery.of(context).size.width,
height: 60,
color: Colors.red,
duration: Duration(milliseconds: 600)
)
}
use this as a basis.
You should try setting resizeToAvoidBottomPadding: false like so:
return Scaffold(
key: _scaffoldKey,
resizeToAvoidBottomPadding: false,
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.
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.
When i select a Textfield, the keyboard is going to be shown but the keyboard hide my selected TextField. Does someone have a solution?
Scaffold(
resizeToAvoidBottomInset: true,
body: SingleChildScrollView(
child: Container(
child: Column(
children: <Widget>[
TextFormField(
decoration: InputDecoration(
labelText: 'Enter Text',
),
),TextFormField(
decoration: InputDecoration(
labelText: 'Enter Text',
),
),TextFormField(
decoration: InputDecoration(
labelText: 'Enter Text',
),
),TextFormField(
decoration: InputDecoration(
labelText: 'Enter Text',
),
),TextFormField(
decoration: InputDecoration(
labelText: 'Enter Text',
),
),TextFormField(
decoration: InputDecoration(
labelText: 'Enter Text',
),
),TextFormField(
decoration: InputDecoration(
labelText: 'Enter Text',
),
),
TextFormField(
decoration: InputDecoration(
labelText: 'Enter Text',
),
)
],
),
),
)
);
// resizeToAvoidBottomPadding: false isDeprecated
use resizeToAvoidBottomInset: true.
Compose an animation and move your TextField container up when a TextField gets focus.
For learning about composing animations refer to:
Composing Animations and Chaining Animations in Dart's Flutter Framework
Use Flutter's FocusNode to detect the focus on a TextField
Edit:
Here I've written an example that does exactly what you want:
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Animation Demo',
theme: new ThemeData(
primaryColor: new Color(0xFFFF0000),
),
home: new FormDemo(),
);
}
}
class FormDemo extends StatefulWidget {
#override
_FormDemoState createState() => _FormDemoState();
}
class _FormDemoState extends State<FormDemo> with SingleTickerProviderStateMixin {
AnimationController _controller;
Animation _animation;
FocusNode _focusNode = FocusNode();
#override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: Duration(milliseconds: 300));
_animation = Tween(begin: 300.0, end: 50.0).animate(_controller)
..addListener(() {
setState(() {});
});
_focusNode.addListener(() {
if (_focusNode.hasFocus) {
_controller.forward();
} else {
_controller.reverse();
}
});
}
#override
void dispose() {
_controller.dispose();
_focusNode.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomPadding: false, // this avoids the overflow error
appBar: AppBar(
title: Text('TextField Animation Demo'),
),
body: new InkWell( // to dismiss the keyboard when the user tabs out of the TextField
splashColor: Colors.transparent,
onTap: () {
FocusScope.of(context).requestFocus(FocusNode());
},
child: Container(
padding: const EdgeInsets.all(20.0),
child: Column(
children: <Widget>[
SizedBox(height: _animation.value),
TextFormField(
decoration: InputDecoration(
labelText: 'I move!',
),
focusNode: _focusNode,
)
],
),
),
),
);
}
}
Just cut and paste your body code in this -
SingleChildScrollView(
child: Stack(
children: <Widget>[
// your body code
],
),
),
A pretty short way to realize this is by simply using a MediaQuery for getting the bottom view insets. This would look as follows:
...
return Column(
children: <Widget>[
TextField(
decoration: InputDecoration.collapsed(hintText: "Start typing ..."),
controller: _chatController,
),
SizedBox(
height: MediaQuery.of(context).viewInsets.bottom,
),
],
);
...
Hope it helps!
In my case I had to combine answer given by #Javid Noutash which uses AnimationController along with scrollPadding property of TextFormField.
code:
Add this line in build method
double bottomInsets = MediaQuery.of(context).viewInsets.bottom;
Add scrollPadding property
return ListView(
children:[
...widgets,
Container(
margin:EdgeInsets.only(
top:1.0,
left:1.0,
right:1.0,
bottom:_focusNode.hasFocus && bottomInsets != 0?
_animation.value : 1.0),
child:TextFormField(
decoration: InputDecoration(
labelText: 'I move!',
),
focusNode: _focusNode,
scrollPadding: EdgeInsets.only(bottom:bottomInsets + 40.0),
),
),
]
);
Note: Combine this code with #Javid Noutash's code
I had the same problem where the parent widget is Material and the other widgets are inside a ListView. The problem was fixed when I changed the parent widget to Scaffold without any extra code and the TextField, TextFormField in my case, is being showed above the Keyboard automatically. So, if you encountered this problem just make sure to have Scaffold as the main widget.
The most simple way is to just wrap it with
SingleChildScrollView( ... )
When the textfield is on the page bottom and the keyboard appears, the textfield is automatically scrolled up. Then the text may be entered right above the keyboard.
My way here
Scaffold(
resizeToAvoidBottomInset: false,
resizeToAvoidBottomPadding: false,
body: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('images/Bg img.png'), fit: BoxFit.fill)),
child: Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom),
child: CustomScrollView(
slivers: [
SliverFillRemaining(
hasScrollBody: false,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
.............
This template has some advantages:
Move content up when the keyboard appears
Column using spaceBetween inside a scroll view
Background is sticked phone scren and never change event the keyboard ups
Wrap your widget into Padding and set padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
<activity
android:name="..ActivityName"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize"/>
only for android
if you use FlutterFragment add configChanges and windowSoftInputMode for the Activity.
another way
add your TextField to a ListView
ListView(
children: <Widget>[
TextField(),
TextField(),
]
)
The above does not work if you have a CustomScrollview in a NestedScrollView.
First, you need to give the TextField a focusNode.
TextField(focusNode:_focusNode(),
...);
Use NestedScrollViewState to get access to the innerScrollController of the NestedScrollView. You can view the example here on how to get the innerScrollController. Declare a globalKey and assign it to NestedScrollView.
body: NestedScrollView(
key: globalKey,
...)
Setup the focusNode Listener to listen for when the textfield has been activated and animate the innerScrollController accordingly.
void initState() {
super.initState();
_focusNode.addListener(() {
if (_focusNode.hasFocus) {
double innerOffSet = globalKey.currentState.innerController.offset;
if(innerOffSet < 100)
globalKey.currentState.innerController.jumpTo(innerOffSet+100);
}
});
}
var _contentController;
void _settingModalBottomSheet(BuildContext context, String initialText) {
_contentController = new TextEditingController(text: initialText);
showModalBottomSheet(
context: context,
isDismissible: true,
builder: (BuildContext bc) {
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
height: 40,
margin: EdgeInsets.only(left: 4, right: 4, bottom: 8),
decoration: BoxDecoration(
color: AppColor.bgTextFieldComment,
borderRadius: BorderRadius.circular(16),
),
child: Row(
children: <Widget>[
Expanded(
child: Padding(
padding: EdgeInsets.only(left: 24),
child: TextField(
focusNode: _contentFocusNode,
autofocus: true,
controller: _contentController,
decoration: InputDecoration(
hintText: 'Enter Content',
border: InputBorder.none,
fillColor: AppColor.bgTextFieldComment,
),
keyboardType: TextInputType.multiline,
maxLines: null,
style: TextStyle(
color: Colors.black87,
fontSize: 16,
fontStyle: FontStyle.normal,
),
)),
),
InkWell(
child: Padding(
padding: EdgeInsets.only(left: 4, right: 4),
child: Icon(
Icons.send,
color: Colors.blue,
),
),
onTap: () {
// do ON TAP
},
),
],
),
),
SizedBox(
height: MediaQuery.of(bc).viewInsets.bottom,
),
],
);
},).then((value) {
print('Exit Modal');
});
print('request focus');
_contentFocusNode.requestFocus();
}
Instead of TextField, use TextFormField and wrap the widget with a list of TextFormField to Form:
Form(
child: Column(
children: <Widget> [
TextFormField(),
TextFormField(),
...
TextFormField(),
]
)
)
you can easily try to use Flexible widget just wrap your widget with it
Flexible(
child: Image(
image :
AssetImage('assets/logo.png'),
),
),
I had a very complex widget with Stack, Column and Single ChildChildScrollView, and I fixed it by adding a padding to the SCSV like this:
child: Stack(
children: [
Padding(
padding: const EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
child: SingleChildScrollView(
child: Column(
children: [... a lot of children here, one of them is a TextField],
),
),
),
// a widget at the bottom of the stack that shows despite the size of the scrollable column
],
),
It's very easy in my case check out code
Column(
children: [
Expanded(
child:// Top View,
),
postSend // edittext. and button
],
)
I created a screen that works well with the columns, but I needed to scroll because of the keyboard.
When you insert the SingleChildScrollView or the ListView attribute the MainAxisAlignment.spaceBetween, it no longer works.
Was there any solution for that?
Gif without the SingleChildScrollView the screen does not roll and the FloatingActionButton is at the bottom of the screen
Gif with SingleChildScrollView the screen roll and he FloatingActionButton is not in bottom of the screen
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(backgroundColor: new Color(0xFF26C6DA)),
body: new SingleChildScrollView(
child: new Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
new Container(
child: new Column(
children: <Widget>[
new TextField(
decoration: const InputDecoration(
labelText: "Description",
),
style: Theme.of(context).textTheme.title,
),
new TextField(
decoration: const InputDecoration(
labelText: "Description",
),
style: Theme.of(context).textTheme.title,
),
new TextField(
decoration: const InputDecoration(
labelText: "Description",
),
style: Theme.of(context).textTheme.title,
),
],
)
),
new Container(
margin: new EdgeInsets.only(bottom: 16.0),
child: new FloatingActionButton(
backgroundColor: new Color(0xFFE57373),
child: new Icon(Icons.check),
onPressed: (){}
),
)
],
),
)
);
}
}
I would recommend against using FloatingActionButton in the way you are doing here. FloatingActionButton should be used for actions that always need to always be on screen at all times. See the Material guidelines on button usage for suggestions on other button types that can be scrolled, like RaisedButton and FlatButton. You could use a RaisedButton here, but I think it would be better to make your screen a dialog and put the save action in the AppBar as I suggested in your other question.
If you do decide to use a RaisedButton or FlatButton, keep in mind that scrollables don't normally change their item spacing based on the size of their container. Instead of using MainAxisAlignment.spaceBetween, you could put some Padding around your RaisedButton to separate it from the TextField elements. This will ensure that they are spaced the same distance apart regardless of rotation, screen size, and regardless of whether the keyboard is up.
Follow the code below to register.
MainAxisAlignment.spaceBetween has been replaced with dynamic padding, depending on screen size.
import 'package:flutter/material.dart';
import 'dart:ui' as ui;
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
final ui.Size logicalSize = MediaQuery.of(context).size;
final double _height = logicalSize.height;
return new Scaffold(
appBar: new AppBar(backgroundColor: new Color(0xFF26C6DA)),
body: new SingleChildScrollView(
child: new Column(
//mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
new Container(
height: 300.0,
child: new Column(
children: <Widget>[
new TextField(
decoration: const InputDecoration(
labelText: "Description",
),
style: Theme.of(context).textTheme.title,
),
new TextField(
decoration: const InputDecoration(
labelText: "Description",
),
style: Theme.of(context).textTheme.title,
),
new TextField(
decoration: const InputDecoration(
labelText: "Description",
),
style: Theme.of(context).textTheme.title,
),
],
)
),
new Container(
padding: new EdgeInsets.only(top: (_height - 450.0)),
margin: new EdgeInsets.only(bottom: 16.0),
child: new FloatingActionButton(
backgroundColor: new Color(0xFFE57373),
child: new Icon(Icons.check),
onPressed: (){}
),
)
],
),
)
);
}
}