Columns+Rows vs Stack for static sized grid? - flutter

I have a list of widgets (from a list of objects that have an x and y coordinate value) of the same size I want display in a grid arrangement, the size and placement wont change.
My initial thought is to use 2 for loops for the columns and rows and pass the widgets as children accordingly.
But would a Stack widget or CustomMultiChildLayout be more performant and suited for this? Given that columns and rows are made to be more dynamically sized and I don't need that functionality.
Edit: Image, literally just a grid of tiles at the moment. It's currently using loops of columns+rows. Yes I will be flipping the Y around to start at the bottom corner.

You can use a gridview to create this layout
class MyHomePage extends StatelessWidget{
#override
Widget build(BuildContext context){
return Scaffold(
body: GridView.count(
crossAxisCount: 15,
children: List.generate(225, (index) {
return GridTile(
child: Container(
height: 50,
width: 50,
color: Colors.blue.shade200,
child: Center(
child: Text('$index'),
)
),
);
}),
),
);
}
}
Or In a wrap
Wrap(
children: List.generate(225, (index) => Container( width: 20, height: 20, child: Text("$index")))
)

Related

How to control size of a Column inside a SizedBox

I am building a widget to display a chart/graph inside a fixed size window. The chart will be bigger than the window so the solution includes the user being able to scroll the graph widgets around inside a window (you can find details about this in an earlier question I asked about this here).
Part of the layout includes a fixed sized panel created using a SizedBox and, within that, a Column containing rows of widgets that make up the graph. I need the Column to fit its contents tightly so that I can track it's size and, for example, stop the user scrolling up when the last row is visible at the bottom of the SizedBox.
When the size of the children in the Column should make the Column smaller than the SizedBox, the Column is still being forced to be the size of the SizedBox. This is explained in the Flutter documentation here.
According to the Flutter documentation, the solution is:
This can be remedied by wrapping the child SizedBox in a widget that
does permit it to be any size up to the size of the parent, such as
Center or Align.
I have tried this and it doesn't seem to work. Below is a test app I wrote on DartPad to check this out. If I use either Align or Center as child of SizedBox and parent of the Column widget, the Column is still the same size as the SizedBox. I have also added MainAxisSize.min to the Column, but this doesn't appear to make any difference.
I have considered doing this using a Stack so that the Column is displayed over the SizedBox, rather than as a child of it, but that feels like a bit of a hacky workaround given that the documentation suggests you can control the size of a Column inside a SizedBox.
Does anyone know how I force the Column to be the smallest size it can be inside a SizedBox of fixed size?
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
List<Widget> graphWidgets = const [
Text(
'long-line-of-text-long-line-of-text-long-line-of-text-long-line-of-text-long-line-of-text-long-line-of-text-long-line-of-text'),
Text(
'long-line-of-text-long-line-of-text-long-line-of-text-long-line-of-text-long-line-of-text-long-line-of-text-long-line-of-text'),
Text(
'long-line-of-text-long-line-of-text-long-line-of-text-long-line-of-text-long-line-of-text-long-line-of-text-long-line-of-text'),
];
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.light(),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: SizedBox(
// This is the window the chart is displayed in
height: 200,
width: 400,
child: DecoratedBox(
decoration: BoxDecoration(
border: Border.all(),
),
child: OverflowBox(
// Used to prevent graph rows from wrapping in the window
maxHeight: double.infinity,
maxWidth: double.infinity,
alignment: Alignment.topLeft,
child: DecoratedBox(
// Debug widget to show extent of child column
decoration: BoxDecoration(
border: Border.all(),
color: Colors.amber,
),
child: Center(
// This should allow the Column to be smaller than the SizedBox?
child: Column(
// This holds the widgets that make up the graph
mainAxisSize: MainAxisSize.min,
children: graphWidgets,
),
),
),
),
),
),
),
),
);
}
}
As per SayyidJ's reply, the answer is to use a ConstrainedBox rather than a SizedBox. The Column can shrink to fit the contents when it is the child of a ConstrainedBox.
Here is the revised code, which you can run in DartPad, showing this working.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
List<Widget> graphWidgets = const [
Text(
'long-line-of-text-long-line-of-text-long-line-of-text-long-line-of-text-long-line-of-text-long-line-of-text-long-line-of-text'),
Text(
'long-line-of-text-long-line-of-text-long-line-of-text-long-line-of-text-long-line-of-text-long-line-of-text-long-line-of-text'),
Text(
'long-line-of-text-long-line-of-text-long-line-of-text-long-line-of-text-long-line-of-text-long-line-of-text-long-line-of-text'),
];
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.light(),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: ConstrainedBox(
// This is the window the chart is displayed in
constraints: BoxConstraints(
maxHeight: 200,
maxWidth: 400,
),
child: DecoratedBox(
decoration: BoxDecoration(
border: Border.all(),
),
child: OverflowBox(
// Used to prevent graph rows from wrapping in the window
maxHeight: double.infinity,
maxWidth: double.infinity,
alignment: Alignment.topLeft,
child: DecoratedBox(
// Debug widget to show extent of child column
decoration: BoxDecoration(
border: Border.all(),
color: Colors.amber,
),
child: Column(
// This holds the widgets that make up the graph
mainAxisSize: MainAxisSize.min,
children: graphWidgets,
),
),
),
),
),
),
),
);
}
}

