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 set an image in the center of Column, and the Text at the bottom of the image, by wrapping Image and Text in the Column widget and placing it in the Center widget.
Unfortunately, it centers the Column and makes Image to be above the center of the screen.
How can I solve it?
My current code:
#override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(color: Theme.of(context).primaryColor),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Image.asset(ImagePaths.newLogoLogin),
Text(Strings.beALocal)
],
),
),
);
}
This can be achieved using the Expanded widget:
#override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(color: Theme.of(context).primaryColor),
child: Column(
children: [
Spacer(),
Image.asset(ImagePaths.newLogoLogin),
Expanded(
Column(
children: [ Text(Strings.beALocal) ],
mainAxisAlignment: MainAxisAlignment.start
)
)
],
),
);
}
You could use a Stack with a Positioned Text widget inside it.
Full example:
void main() => runApp(App());
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text("Test")),
body: Stack(children: [Placeholder(), Test()]),
),
);
}
}
class Test extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(
child: Stack(
alignment: AlignmentDirectional.bottomCenter,
overflow: Overflow.visible,
children: <Widget>[
Container(
width: 150,
height: 150,
color: Colors.blue,
),
Positioned(child: Text("Some text"), bottom: -25),
],
),
);
}
}
you can use widget SafaArea or calc size of the appbar and the navigation bar, when you have this result use this for remove in height size of screen after this you can add column MainAxisAlignment.center, CrossAxisAlignment.center and add other widget in Column
I want to make a full screen dialog box. Dialog box background must be opaque.
Here is an example:
How to make like this in Flutter?
You can use the Navigator to push a semi-transparent ModalRoute:
import 'package:flutter/material.dart';
class TutorialOverlay extends ModalRoute<void> {
#override
Duration get transitionDuration => Duration(milliseconds: 500);
#override
bool get opaque => false;
#override
bool get barrierDismissible => false;
#override
Color get barrierColor => Colors.black.withOpacity(0.5);
#override
String get barrierLabel => null;
#override
bool get maintainState => true;
#override
Widget buildPage(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) {
// This makes sure that text and other content follows the material style
return Material(
type: MaterialType.transparency,
// make sure that the overlay content is not cut off
child: SafeArea(
child: _buildOverlayContent(context),
),
);
}
Widget _buildOverlayContent(BuildContext context) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
'This is a nice overlay',
style: TextStyle(color: Colors.white, fontSize: 30.0),
),
RaisedButton(
onPressed: () => Navigator.pop(context),
child: Text('Dismiss'),
)
],
),
);
}
#override
Widget buildTransitions(
BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
// You can add your own animations for the overlay content
return FadeTransition(
opacity: animation,
child: ScaleTransition(
scale: animation,
child: child,
),
);
}
}
// Example application:
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Playground',
home: TestPage(),
);
}
}
class TestPage extends StatelessWidget {
void _showOverlay(BuildContext context) {
Navigator.of(context).push(TutorialOverlay());
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Test')),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Center(
child: RaisedButton(
onPressed: () => _showOverlay(context),
child: Text('Show Overlay'),
),
),
),
);
}
}
Well here is my implementation which is quite straightforward.
from first screen
Navigator.of(context).push(PageRouteBuilder(
opaque: false,
pageBuilder: (BuildContext context, _, __) =>
RedeemConfirmationScreen()));
at 2nd screen
class RedeemConfirmationScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white.withOpacity(0.85), // this is the main reason of transparency at next screen. I am ignoring rest implementation but what i have achieved is you can see.
.....
);
}
}
and here are the results.
Screenshot (Flutter's native dialog)
Call this method to show the dialog in fullscreen.
showGeneralDialog(
context: context,
barrierColor: Colors.black12.withOpacity(0.6), // Background color
barrierDismissible: false,
barrierLabel: 'Dialog',
transitionDuration: Duration(milliseconds: 400),
pageBuilder: (_, __, ___) {
return Column(
children: <Widget>[
Expanded(
flex: 5,
child: SizedBox.expand(child: FlutterLogo()),
),
Expanded(
flex: 1,
child: SizedBox.expand(
child: ElevatedButton(
onPressed: () => Navigator.pop(context),
child: Text('Dismiss'),
),
),
),
],
);
},
);
Note: This answer does not discuss making the modal transparent, but is an answer is for the stated question of "How to make a full screen dialog in flutter?". Hopefully this helps other that find this question through a search like I did, that don't need a transparent modal.
Create your modal dialog class:
class SomeDialog extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: const Text('Dialog Magic'),
),
body: new Text("It's a Dialog!"),
);
}
}
In the class that needs to open the dialog, add something like this:
void openDialog() {
Navigator.of(context).push(new MaterialPageRoute<Null>(
builder: (BuildContext context) {
return new SomeDialog();
},
fullscreenDialog: true));
}
If fullscreenDialog above is true, then the app bar will have an "x" close button. If false, it will have a "<-" back arrow.
If you need to get the result of a dialog action, add a button to your dialog that returns a value when popping the navigation stack. Something like this:
onPressed: () {
Navigator
.of(context)
.pop(new MyReturnObject("some value");
}
then in your class opening the dialog, do capture the results with something like this:
void openDialog() async {
MyReturnObject results = await Navigator.of(context).push(new MaterialPageRoute<MyReturnObject>(
builder: (BuildContext context) {
return new SomeDialog();
},
fullscreenDialog: true));
}
You can use showGeneralDialog method with any widget extends from Material like Scaffold, Card, ..etc.
For example I am going to it with Scaffold like this:
showGeneralDialog(
context: context,
pageBuilder: (context, animation, secondaryAnimation) => Scaffold(
backgroundColor: Colors.black87,
body: //Put your screen design here!
),
);
And now you can set your design as a normal screen by using Scaffold.
Note: if you want to go back you can Navigator like this:
Navigator.of(context).pop(null)
Different ways to show fullscreen dialog
A. Material Dialog
showDialog<void>(
context: context,
useSafeArea: false,
builder: (BuildContext context) {
return const SomeScaffoldView();
},
);
B. Cupertino Dialog
showCupertinoDialog<void>(
context: context,
builder: (BuildContext context) {
return const SomeScaffoldView();
},
);
C. Custom Dialog
Flutter uses this under-the-hood when displaying dialogs.
Can customize transition animation with transitionBuilder, here's a random guide with example animations.
showGeneralDialog(
context: context,
pageBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) {
return const SomeScaffoldView();
},
);
Sample Scaffold View used in above snippets.
class SomeScaffoldView extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Sample Fullscreen Dialog'),
),
body: const Center(child: Text('Dialog Body')),
);
}
}
You can use AlertDialog with zero insetPadding like below:
showDialog(
barrierDismissible: false,
context: context,
builder: (BuildContext context) {
return StatefulBuilder(builder: (context, setState) {
return AlertDialog(
insetPadding: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10.0))),
content: SizedBox.expand(
child: Column(
children: <Widget>[
SingleChildScrollView(
physics: BouncingScrollPhysics(),
child: Wrap(
children: <Widget>[
Row(
children: <Widget>[
Expanded(
flex: 1,
child: Text(
"Sample type",
style: TextStyle(fontWeight: FontWeight.w700),
),
),
Expanded(flex: 1, child: Text(""))
],
),
],
)),
],
),
));
});
},
);
RFlutter Alert is super customizable and easy-to-use alert/popup dialogs for Flutter. You may create reusable alert styles or add buttons as much as you want with ease.
Alert(context: context, title: "RFLUTTER", desc: "Flutter is awesome.").show();
RFlutter
It's easy to use! :)
you can do like this if you use popular flutter library getx
getx link
void showAlertDialogg(
String body,
String? confirmButtonText,
String? cancelButtonText,
Function(bool onConfirm, bool onCancel) clickEvent,
{barrierDismissible = false}) {
Get.dialog(
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextComponent(
body,
textAlign: TextAlign.center,
fontSize: textSmallFontSize,
fontWeight: titleFontWeight,
color: Colors.white,
),
Row(
//crossAxisAlignment : CrossAxisAlignment.center,
children: [
Expanded(
flex: 1,
child: OutlineButtonComponent(
text: cancelButtonText,
borderColor: kPrimaryColor,
onPressed: () {
Get.back();
clickEvent(false, true);
},
textColor: kPrimaryColor,
padding: EdgeInsets.fromLTRB(16, 16, 8, 16),
),
),
Expanded(
flex: 1,
child: ButtonComponent(
text: confirmButtonText,
buttonColor: kPrimaryColor,
onPressed: () {
Get.back();
clickEvent(true, false);
},
textColor: Colors.white,
padding: EdgeInsets.fromLTRB(8, 16,16, 16),
),
),
],
)
],
),
barrierColor: Colors.black12.withOpacity(0.8),
useSafeArea: true
);
}
you can pas params as you want & call this method where you need it. it supports widget so you can setup the widget as you want.
Wrap your top-level widget with Navigator widget like so:
return Navigator(
pages: [
MaterialPage(
child: MainScreen(
child: widgets...
then call showDialog and because useRootNavigator is set to true in default it will use the root navigator that we added above the MainScreen
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: (){}
),
)
],
),
)
);
}
}
I would like to display a large FlutterLogo in my app:
https://docs.flutter.io/flutter/material/FlutterLogo-class.html
In order to account for varying screen sizes I would like to make it stretch-to fill. Is that possible? Or do I need to use a MediaQuery to determine the parent's size and pass that into FlutterLogo(size:)?
My current code:
Widget build(BuildContext context) {
return new Center(
child: new FlutterLogo(size: 800.0, style: FlutterLogoStyle.horizontal, textColor: Colors.white),
);
}
You can accomplish this with a ConstrainedBox:
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
theme: new ThemeData.dark(),
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(title: new Text('Example App')),
body: new ConstrainedBox(
constraints: new BoxConstraints.expand(),
child: new FlutterLogo(
style: FlutterLogoStyle.horizontal,
textColor: Colors.white,
),
),
);
}
}
I believe I have answered a similar question
How to stretch an icon to fill parent?
https://docs.flutter.io/flutter/widgets/Expanded-class.html
https://groups.google.com/forum/#!msg/flutter-dev/lsgdU1yl7xc/0pYS2qrzBQAJ
https://docs.flutter.io/flutter/widgets/FittedBox-class.html
https://docs.flutter.io/flutter/painting/BoxFit-class.html
new Expanded(
child: new FittedBox(
fit: BoxFit.fill,
child: new FlutterLogo( style: FlutterLogoStyle.horizontal, textColor: Colors.white),
),
),
I feel kinda strange. Looking at the OP profile ID, I wonder if I answer the question correctly.
I hope this helps.
used this code to run it
import 'package:flutter/material.dart';
class MyAppBar extends StatelessWidget {
MyAppBar({this.title});
// Fields in a Widget subclass are always marked "final".
final Widget title;
#override
Widget build(BuildContext context) {
return new Container(
height: 56.0, // in logical pixels
padding: const EdgeInsets.symmetric(horizontal: 8.0),
decoration: new BoxDecoration(color: Colors.blue[500]),
// Row is a horizontal, linear layout.
child: new Row(
// <Widget> is the type of items in the list.
children: <Widget>[
new IconButton(
icon: new Icon(Icons.menu),
tooltip: 'Navigation menu',
onPressed: null, // null disables the button
),
// Expanded expands its child to fill the available space.
new Expanded(
child: title,
),
new IconButton(
icon: new Icon(Icons.search),
tooltip: 'Search',
onPressed: null,
),
],
),
);
}
}
class MyScaffold extends StatelessWidget {
#override
Widget build(BuildContext context) {
// Material is a conceptual piece of paper on which the UI appears.
return new Material(
// Column is a vertical, linear layout.
child: new Column(
children: <Widget>[
new MyAppBar(
title: new Text(
'Example title',
style: Theme.of(context).primaryTextTheme.title,
),
),
new Expanded(
child: new FittedBox(
fit: BoxFit.fill,
child: new FlutterLogo( style: FlutterLogoStyle.horizontal, textColor: Colors.white),
),
),
],
),
);
}
}
void main() {
runApp(new MaterialApp(
title: 'My app', // used by the OS task switcher
home: new MyScaffold(),
));
}
edit: I posted complete code just for darky, since I forgot to mention that expanded needs to be wrapped into row, column, or flex container to expand