Flutter flexible layout, vertical overflow - flutter

I'm starting out my first real world flutter app and so far I cannot wrap my head around how to solve this very basic use case I have.
I find various information online but none has really solved it yet. The use case seems so common to me that there has to a good standard way of doing this.
I have a column layout. To illustrate this there is a logo (blue), a form (red), a button (green) and some custom navigation at the bottom (black).
Here is the code:
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.amber,
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(32.0),
child: Column(
children: [
const Placeholder(
color: Colors.blue,
fallbackHeight: 100.0,
),
const Padding(
padding: EdgeInsets.symmetric(vertical: 32.0),
child: Placeholder(
color: Colors.red,
fallbackHeight: 300.0,
),
),
const Spacer(),
Column(
children: [
const Placeholder(
color: Colors.green,
fallbackHeight: 40.0,
),
Container(
margin: const EdgeInsets.only(top: 40.0),
child: const Placeholder(
color: Colors.black,
fallbackHeight: 40.0,
),
),
],
)
],
),
),
),
);
}
}
I'm running this on a Pixel 5 emulator. The sizes are not 100% accurate but good enough to explain the scenario I think.
This screen is part of a flow with similar screens, hence I want to layout the custom navigation and button from the bottom and up so to speak. I.e. I want the button and the nav to be at the same position when I navigate to the next screen.
The same is true with the logo and the main content from the top. Therefor I put a spacer in between that will take up the space in between.
To the issue. I have some validation within the form. When pressing the button and breaking the validation the form will display a bunch of messages below each field. I simulate this by increasing the height of the form placeholder to 500.
So now all the components won't fit in the screen anymore and I get some overflow at the bottom. Nothing strange.
When researching the most idiomatic way to solve this I find it is to add a SingleChildScrollView which make sense.
But adding a SingleChildScrollView breaks the code since I have a Spacer in there. And I understand that part. Since we now say that "take up as much space as you want, I will make it scrollable" we have infinit of space at our disposal. At the same time the Spacer basically says, "I'll push everything down as much as I can until there are no space left". So this also make sense. Let's remove the spacer.
Here is the code:
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.amber,
body: SafeArea(
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(32.0),
child: Column(
children: [
const Placeholder(
color: Colors.blue,
fallbackHeight: 100.0,
),
const Padding(
padding: EdgeInsets.symmetric(vertical: 32.0),
child: Placeholder(
color: Colors.red,
fallbackHeight: 500.0,
),
),
Column(
children: [
const Placeholder(
color: Colors.green,
fallbackHeight: 40.0,
),
Container(
margin: const EdgeInsets.only(top: 40.0),
child: const Placeholder(
color: Colors.black,
fallbackHeight: 40.0,
),
),
],
)
],
),
),
),
),
);
}
}
It works as expected. I get no error and I can scroll to see the button and navigation.
Now to the sum this up. What happens when my form goes back to it's original height, 300px? Of course since the spacer is removed I don't have that behaviour I want anymore where the button and nav is based from the bottom. They are now pulled up to the form instead. And of course, this also make sense.
I understand why all the different scenarios act as they do. But how can I then create a flexible layout where some stuff are positioned based from the bottom and some from the top but still protect against the overflow?
Do I have to start calculating when and if any overflow is going to occur and dynamically add a scroll view? I haven't gone down that route yet because I was hoping there was a more declarative, standard way of dealing with this.

This should do the trick:
return Scaffold(
appBar: AppBar(
title: Text('Demo'),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Expanded(
child: ListView(children: [
Text('Logo'),
SizedBox(height: 1000,), //large placeholder
Text('bottom')
],)),
ElevatedButton(onPressed: () {}, child: Text('Your button'))
],
),
);
So basically you start filling up your screen from the bottom with your button and then with the expanded widget you fill up the rest of the available space, wchich you fill up with a scrollable widget like ListView (which is better than SingleChildscrollView for more than one widget as it's child.
To apply this concept to you code:
return Scaffold(
backgroundColor: Colors.amber,
body: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Expanded(
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(32.0),
child: Column(
children: [
const Placeholder(
color: Colors.blue,
fallbackHeight: 100.0,
),
const Padding(
padding: EdgeInsets.symmetric(vertical: 32.0),
child: Placeholder(
color: Colors.red,
fallbackHeight: 500.0,
),
),
],
),
),
),
),
Column(
children: [
const Placeholder(
color: Colors.green,
fallbackHeight: 40.0,
),
Container(
margin: const EdgeInsets.only(top: 40.0),
child: const Placeholder(
color: Colors.black,
fallbackHeight: 40.0,
),
),
],
)
],
),
),
);