Flutter Card child content height is larger than its parent

I'm trying to use a GridView to handle displays for multiple Card, each Card contains of an Image. Unfortunately it turns out that the Image is taking a larger height than its parent (see attached picture for the details).
I'm pretty new to Flutter layout so any ideas why this is happening and how I can resolve this? I want the layout to be something like this:
Display 2 cards on each line.
The Card width or height should not be fixed.
The Image height should be scaled according to its width.
class SquadSelectionScreen extends StatelessWidget {
final List<Team> teams;
const SquadSelectionScreen({super.key, required this.teams});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Squads'),
),
body: GridView.count(
crossAxisSpacing: 10,
crossAxisCount: 2,
padding: const EdgeInsets.all(16),
children: teams
.map(
(team) => SquadView(team: team),
)
.toList(),
),
);
}
}
class SquadView extends StatelessWidget {
final Team team;
const SquadView({super.key, required this.team});
#override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
context.push('/squads/${team.code}');
},
child: Card(
elevation: 1,
child: Column(
children: [
Image(
image: NetworkImage(team.imageUrl),
),
const SizedBox(
height: 8,
),
Center(
child: Text(team.name),
),
],
),
),
);
}
}
Using GridView.count has a very visible drawback, namely the size of the aspect ratio of the grid will always be one (1:1 or Square) and can't be changed.
So if you look at the code above, you can't set an image with the same aspect ratio because the text will sink.
The first suggestion for me if you still want to use GridView.count is
Wrapping your Image with AspectRatio that has value higher than one (example set Ratio to 4/3, 5/3, 16/9, or landscape looks). Note: 4/3 = is higher than 1, 16/9 = is higher than 1, etc..
Then wrap the Text Widget with Expanded()
Example code:
class SquadView extends StatelessWidget {
final Team team;
const SquadView({super.key, required this.team});
#override
Widget build(BuildContext context) {
return InkWell(
onTap: () {},
child: Card(
elevation: 1,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
AspectRatio(
aspectRatio: 4/3, // you can set the value to 16/9 or anything that result is higher than one
child: Image(
image: NetworkImage(team.imageUrl),
fit: BoxFit.cover, // set How the image looks to Fit
),
),
const SizedBox(
height: 8,
),
Expanded(
child: Center(
child: Text(team.name, overflow: TextOverflow.ellipsis),
),
),
],
),
),
),
);
}
}
I suggest you try GridView.builder or another GridView. You can look at the documentation here
or this third package this will be good for to try flutter_staggered_grid_view. The flutter_staggered_grid_view is more flexible to create GridView with various size.

Flutter align two widgets in same position on stack

I have 2 widget in stack. One of them is aligned in center. And the widget which is on the top is other stack. And I want show second widget with first widget's top and left position. Here is the explain:
The yellow area is my stack. And first widget is setted like Center(child: myFirstWidget). My second widget is referenced from here it's a resizable widget so it's an another stack and it's childs are "positioned". So I need to set top and left value for initialize. But my main stack filled page So when I set my second widget's top and left to 0. It's shown as below.
But I want to show align it to centered child's top like:
My code snip:
child: Container(
color: Colors.red,
child: Stack(
children: [
Center(
child: Image.file(
File("myfilepath"),
),
),
ResizableWidget(
child: Container(
color: Colors.blue,
),
),
],
),
),
You can adjust the position of the widgets with the Position widget like this:
Positioned(
child: ResizableWidget(
child: Container(
color: Colors.blue,
),
),
top: 80,
right: -5,
),
The value of top and right are just random values, you should check what works for you!
You should make Center the parent of the Stack.
The Stack will be positioned at the center of its parent because of Center, it will get the dimensions of its biggest childs (height and width) and will position other childs according to the alignment value.
Edit : I modified the code to include a placeHolder image. Here I gave it a 6000x2000 size but you can change it to whatever value you want. Code is working as expected, see capture below.
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Stack(
alignment: AlignmentDirectional.topStart,
children: [
Image.network('https://via.placeholder.com/6000x2000'),
SizedBox(
height: 50,
width: 50,
child: Container(
color: Colors.green,
))
],
),
),
);
}

How to use Intrinisic Height for column in flutter

