Making a card having text and image using Row and an inner Column - flutter

My card's text part has multiple text widgets that I have to put under column. I want to add this whole part under a row widget, so that I can add the image part to the right of this column widget.
This is what I've done:
class ContactMe extends StatelessWidget {
static const String route = '/contact_me';
const ContactMe({Key? key}) : super(key: key);
Widget build(BuildContext context) {
double width = MediaQuery.of(context).size.width;
double height = MediaQuery.of(context).size.height;
return Scaffold(
body: Column(
children: [
CommonNavBar(height: height),
Expanded(
child: FutureBuilder(
future: contactMe(),
builder: (context, snapshot) {
if (snapshot.data == null)
return Center(child: CircularProgressIndicator());
else {
var data = snapshot.data as List<String>;
return LayoutBuilder(builder: (context, constraints) {
if (constraints.maxWidth < 1000) {
return Center();
} else {
return Row( //this is parent row
children: [
Container(
child: Column( //the column that contains multiple other widgets
children: [
text('Reach Out to me!', 25,
Theme.of(context).primaryColorLight),
Text('anything'),
],
),
),
Image.asset('assets/contact_me/' + data[2], scale: 2), //the image widget
],
);
}
});
}
}),
),
],
),
);
}
}
But instead of a card, the layout looks like this:
As visible in the picture, the image for some reason is not in line with the column widget. What's the mistake I'm making here?

In continuation to #OlegBezr's answer, I fixed it by using mainAxisAlignment and crossAxisAlignment for both the widgets.
return Row(
crossAxisAlignment: CrossAxisAlignment.start, //takes the row to the top
mainAxisAlignment: MainAxisAlignment.spaceAround, //Used this for spacing between the children
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start, //used for aligning the children vertically
children: [
text('Reach Out to me!', 25,
Theme.of(context).primaryColorLight),
text(
'DISCUSS A PROJECT OR JUST WANT TO SAY HI? MY INBOX IS OPEN FOR ALL.',
18,
Theme.of(context)
.primaryColorLight
.withOpacity(0.3)),
],
),
Image.asset('assets/contact_me/' + data[2], scale: 2),
],
);

I think your problem is related to different alignments. The default crossAxisAlignment for a Row is CrossAxisAlignment.center, thus your image and column are located in the vertical middle of the row. The default mainAxisAlignment for a Column is MainAxisAlignment.start, so the content inside your column is located at its top.
I can see two possible ways to put your content on the same horizontal line:
Set mainAxisAlignment for the Column to MainAxisAlignment.center
Set crossAxisAlignment for the Row to CrossAxisAlignment.start
For the future, you might find this resource helpful when dealing with different layouts: https://docs.flutter.dev/development/ui/layout

Related

Widget flickers and disappears when scrolling

I'm already losing sleep over this.
I'm trying to display a chart inside a ListView (for scrolling). For some reason the contents of the Card flickers when scrolling and randomly completely disappears (the Card itself stays visible though).
Any idea why would that happen?
(...) ListView (...)
children: [Row ( children: [buildChartBox()] )] (...)
Expanded buildChartBox() {
return Expanded(
child: Card(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
children: [
Column(
mainAxisSize: MainAxisSize.min,
children: [
chartTitles(
title: 'Items',
subtitle: 'by value'),
SizedBox(
height: 300,
child: ValuesChart(data: calculateValues(items)))
],
),
],
),
),
),
);
}
Row chartTitles({String title = '', String subtitle = ''}) {
return Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: text_charttitle),
Text(subtitle, style: text_chartsubtitle),
],
)
],
);
}
Things tried:
Both of these were originally Stateless Widgets; I changed to simple
methods to simplify but it didn't change the weird behaviour.
Replacing the chartTitles return with an empty Container (i.e. removing the titles) does mitigate the issue. The chart then stays displayed but also flickers slightly.
Replacing the ListView with a SingleChildScrollView doesn't change anything.
EDIT: Code for the ValuesChart:
import 'package:fl_chart/fl_chart.dart';
class ValuesChart extends StatelessWidget {
final Map<String, int> data;
const ValuesChart({required this.data});
#override
Widget build(BuildContext context) {
return Container(
child: PieChart(
_theData(data),
));
}
}
Note I'm using a package called 'fl_chart'. _theData just returns various parameters for the chart, I don't think it's relevant.
Try to replace ListView with SingleChildScrollView
ListViews in flutter by default using what it is called in Android RecyclerView to efficiently use render resources.
If you are interested here an article
https://medium.com/1mgofficial/how-recyclerview-works-internally-71290de5d2c4