Try below code hope its help to you. add your column to SingleChildScrollView
Scaffold(
backgroundColor: Colors.amber,
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(32.0),
child: SingleChildScrollView(
child: Column(
children: [
const Placeholder(
color: Colors.blue,
fallbackHeight: 100.0,
),
const Padding(
padding: EdgeInsets.symmetric(vertical: 32.0),
child: Placeholder(
color: Colors.red,
fallbackHeight: 900.0,
),
),
SingleChildScrollView(
child: Column(
children: [
const Placeholder(
color: Colors.green,
fallbackHeight: 40.0,
),
Container(
margin: const EdgeInsets.only(top: 40.0),
child: const Placeholder(
color: Colors.black,
fallbackHeight: 40.0,
),
),
],
),
),
],
),
),
),
),
// If you want your bottom widget is constant then used bottomSheet (uncomment below bottomSheet code)
/*
bottomSheet: Container(
height: 40,
color: Colors.red,
),
*/
);

Related

Convert dart code into html/JS web format

Is there any way to convert pure Flutter/Dart code into Html and JS?
My aim is to a basic webpage but from a widget I have created in Flutter. I have read that Dart is compiled behind with JS .
Just wondering if I would be able to see the code behind a page created in flutter, like this one:
import 'package:flutter/material.dart';
class CardWebQR extends class name extends StatelessWidget {
const name({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("QR Code"),
),
body: Column(
children: <Widget>[
Align(
alignment: Alignment.center,
child: SizedBox(
height: MediaQuery.of(context).size.height * 0.2,
width: MediaQuery.of(context).size.width * 0.76,
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20)),
elevation: 10,
margin: const EdgeInsets.all(20),
child: Column(
children: [
Text('hello'),
const SizedBox(height: 10),
Text('bye'),
const SizedBox(height: 10),
CircleAvatar(
radius: 30,
backgroundImage: NetworkImage(url),
),
],
)),
),
),
],
),
);
}
}

How to put two containers on the same screen without page scrolling?

