Flutter stack of cards - flutter

Im trying to create a stack of cards, superimposed, and offset of each other to visualize multiple versions of a card.
I have tried putting cards inside cards, but do not find a way to offset them.
I also tried using the stack class with no luck.
Anyone know how i can achieve this effect?

You were going in the right direction with Stack - you just had to figure out how to offset the widget. The best way to do this for the 'top' of the stack is to use padding, but you don't want to have to specify the size of each of the cards... it's much better if the stack grows/shrinks based on the content which is actually being shown.
To that end, you can use Positioned with all of the sizes specified for the cards. That will make sure that they grow to the appropriate size, without making the stack resize or having to specify each of their sizes.
Here's the code:
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: SafeArea(
child: Container(
color: Colors.grey,
child: ListView(
children: [
StackOfCards(
child: Container(height: 100.0),
num: 5,
),
StackOfCards(
child: Container(height: 100.0),
num: 4,
),
StackOfCards(
child: Container(height: 100.0),
num: 3,
),
StackOfCards(
child: Container(height: 100.0),
num: 2,
),
StackOfCards(
child: Container(height: 100.0),
num: 1,
)
\],
),
),
),
);
}
}
class StackOfCards extends StatelessWidget {
final int num;
final Widget child;
final double offset;
const StackOfCards({Key key, int num = 1, #required this.child, this.offset = 10.0})
: this.num = num > 0 ? num : 1,
assert(offset != null),
super(key: key);
#override
Widget build(BuildContext context) => Stack(
children: List<Widget>.generate(
num - 1,
(val) => Positioned(
bottom: val * offset,
left: val * offset,
top: (num - val - 1) * offset,
right: (num - val - 1) * offset,
child: Card(child: Container()))).toList()
..add(
Padding(
child: Card(child: child),
padding: EdgeInsets.only(bottom: (num - 1) * offset, left: (num - 1) * offset),
),
),
);
}
Hmmm... I guess that build function could probably be explained a bit. What I'm doing is using a generated list to iterate from 0..(num cards - 1) and calculating the appropriate offsets for each Positioned widget (each of which contains an essentially empty card).
Then this gets made from an iterable to a list with .toList(), but doesn't have the top card yet... so I do an inline add (I'm sure there's a better word for that, but I don't know it) of the Padding widget that has the appropriate offsets and contains the child. The ..add just makes it so that I can do it in one line - it returns the list instead of void as .add would. Yay for dart =)!
I made it a little bit flexible, but you could go further and define the offset as two parameters, make it so that you can go different directions etc. Anyways, the code results in this:

This may not be the best approach but according to the sample you posted in the comment you can align it with a Stack and Padding:
Stack(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 8.0, right: 8.0),
child: Card(
child: Center(
child: Padding(
padding: const EdgeInsets.all(50.0),
child: Text("Content"),
),
)),
),
Padding(
padding: const EdgeInsets.only(left: 8.0, bottom: 8.0),
child: Card(
child: Center(
child: Padding(
padding: const EdgeInsets.all(50.0),
child: Text("Content"),
),
)),
),
],
)
Which will look like this:
You can then customize it with setting different paddings etc.

Related

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.

How can i mack like this wedget in flutter exactly?

I'm working on the application and I need to build this widget which you can then click on the plus icon join to the team the problem is I can't do it like picture exactly
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(30),
child: Stack(
children: List.generate(
listOfEvents[i].attendeesList.length, (index) {
return Positioned(
left: index * 30,
child: const CircleAvatar(
backgroundImage: NetworkImage("https://avatars.githubusercontent.com/u/61495501?v=4"),
radius: 30,
)
);
}
)
)
);
}
you have to use the package called "dotted_border" and follow the given code
DottedBorder( child: // put your image here //borderType: BorderType.Circle,dashPattern: [10,5,10,5,10,5]),

scale/transform image in a dialog on load

I have a need to show a dialog with a map in it. The map will be somewhat small on smaller devices and only part of the map needs to be visible. I'd like to have the map load and then zoom and move to a predefined point based on which part of the map I need to show. So far I have this:
class RegionMap extends StatelessWidget {
const RegionMap({
Key key,
#required this.region,
}) : super(key: key);
final RegionMapping region;
#override
Widget build(BuildContext context) {
return Card(
color: Colors.white,
elevation: 4,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ClipRect(
child: Transform.scale(
scale: region.imageZoomScale,
alignment: Alignment.topLeft,
origin: region.imageOffset,
child: Stack(
children: [
Image.asset('assets/us_map.png'),
Image.asset(
'assets/regions_indv/${region.regionCode}.png'),
],
),
),
),
Divider(thickness: 2),
Text('${region.regionFullName}',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: textSize,
)),
],
),
);
}
}
which produces this output:
What I am struggling with is the right combination of animations and or transforms to get what I want. No matter what I set the Offset too it shows the same section of the map. Any thoughts on the right combination of things to get what I'm looking for?

Flutter putting Image.network into Dart isolate