Why the sibling applies the cross mainAxisAlignment?

I am pretty new in Flutter and I have created the following widget:
class Registration extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[Text("Hello1")]),
),
Expanded(child: Text("Hello2")),
],
);
}
}
it looks:
I would like to know, why the text Hello2 is also placed in the middle of screen. I did not tell it explicitly.
What I am trying to achieve is
Row and Column all have default alignment even you didn't assign any value. Here is the source of Column:
...
Column({
Key? key,
MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
MainAxisSize mainAxisSize = MainAxisSize.max,
CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
TextDirection? textDirection,
VerticalDirection verticalDirection = VerticalDirection.down,
TextBaseline? textBaseline,
List<Widget> children = const <Widget>[],
}) : super(
...
To your question:
why the text Hello2 is also placed in the middle of screen. I did not tell it explicitly.
It is because crossAxisAlignment is default CrossAxisAlignment.center.
You can get more reference about flutter layout/constraint here: Flutter Layout Cheat Sheet and Understanding constraints
You can align Hello2 with Align Widget. By default Hello2 is placed at the beginning of the second Expanded which takes half the screen.
class Registration extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[Text("Hello1")]),
),
Expanded(child: Align(alignment: Alignment.bottomRight,child:Text("Hello2"))),
],
);
}
}
You can achieve the same with a Container in place of Align.
To understand wrap you zones with containers and add colors:
class Registration extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Expanded(
child: Container(color: Colors.green,child:Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[Text("Hello1")]),
),
),
Expanded(
child:
Container(color: Colors.red, child: Text("Hello2"))),
],
);
}
}
The Expanded widget takes all the space available. if you put 2 or more Expanded widgets in a column, they will share equally the available space unless you specify a different flex property for one of them. By default flex = 1 for an Expanded widget.
Flutter widget of the week (Expanded):
https://www.youtube.com/watch?v=_rnZaagadyo

Flutter : How to place a Widget below fixed Centered Widget inside a Container

How to place a Widget below fixed Centered Widget inside a Container? I am using a GridView to show widgets horizontally. GridView item will have a Text Widget which has to be fixed at the Centered everytime in the screen. I have to place a Text widget below that Centered Widget.
Reference Screenshot:
Adding the build method code of the GridView item I have tried till now. But the Text Widget is not getting centered. The output I am getting. How to fix this part ?
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CustomText(
(dayModel?.date?.day ?? "").toString(),
AppTextStyle.Body,
customColor: _getColorBasedOnStyle(
dayModel.style,
),
),
Visibility(
visible: dayModel?.style == CalendarDayStyles.NOT_AVAILABLE,
child: CustomText(
Strings.no_slots_label,
AppTextStyle.SublineForCalendar,
customColor: AppColors.BLACK_20,
),
),
],
);
}
I believe the secret to doing this right is not only in how you build "6", but also in how you build "5" and "7".
E.g. you could build every one of them as column with 3 boxes on top of each other, pseudocode:
Column(
children: [
SizedBox(height: fixedHeight, child: empty)
SizedBox(height: fixedHeight, child: Text("5")) // or "6" or "7"
SizedBox(height: fixedHeihgt, child: empty) // or booking status
]
)
or other way of doing it if we have to avoid using fixedHeight is by using the Expanded Widget inside the Column Widget
Column(
children: [
Expanded(child: Container()),
Expanded(child: Center(child : Text("5"))), // or "6" or "7"
Expanded(child: Center(child : Text("No Slots"))) // or booking status
]
)
If you set crossAxisAlignment of the row to start and then show a column with "no slots" underneath, shouldn't this fix your issue?
You could use the CrossAxisAlignment.center:
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
),
Full snippet code:
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
CustomText(
(dayModel?.date?.day ?? "").toString(),
AppTextStyle.Body,
customColor: _getColorBasedOnStyle(
dayModel.style,
),
),
Visibility(
visible: dayModel?.style == CalendarDayStyles.NOT_AVAILABLE,
child: CustomText(
Strings.no_slots_label,
AppTextStyle.SublineForCalendar,
customColor: AppColors.BLACK_20,
),
),
],
);