I have a search page. I display 2 containers with information on the search page. But I ran into a problem, my bottom station container goes off the screen and I need to scroll the page to see the information. How can I put 2 containers on the screen and not have to scroll the page so that 2 containers fit on the same screen?
1
Widget _addresses(Size size, StationCubit stationCubit) => ConstrainedBox(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height / 2,
),
child: SizedBox(
width: size.width,
child: ClipRRect(
borderRadius: BorderRadius.circular(24),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 8.0, sigmaY: 8.0),
child: Container(
padding: const EdgeInsets.only(left: 20, top: 17),
decoration: BoxDecoration(
color: constants.Colors.greyXDark.withOpacity(0.8),
borderRadius: BorderRadius.circular(24),
),
child: SingleChildScrollView(
controller: _addressesController,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Addresses',
style: constants.Styles.smallBookTextStyleWhite,
),
const SizedBox(height: 25),
2
Widget _station(Size size, StationCubit stationCubit) => ConstrainedBox(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height / 2,
),
child: SizedBox(
width: size.width,
child: ClipRRect(
borderRadius: BorderRadius.circular(24),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 8.0, sigmaY: 8.0),
child: Container(
padding: const EdgeInsets.only(left: 20, top: 17),
decoration: BoxDecoration(
color: constants.Colors.greyXDark.withOpacity(0.8),
borderRadius: BorderRadius.circular(24),
),
child: SingleChildScrollView(
controller: _stationController,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Station',
style: constants.Styles.smallBookTextStyleWhite,
),
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Column(
children: [
Expanded(
child: Container(
color: Colors.deepPurple,
child: ListView.builder(itemBuilder: (c, i) {
return Text("Test $i");
})),
),
Expanded(
child: Container(
color: Colors.deepOrange,
child: ListView.builder(itemBuilder: (c, i) {
return Text("Test $i");
})),
),
],
));
}
}
Try placing both containers in column and wrap both container with flexible/expanded to expand containers in full screen.
Example code:
column(
children: [
Expanded(
child: Container(child: Text("Container 1")
),
Expanded(
child: Container(child: Text("Container 2")
)
]
)
Use 2 Expanded container in single column
column( children: [ Expanded( child: Container(child: Text("Container 1") ), Expanded( child: Container(child: Text("Container 2") ) ] ).
Abdul Rahman Panhyar your answer is right but Max need to show data came from any API so there is a chance of bulk data and just wrapping the container with expanded will disrupt the UI. so what is suggest you can divide your screen in two parts then in each part you can use Listview builder so it will be inner scrollable.

Flutter - Place Text Box without using Scaffold

I am new to flutter and having come from an android dev background, having trouble placing my widgets appropriately.
I intent to place a bunch of texts/buttons one on top of another. Below is how i want to set it in my final screen. Additionally, I want to set the entire page's background color as say Red.
Given i dont need the title bar etc, I thought I'd start by not using the Scaffold at all. So here's my code as of now. I am first trying just to the 'MYAPP' text placed correctly, and not getting it to work.
void main() {
runApp(const MaterialApp(
home: MyPage(),
));
}
And in a separate file, I define MyPage
class MyPage extends StatelessWidget {
const MyPage ({Key? key}) : super(key: key);
static const TextStyle myStyle = TextStyle(
color: AppColors.complementColor,
fontSize: 80,
);
#override
Widget build(BuildContext context) {
return Container( // -> using Container, so I can set the background color as Red.
color: Colors.Red,
child: Column(
children: [
Container( // -> using container again so I can add axis etc, and margins
child: const Text(
'MYAPP',
style: myStyle,
textDirection: TextDirection.ltr,
))
],
),
);
}
}
What happens with this is that I get my MYAPP, in the top middle of screen. What i want is for it to be in the left side (and with margin from start).
I thought of adding, mainAxisAlignment: MainAxisAlignment.center and crossAxisAlignment: CrossAxisAlignment.start, but when I add crossAxisAlignment, the text box isnt getting shown at all, and even the main background color which was originally red, changes to black.
So I've got a few questions.
How do I get my text 'MYAPP' to be placed properly using the widgets I have, and why does adding crossAxisAlignment as start removes the entire widget.
If I need to add margin/padding etc, does it make sense to do the way I am doing (i.e. have a container around column)
When I set the color of root container to Red, even then the topmost bar of my android phone (the one that is used to scroll down any notifications) is not really of red color, but an offset of red. Why is that?
Is it better if I used scaffold without appbar for this?
If i dont give any axis alignment for my column, why is the text placed in the top center and why not top left? Additionally, why does the text have some padding already from the surrounding container?
Please use this code
void main() {
runApp(const MaterialApp(
debugShowCheckedModeBanner: false,
home: MyPage(),
));
}
class MyPage extends StatelessWidget {
const MyPage({Key? key}) : super(key: key);
static const TextStyle myStyle = TextStyle(
color: Colors.black,
fontSize: 80,
);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: Colors.red,
width: double.infinity,
height: double.infinity,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Padding(
padding: EdgeInsets.only(left: 20.0),
child: Text(
'MYAPP',
style: myStyle,
textDirection: TextDirection.ltr,
),
),
const Padding(
padding: EdgeInsets.only(left: 25),
child: Text(
'This is my app',
),
),
const SizedBox(
height: 30,
),
Padding(
padding: const EdgeInsets.only(left: 25.0, right: 25),
child: Row(
children: [
Expanded(
child: SizedBox(
height: 50,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
primary: Colors.transparent,
elevation: 0,
// side: const BorderSide(width: 2.0, color: Colors.black,),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18.0),
side: const BorderSide(
color: Colors.black, width: 3))),
child: const Text(
'This is my button',
style: TextStyle(color: Colors.black),
),
onPressed: () {},
),
),
),
],
),
),
],
),
),
);
}
}
Output :

Place a CircularProgressIndicator inside RaisedButton mantaining size

