Flutter - Place Text Box without using Scaffold - flutter

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 :

Related

How to align the child of a column on the bottom while keeping other children's position at the center in Flutter?

I want to align the text "2022" on the bottom of the screen while keeping the image and its neighbor text at the center of the screen.
class SplashScreen extends StatelessWidget {
const SplashScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Globalcolors.mainColor,
body: Container(
padding: const EdgeInsets.all(10),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Center(
child: Image.asset(
'assets/images/splash_logo.png',
color: Colors.white,
),
),
const Padding(
padding: EdgeInsets.all(50),
child: Text(
'Sample Text',
style: TextStyle(
color: Colors.white,
fontSize: 36,
fontFamily: 'MouseMemoirs'),
),
),
const Text(
'2022',
style: TextStyle(color: Colors.white, fontSize: 12),
),
],
),
),
);
}
}
I tried to use this link to solve the problem. I used Expanded:
const Expanded(
child: Align(
alignment: FractionalOffset.bottomCenter,
child: Text(
'2022',
style: TextStyle(color: Colors.white, fontSize: 12),
),
),
)
But, The result is not good:
You can user Stack and column widgets like
Stack(
children: [
Column(), // contains the image and title
Align(
alignment: Alignment.bottomCenter,
child: Text("2022"),
),
]
)
You can also use Positioned widget to align the text 2022
you can use the bottom method that comes with the scaffold
Scaffold(
bottom : Text("2020")
)
this will make the text always on the bottom of the screen

Flutter flexible layout, vertical overflow

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,
),
*/
);

widget hiding under background when use image in background, no problem with color

I am getting trouble using flutter widget when set background image.
if I use color as background, then widget showing correctly, otherwise it only showing text of that widget, other widget color goes under background image.
class SplashView extends StatelessWidget {
const SplashView({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Positioned.fill(
child: Image(
image: AssetImage('assets/images/decent.jpg'),
fit : BoxFit.fill,
),
// child: Container( color: Colors.amber)
),
Container(
margin: EdgeInsets.only(top: 20),
padding: EdgeInsets.symmetric(horizontal: 60, vertical: 12),
child: Text(title,
style: TextStyle(
fontSize: 18,
color: Colors.white
),
),
decoration: BoxDecoration(
color: primaryColor,
borderRadius: BorderRadius.circular(5),
),
);
],
);
}
}
Widget showing correctly here (Button showing background here)
Here it only shows text of button widget, button background not showing.
NOTE* I am facing this problem on WEB.
I run your code it is working without any Issue
Just check with another Image asset, also uninstall the app and run the project again
You should use like this
class SplashView extends StatelessWidget {
const SplashView({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Image.asset(
"assets/login_bg.png",
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
fit: BoxFit.cover,
),
),
Container(
margin: EdgeInsets.only(top: 20),
padding: EdgeInsets.symmetric(horizontal: 60, vertical: 12),
child: Text(title,
style: TextStyle(
fontSize: 18,
color: Colors.white
),
),
decoration: BoxDecoration(
color: primaryColor,
borderRadius: BorderRadius.circular(5),
),
);
],
);
}
}
Several changes you can make like removing positioned filled to setting up
Column with
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
and setting it's children inside it.

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],
),
),
),
)
],
),
),
);
}
}