I'm struggling with building a fraction in Flutter.
I would like my Column to render two independent containers: nominator and denominator. And I would like them to be the same exact size. I mean when the nominator has another fraction in it, the denominator should be the same height.
Is it possible to have a Column with two elements of each size depending on the highest one?
I do NOT want to use Expanded because I use heights dynamically. I use it in the SingleChildScrollView.
I can't predict the exact height because just imagine a fraction like this
1/2 / 3
and I would like a "3" object to have the same height like "1/2".
My code:
Column(
children: [
IntrinsicWidth(
child: Column( children: [
numerator,
SizedBox(height: 5),
fractionBar,
SizedBox(height: 5),
denominator
],),
)
],
),
As you sure know, actually in Flutter everything is a Widget, that also means you can store every Widget as a variable and use the getter to get the parameters.
Here's an example code:
import 'package:flutter/material.dart';
class Test extends StatelessWidget {
//saving the Container as variables, your nominator & dominator widgets could be placed as child's inside them
final Container nominator = Container(height: 50.0, color: Colors.purple);
final Container denominator = Container(height: 100.0, color: Colors.red);
double getContainerHeight () { //helper function for dynamically setting the height depending on which widget is bigger.
var _height;
nominator.constraints.minHeight < denominator.constraints.minHeight ?
_height = denominator.constraints.minHeight :
_height = nominator.constraints.minHeight;
return _height;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
child: Column(
children: [
SizedBox(height: 100.0),
Container(
height: getContainerHeight(),
color: Colors.green,
child: Center(child: nominator),
),
Container(
height: getContainerHeight(),
color: Colors.blue,
child: Center(child: denominator),
)
],
),
)
);
}
}
In this example I used Container to wrap the denominator & nominator for an easy access to their height parameters: Container.constraints.minHeight
Also notice that the nominator & denominator get built before the Container in the Column.
Here's the output:

Making a 2x2 grid in Flutter

I'm trying to create a 2x2 grid for displaying some info in cards. Disclaimer: I'm totally new to Dart and Flutter, so expect a lot of ignorance on the topic here.
These cards should have a fixed size, have an image, display some text... and be positioned from left to right, from top to bottom.
First, I tried to use the Flex widget, but it seems to only work horizontally or vertically. Therefore, my only solution was to use two Flexes, but only showing the second when the amount of elements is higher than 2 (which would only use one row).
Then, I tried using GridView, but it doesn't work in any possible way. It doesn't matter which example from the Internet I copy and paste to begin testing: they just won't show up in the screen unless they're the only thing that is shown in the app, with no other widget whatsoever. I still don't understand why that happens.
This is my current code:
First widgets in "home_page.dart":
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Padding(padding: EdgeInsets.only(top: 30)),
Text(
'App test',
style: TextStyle(fontSize: 24),
),
EventsList(key: new Key('test')),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
The "EventList" part is a widget that should represent the grid functionality I explained before. This class gets some info from a service (which currently just sends some hardcoded info from a Future), and paints the given widgets ("Card" items, basically) into the EventList view:
class _EventsListState extends State<EventsList> {
#override
Widget build(BuildContext context) {
return FutureBuilder<List<Event>>(
future: new EventsService().getEventsForCoords(),
builder: (context, AsyncSnapshot<List<Event>> snapshot) {
if (snapshot.hasData) {
return Padding(
padding: EdgeInsets.only(left: 20, right: 20),
child: Flex(
direction: Axis.horizontal,
verticalDirection: VerticalDirection.down,
mainAxisAlignment: MainAxisAlignment.center,
children: generateProximityEventCards(snapshot.data),
));
} else {
return CircularProgressIndicator();
}
});
}
List<Card> generateProximityEventCards(List<Event> eventList) {
// Load Events from API
print(eventList);
// Render each card
return eventList.map((Event ev) {
return Card(
child: Padding(
padding: EdgeInsets.only(bottom: 15),
child: Column(
children: <Widget>[
Image(
fit: BoxFit.cover,
image: ev.imageUrl,
height: 100,
width: 150,
),
Padding(
child: Text(ev.name),
padding: EdgeInsets.only(left: 10, right: 10),
),
Padding(
child: Text(ev.address),
padding: EdgeInsets.only(left: 10, right: 10),
),
],
),
));
}).toList();
}
}
This is how it currently looks:
As I said before, I understand that the Flex widget can't really get that 2x2 grid look that I'm looking for, which would be something like this (done with Paint):
So, some questions:
How can I get a grid like that working? Have in mind that I want to have more stuff below that, so it cannot be an "infinite" grid, nor a full window grid.
Is it possible to perform some scrolling to the right in the container of that grid? So in case there are more than 4 elements, I can get to the other ones just scrolling with the finger to the right.
As you can see in the first image, the second example is bigger than the first. How to limit the Card's size?
Thank you a lot for your help!
The reason the gridview was not working is because you need to set the shrinkWrap property of theGridView to true, to make it take up as little space as possible. (by default, scrollable widgets like gridview and listview take up as much vertical space as possible, which gives you an error if you put that inside a column widget)
Try using the scrollable GridView.count widget like this and setting shrinkWrap to true:
...
GridView.count(
primary: false,
padding: /* You can add padding: */ You can add padding const EdgeInsets.all(20),
crossAxisCount: /* This makes it 2x2: */ 2,
shrinkWrap: true,
children: generateProximityEventCards(snapshot.data),
...
Is this what you exactly want?
do let me know so that I can update the code for you
import 'package:flutter/material.dart';
class List extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.white,
title: Text('Inicio', style: TextStyle(color: Colors.black, fontSize: 18.0),),
),
body: GridView.count(
shrinkWrap: true,
crossAxisCount: 2,
children: List.generate(
50,//this is the total number of cards
(index){
return Container(
child: Card(
color: Colors.blue,
),
);
}
),
),
);
}
}