Here i have a simple class that i show images from network with Image.network. as i used this class into a Listview, during scrolling that cause of a bit lag and i think i can fix it with Isolate.
after reading some documentation about this feature in Dart i'm not sure how can i put this this class or part of that such as a simple widget into that.
class InistaLikers extends HookWidget {
final List<String> imageUrls;
const InistaLikers({required this.imageUrls});
#override
Widget build(BuildContext context) {
late double _width = 0;
late int count = 4;
final _orientation = MediaQuery.of(context).orientation;
final _screenWidth = MediaQuery.of(context).size.width;
useEffect((){
if(_orientation == Orientation.portrait){
_width = _screenWidth* 0.39;
count = 4;
}else if(_orientation == Orientation.landscape){
_width = (_screenWidth / 2) * 0.39;
count = 3;
}
});
return Container(
width: _width,
height: 35.0,
child: Row(
children: [
Expanded(
child: Stack(
children: List.generate(
count,
(i) {
return Positioned(
right: imageUrls.length + (20.0 * i),
child: ClipOval(
child: Container(
width: 35,
height: 35,
child: Image.network(
imageUrls[i],
),
),
),
);
},
).toList(),
),
),
ClipOval(
child: Container(
width: 35,
height: 35,
child: Image.network(
imageUrls.last,
),
),
),
],
),
);
}
}
You cannot build a widget through an isolate as dart:ui which is used to render your widgets is only available on the main isolate. Moreover, Image.network already uses an ImageStream to manage the recuperation of an online image.
If you have some performance issues you should try to optimize the way you are building your widgets, for example if it was not the case already you should use ListView.builder if you have a lot of widgets to render.
You can find some "Performance best practices" documentation on the flutter website or the article Flutter Performance Tips written by Hasan Basri Bayat.
Here's some of the tips described in this article which you can apply to improve the performances of your app:
Use Widgets Over Functions
// Don't do this
[
_buildHeaderWidget(),
_buildMainWidget(context),
_buildFooterWidget(),
]
// Do this
[
HeaderWidget(),
MainWidget(),
FooterWidget(),
]
Use const where possible
const _myFixedHeight = 48.0;
Use const constructors whenever possible
class CustomWidget extends StatelessWidget {
const CustomWidget();
#override
Widget build(BuildContext context) {
// ...
}
}
Use nil instead of Container()
// Don't do this
Column(
children: [
text != null ? Text(text) : Container(),
],
)
// Do this
Column(
children: [
if (text != null)
Text(text),
],
)
And you'll find some more tips in the article.

Wrap stacked cards in flutter

I'm trying to stack some playing cards like the image shows. BUT I want the cards to wrap into a new line when there is no more space on the device.
This is what I have so far
class CollectionOfCards extends StatelessWidget {
List<PlayingCard> cards;
CollectionOfCards(this.cards);
#override
Widget build(BuildContext context) {
return Stack(
children: [
...cards
.asMap()
.map(
(key, value) => MapEntry(
key,
Positioned(
child: PlayingCardWidget(value),
left: 35.0 * key,
),
),
)
.values
.toList(),
],
overflow: Overflow.visible,
);
}
}
Any ideas how to do this? I imagine it is somewhat a common task.
Another idea would be to make a fan of cards that can always fit but I imagine thats harder to achieve!
Assume we want to have 2 rows of cards, each containing 10 cards. We need to use Positioned in a way that every card is positioned according to its index in the row(from 0 to 9) and its column(column 0 or column 1). So we need to set top and left in the Positioned widget. The 0.7 * cardHeight in top is for the vertical stacking, you can change that to cardHeight if you don't want to have vertical stacking.
Note: Set width and height of the cards using MediaQuery size. I didn't have the cards so I just made empty containers instead.
class _MyHomePageState extends State<MyHomePage> {
List<Widget> _cards = [];
void _initCards() {
double cardHeight = 400 / 2;
double cardWidth = 2500 / 20;
for (int i = 0; i < 20; i++) {
_cards.add(Positioned(
left: ((i >= 10) ? i - 10 : i) * cardWidth / 2,
top: (i >= 10) ? 0.7 * cardHeight : 0,
child: Card(
elevation: 10,
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)),
child: Container(width: cardWidth, height: cardHeight),
),
));
}
}
#override
void initState() {
super.initState();
_initCards();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
padding: EdgeInsets.symmetric(horizontal: 15.0),
child: Stack(
children: _cards
),
decoration: BoxDecoration(
color: Colors.yellow,
),
),
);
}
}
Result:
Just for reference. I ended up with solution like this inspired by #Mobina's answer
class CollectionOfCards extends StatelessWidget {
final List<PlayingCard> cards;
final double stackSkew = 0.4;
CollectionOfCards(this.cards);
#override
Widget build(BuildContext context) {
final double widgetWidth = MediaQuery.of(context).size.width - 16;
final double cardWith = PlayingCardWidget.defaultWidth(context);
final int cardsPerRow =
(widgetWidth / cardWith / stackSkew - 1 / stackSkew).floor();
return Stack(
children: [
...cards
.asMap()
.map(
(index, card) {
return MapEntry(
index,
Positioned(
child: Draggable(
childWhenDragging: Container(),
feedback: PlayingCardWidget(card: card),
child: PlayingCardWidget(
card: card,
),
),
left: stackSkew *
cardWith *
(index - (cardsPerRow) * (index / cardsPerRow).floor()),
top: (card.heightFromWidth(cardWith) + 2) *
(index / cardsPerRow).floor(),
),
);
},
)
.values
.toList(),
],
overflow: Overflow.visible,
);
}
}
It seems to work flawless.
One way you can do this is by passing left as well as top parameter to Positioned calculated based on -
Screen width
Card index ("key" in your code)
Card width
Card height
See the demo on dartpad - https://dartpad.dev/d36e018c1f1c7a6cd4b91d5b09e69a7c
Try using Wrap() widget inside a Column()
Column(
children: <Widget>[
Wrap(
children: <Widget>[
//your card widget
]
)
]
)