How to make a Widget to be as small as it can (wrap_content in Android)?

I have a rendering issue with render tree similar to Column>PageView>Column, where the last Column is inside a page of the PageView.
The PageView isn't being rendered correctly, so I get an exception (Horizontal viewport was given unbounded height.) as the framework can't calculate its sizes, I can fix it by wrapping it into a Flexible or an Expanded, but I don't want the PageView to take the whole screen, I want it to be as small as possible and on the center on the screen.
Here's a representation of my problem:
// This code throws an exception:
class Widget_1_Has_Problem extends StatelessWidget {
final PageController pageController = PageController();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(Icons.done,size: 112),
SizedBox(height: 32),
PageView(
physics: NeverScrollableScrollPhysics(),
controller: pageController,
children: <Widget>[
// Many widgets go here, I am just simplifying with a Column.
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Text('TEXT TEXT TEXT TEXT TEXT TEXT'),
SizedBox(height: 16),
Text('TEXT2 TEXT2 TEXT2 TEXT2 TEXT2 TEXT2'),
],
),
],
),
],
),
);
}
}
This is what I wanted to achieve (I removed the PageView in order to show it):
class Widget_2_No_PageView extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(Icons.done,size: 112),
SizedBox(height: 32),
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Text('TEXT TEXT TEXT TEXT TEXT TEXT'),
SizedBox(height: 16),
Text('TEXT2 TEXT2 TEXT2 TEXT2 TEXT2 TEXT2'),
],
),
],
),
);
}
}
And this is how I can fix it, but it's not perfect, I will show later:
class Widget_3_With_Flex_Not_Perfect_Solution extends StatelessWidget {
final PageController pageController = PageController();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Spacer(flex: 2,),
Icon(Icons.done,size: 112),
SizedBox(height: 32),
Flexible(
flex: 1,
child: PageView(
physics: NeverScrollableScrollPhysics(),
controller: pageController,
children: <Widget>[
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Text('TEXT TEXT TEXT TEXT TEXT TEXT'),
SizedBox(height: 16),
Text('TEXT2 TEXT2 TEXT2 TEXT2 TEXT2 TEXT2'),
],
),
],
),
),
Spacer(flex: 2,),
],
),
);
}
}
This solution isn't perfect because Spacer and Flexible or Expanded always try to maintain it's proportionality of the flex weight, which means that for smaller displays the Spacer won't vanish, it will remain there, whereas the first and desired image won't have such a void space. As we can see in the below image, the Spacer is always there. Also I have to calculate the flex factor for every change in this design, whereas in my first code example the widget will size itself to the center of the screen automatically.
How can I instruct PageView then to be as small as it can be, instead of expand as much as it wants, as that's the only solution I can find online?
The PageView uses SliverFillViewport to wrap around given children.
This SliverFillViewport doesn't care about how small the children are. It only cares how much space it can occupy from its parent. So the Flex and Align widget are useless.
Currently with PageView, it most likely not possible unless we do some calculation and use SizedBox or ConstrainedBox.
I also found this discussion
This can be done, but not with PageView. I had to use SingleChildScrollView with a Row. The reason for that is: if you want to size your PageView properly, you need to build and layout every child it has. PageView does not do that, but Row does, you can even decide how smaller elements should be positioned. PageController also works if you properly set widths of children in the row.
Note that this will only work nicely if you have small amount of pages.
class SizingPageView extends StatelessWidget {
final ScrollPhysics physics;
final PageController controller;
final List<Widget> children;
const SizingPageView({Key key, this.physics, this.controller, this.children})
: super(key: key);
#override
Widget build(BuildContext context) {
Widget transform(Widget input) {
return SizedBox(
width: MediaQuery.of(context).size.width,
child: input,
);
}
return SingleChildScrollView(
physics: physics ?? PageScrollPhysics(),
controller: controller,
scrollDirection: Axis.horizontal,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: children.map(transform).toList(),
),
);
}
}
class Solution extends StatelessWidget {
final PageController pageController = PageController();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Spacer(
flex: 2,
),
Icon(Icons.done, size: 112),
SizedBox(height: 32),
Text('BEFORE SCROLL'),
SizingPageView(
physics: NeverScrollableScrollPhysics(),
controller: pageController,
children: <Widget>[
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Text('TEXT TEXT TEXT TEXT TEXT TEXT'),
SizedBox(height: 16),
Text('TEXT2 TEXT2 TEXT2 TEXT2 TEXT2 TEXT2'),
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Text('TEXT TEXT TEXT TEXT TEXT TEXT'),
],
),
Text('Third'),
],
),
Text('AFTER SCROLL'),
Spacer(
flex: 2,
),
FlatButton(
child: Text('Next'),
onPressed: () {
var next = pageController.page.round() + 1;
if (next >= 3) {
next = 0;
}
pageController.animateToPage(
next,
duration: Duration(seconds: 1),
curve: Curves.easeInOut,
);
},
)
],
),
);
}
}
You are asking for items inside of the PageView to set the size of PageView itself. This is not going to work with the standard class. If you look at its source, you'll find that uses Viewport inside Scrollable. Viewport grabs maximum size and ignores the size of the children (Slivers). You'll need to use a different approach or maybe create a custom PageView, perhaps one that uses ShrinkWrappingViewport.
https://api.flutter.dev/flutter/widgets/ShrinkWrappingViewport-class.html
By wrapping PageView inside SizedBox, we can get rid of the exception you are getting. (Horizontal viewport was given unbounded height.).
SizedBox(height: 32, child:
PageView(
physics: NeverScrollableScrollPhysics(),
controller: pageController,
children: <Widget>[
// Many widgets go here, I am just simplifying with a Column.
Column(
...
But that will give you a bottom overflow exception of 16 pixels since you have another SizedBox with fixed height inside Column in PageView.
In order to resolve this issue, let's just for time being replace top SizedBox with Container and add color property to it to see how much space and height is available to us.
So, instead of hardcoding the sizedbox height to 32, we can make use of MediaQuery to calculate height for us.
If you print height of the top SizedBox, it will come out to be 597
SizedBox(height: MediaQuery.of(context).size.height,
I for demo, divided the height with 5 to get 119.4 height of the SizedBox widget that contains PageView, which resolves the renderflow exception as well as maintains pageview's location at center of the screen.
If we again use Container to see now how much space and height is available to us, it will look like:
So with this approach, you will not be required to use Expanded or Flexible widgets as you mentioned and won't need to change much of your code.
The working code is as below:
h = MediaQuery.of(context).size.height / 5;
print(h); // 119.4
return Scaffold(
body: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(Icons.done,size: 112),
SizedBox(height: h, child:
PageView(
physics: NeverScrollableScrollPhysics(),
controller: pageController,
children: <Widget>[
// Many widgets go here, I am just simplifying with a Column.
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Text('TEXT TEXT TEXT TEXT TEXT TEXT'),
SizedBox(height: 16),
Text('TEXT2 TEXT2 TEXT2 TEXT2 TEXT2 TEXT2'),
],
),
],
),
),
],
),
);
You might need to adjust height per your need, but I hope this resolves your issue and answers your question.
You can simply wrap your widget with Flexible widget https://api.flutter.dev/flutter/widgets/Flexible-class.html

Is it possible to exclude 1 widget from a column that has padding?

I have a column with buttons where I want padding for, however the top most widget is a row that I do not want padding for.
Is it possible to exclude the row from the padding or do I have to define the padding for each and every button individually?
Is your row inside your column? If yes simply take your row out of the column so it will not get the padding.
You can create a seperate class that creates the buttons and specify the padding once. Then you can call that class again and again to create as many buttons as you want in the Column.
class MakeButton extends StatelessWidget{
final String _buttonCaption;
MakeButton(this._buttonCaption);
#override
Widget build (BuildContext context){
return Padding(
padding: EdgeInsets.all(10),
child: FlatButton(
onPressed: () {},
child: Text(
_buttonCaption
),
),
);
}
}
Now you don't need to specify the padding for the Column and the Row will not have any padding.
class Test extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text("Row DATA 1"),
Text("Row DATA 2"),
Text("Row DATA 3"),
],
),
MakeButton("Flat Button 1"),
MakeButton("Flat Button 2"),
MakeButton("Flat Button 3"),
],
);
}
}
It should look something like this -
Try this approach.
Column(
children: <Widget>[
Row(), // Row without padding
Padding( // add Padding widget, make Column its child and put Buttons inside
padding: your_padding_here,
child: Column(
children: <Widget>[
Button1(),
Button2(),
],
),
)
],
)