I want to place a CircularProgressIndicator inside a RaisedButton, but when I do it, the CircularProgressIndicator doesn't fit the button and sticks out of it.
I want to shrink the CircularProgressIndicator, so that it fits inside the RaisedButton, I know I can do it using a SizedBox but I want it to be automatic, not that I give it the size that I want. I tried FittedBox but all its options make no difference at all.
this is my button's code:
RaisedButton(
onPressed: () => print('hi'),
shape: StadiumBorder(),
color: Theme.of(context).primaryColor,
child: Row(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(right: 12, top: 6, bottom: 6),
child: CircularProgressIndicator(
backgroundColor: Colors.white,
strokeWidth: 2,
),
),
Text('Loading'),
],
),
),
And this is how it looks like:
When I Add Padding it grows the button:
Are there any ways to achieve this automatically?
EDIT:
Just to make it clear, the end effect that I want is this:
Before and after the user presses the button:
I want the height to stay the same, automatically, without magic values inside a SizedBox.
This is totally possible. You wrap the CircularProgressIndicator inside a SizedBox to constrain it to that size. Also using MainAxisSize.Min in the Row prevents the button from trying to expand to infinite size.
ElevatedButton(
onPressed: (){},
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.blue)),
child: isLoading
? Padding(
padding: const EdgeInsets.all(5.0),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text("Loading"),
SizedBox(
width: 5,
),
SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation(Colors.white),
backgroundColor: Colors.blue,
strokeWidth: 3,
),
)
],
),
)
: Text("Not Loading"))
This gives you the following
Looking at the CircularProgressIndicator source code, I can find there's a hardcoded minimum size of 36.0 height/width, so there's no way of getting smaller than that automatically.
I re-created your case and was able to see the CircularProgressIndicator properly inside the RaisedButton maintaining it's size.
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Progress Button',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Progress Button'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
onPressed: () => print('hi'),
shape: StadiumBorder(),
color: Theme.of(context).primaryColor,
child: Row(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(right: 12, top: 6, bottom: 6),
child: CircularProgressIndicator(
backgroundColor: Colors.white,
strokeWidth: 2,
),
),
Text('Loading'),
],
),
),
],
),
),
);
}
}
Output (both platforms):
you can use mainAxisAlignment: MainAxisAlignment.spaceBetween to make space between your widgets in Row.
You can wrap your Row widget to a Padding widget to make padding for whole content in your button as well.
Padding(
padding: EdgeInsets.all(10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
CircularProgressIndicator(
backgroundColor: Colors.white,
strokeWidth: 2,
),
Text('Loading'),
],
),
)

Overlay a card partially on an image

I am trying to partially overlay a Card on an image using Stack, just like this
So this is what I have tried
#override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
body: Container(
margin: const EdgeInsets.all(10.0),
child: Stack(
alignment: AlignmentDirectional.bottomCenter,
children: <Widget>[
ClipRRect(
child: Image.asset("assets/images/placeholder.jpg"),
borderRadius: new BorderRadius.circular(8.0),
), // image
new Positioned(
child: Card(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(
title: Text('1625 Main Street',
style: TextStyle(fontWeight: FontWeight.w500)),
subtitle: Text('My City, CA 99984'),
leading: Icon(
Icons.restaurant_menu,
color: Colors.blue[500],
),
),
],
),
) //card
)
],
)),
);
}
However it displays the card at the bottom of the image but trying to overlap it partially over the image with the help of Stack's bottom, top arguments makes the card not display all together. How can I go about it?
I think the issue is that when you're making your stack, you're not allowing it to size itself properly. A stack sizes itself to any children which are not positioned - in your case, the ClipRRect. The ClipRRect looks like it is sized to its child image, which has a defined height. So the stack will also be this size I believe (you can turn on debug painting to see).
It looks like you want the image and white to be the background for your entire page, which means that you should be letting the stack expand to the size of the page. Wrapping your image in an alignment should do this.
The next part is that you've made your card positioned, but not defined any parameters. You want to define at the least the top, but probably also the left and right.
This works for me (although I'm not using all the same widgets, but it should apply anyways):
import 'package:flutter/material.dart';
main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text("Card over stack"),
),
body: Stack(
children: <Widget>[
Align(
alignment: Alignment.topCenter,
child: Container(
decoration:
BoxDecoration(borderRadius: BorderRadius.all(Radius.circular(10.0)), color: Colors.lightBlueAccent),
height: 100,
),
),
Positioned(
top: 60,
right: 10,
left: 10,
child: Card(
child: ListTile(
title: Text('1625 Main Street', style: TextStyle(fontWeight: FontWeight.w500)),
subtitle: Text('My City, CA 99984'),
leading: Icon(
Icons.restaurant_menu,
color: Colors.blue[500],
),
),
),
)
],
),
),
);
}
}