How to overlap items in a ListView? - flutter

I would like to have a drop shadow on each item and each item should overlap the top of the next sibling.

It's probably too late for the original poster, but maybe it will help someone else looking for this:
The best solution I found to this (without resorting to a manually managed stack) is to use an Align widget with a height factor of less than 1. This will cause the next item to overlap with the previous one.
Here are the key elements to make this work:
final elements = ['A', 'B', 'C'];
final padExtend = 16.0;
final media = MediaQuery.of(context);
return ListView.builder(
itemCount: elements.length,
itemBuilder: (BuildContext context, int index) {
final String content = elements[index];
return Align(
child: Container(
width: media.size.width - padExtend / 2.0,
padding: EdgeInsets.all(padExtend),
decoration: BoxDecoration(
boxShadow: [
BoxShadow(color: Colors.grey, spreadRadius: 3, blurRadius: 3, offset: Offset(2, 1)),
],
color: Colors.white,
),
child: Text(content),
),
alignment: Alignment.topCenter,
heightFactor: 0.9,
);
},
);
Since the align would center the elements, a media query is used to set a fixed width.

Wraping your elements with OverflowBox and giving the maxHeight value will achieve this effect.
return SizedBox(
height: 35, //--> list children will be 35 in height
child: OverflowBox(
maxHeight: 50, // --> allowing the child to overflow will cause overlap between elements
child: Container(
height: 50,
child: Text((index + 1).toString()),
),
),
);

use SizedOverflowBox (preferred) or OverflowBox, it is very easy to implement such effects... Flutter YYDS

You could try my package (signed_spacing_flex). It's exactly the same as a normal Column (or Row and Flex). But it lets you set negative spacing which causes its children to overlap.
You can also set which children should be on top when they overlap.
It works with expanded children if you need.

You can use a elevation and padding top to achieve the effect you want :
ListView.builder(
itemCount: 5,
itemBuilder: (BuildContext content, int index) {
return Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Material(
elevation: 5.0,
child: new Container(
height: 100.0,
),
),
);
})
If you want to overlap your items just use a heightFactor below 1.0
return Align(
alignment: Alignment.topLeft,
heightFactor: 0.9,
child: your item...

Related

How to make a dynamic list of elements appear horizontally up to each 6 index and then vertically in flutter

I have a dynamic list which will be increased every 10 seconds and I want this list to be displayed in flutter UI in such a way that the first 6 numbers should display on the first line and the next 6 numbers should display on the next line and so on.
Currently, the code I made just displays a list in a horizontally scrollable view.
Widget horizontalList1 = new Container(
margin: EdgeInsets.symmetric(vertical: 20.0),
height: 40.0,
child: new ListView(
scrollDirection: Axis.horizontal,
children: calledNumersArray(),
)
);
You could use a GridView and limit its CrossAxisCount
Widget horizontalList1 = new Container(
margin: EdgeInsets.symmetric(vertical: 20.0),
height: 40.0,
child: GridView.count(
count: calledNumbersArraay().length
crossAxisCount: 6,
children: calledNumersArray(),
)
);
https://api.flutter.dev/flutter/widgets/GridView-class.html
If you're adding elements dynamically, I suggest you use a builder constructor to avoid performance issues. I think GridView can help you here. However, you'd need to refactor your code so that your number widgets are generated in the itemBuilder function.
Like so:
final calledNumbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]; // generate the numbers somehow
Widget horizontalList1 = Container(
margin: EdgeInsets.symmetric(vertical: 20.0),
child: GridView.builder(
itemCount: calledNumbers.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 8,
),
itemBuilder: (BuildContext context, int index) { // create the widgets here
final calledNumber = calledNumbers[index];
return Container(
height: 30,
width: 30,
margin: EdgeInsets.all(14.0),
key: ValueKey(index), // make sure to add a key
child: Center(child: Text(calledNumber.toString())),
decoration:
BoxDecoration(shape: BoxShape.circle, color: Colors.red));
},
));
The result:

Wrapping contents of Row widget if overflowed

I have a list storing the asset image location. Using that list I display those many cards of certain width side by side inside a website.
When the items in list increases, the row overflows. I want those cards to appear below, To have rest of cards in another line.
I'm returning this row to a singleChildScrollView parent widget.
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: ProjectData.map((item) => Container(
constraints: BoxConstraints(maxHeight: 320, maxWidth: 240),
child: Card(
child: Image.asset(item.fileLoc),
),
)).toList(),
),
This row sits under a Column() widget and this entire Column is returned to a SingleChildScrollView()
Use the Wrap widget:
A widget that displays its children in multiple horizontal or vertical runs.
In your case, you can use as follows:
Wrap(
spacing: 8.0, // gap between adjacent cards
runSpacing: 4.0, // gap between lines
children: ProjectData.map((item) => Container(
constraints: BoxConstraints(maxHeight: 320, maxWidth: 240),
child: Card(
child: Image.asset(item.fileLoc),
),
)).toList(),
)
You can use GridView class, since, you can keep count of the item you want to show in per row. Basically GridView.count() is the one for you.
To control the number of items in it, the power will be given by crossAxisCount
Do not forget to give height to the GridView(), else, you will see a lot of errors coming up. It needs a parent height
We are passing the item as arrayname.map((item) => Your_Widget()).toList().cast<Widget>()
I have used my myArray for dummy show purpose. Feel free to use yours. The idea is to give you the best possible option
In case you wanna explore SingleChildScrollView class it will come in handy
LayoutBuilder(
builder: (BuildContext context, BoxConstraints viewportConstraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: viewportConstraints.maxHeight,
),
child: Container(
height: MediaQuery.of(context).size.height * 0.7,
width: MediaQuery.of(context).size.width,
child: GridView.count(
crossAxisCount: 2, // here you keep track of count
children: myArray.map((item) => Container(
margin: EdgeInsets.only(left: 15.0, right: 15.0, top: 15.0),
constraints: BoxConstraints(maxHeight: 320, maxWidth: 240),
decoration: BoxDecoration(
color: Colors.redAccent,
borderRadius: BorderRadius.circular(15.0)
)
)).toList().cast<Widget>()
)
)
)
);
}
)
Result
Feel free to try it out.

Setting a specific height to Flutter's Card widget

I'm trying to expand the height of a given grid of cards, so they are able to fit some more information than they currently do. These cards are wrapped by a GridView.count() that is shrinked, since I'm going to put more things below this widget.
As for now, the cards look like these, in which you can see that one of them overflows the text at the bottom, which is an undesired behavior (especially when I want the cards to have some bottom padding):
Being this the case, I would like to know if it's possible to manually change the card's height. I'm maybe letting this concrete configuration stay and remove some info, since I like the fact that the cards currently maintain their 1x1 proportion, but for curiosity sake, I would like to discover how to do this.
I tried many things, such as wrapping the Card widget with a Container or a SizedBox and manually setting the height, but none of these approaches change anything.
I guess that the problem may be in the GridView itself. This is how it looks:
return FutureBuilder<List<Event>>(
future: new EventsService().getEventsForCoords(),
builder: (context, AsyncSnapshot<List<Event>> snapshot) {
if (snapshot.hasData) {
return GridView.count(
crossAxisCount: 2,
shrinkWrap: true,
children: generateProximityEventCards(snapshot.data));
} else {
return CircularProgressIndicator();
}
});
As you can guess, the generateProximityEventCards method is the one that prints the Card widgets at the end. This is how the method looks as for now:
List<Widget> generateProximityEventCards(List<Event> eventList) {
// Render each card
return eventList.map((Event ev) {
return Card(
semanticContainer: true,
clipBehavior: Clip.antiAliasWithSaveLayer,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
elevation: 5,
margin: EdgeInsets.all(7),
child: SizedBox(
height: 600,
child: Column(
children: <Widget>[
Image(
alignment: Alignment.topCenter,
fit: BoxFit.fitWidth,
image: ev.imageUrl,
height: 110,
width: 200,
),
Container(
child: Text(ev.name),
padding: EdgeInsets.only(left: 10, right: 10),
),
Container(
child: Text(ev.startDate.toString()),
padding: EdgeInsets.only(left: 10, right: 10),
),
Container(
child: Text(ev.address),
padding: EdgeInsets.only(left: 10, right: 10),
),
],
),
));
}).toList();
}
So, in conclussion: how can I change the height of the cards so they can hold more information?
Thanks!
GridView isn't really designed to have tiles of different size. A good option is to use the package flutter_staggered_grid_view.
Now your tile sizes can even be dynamic, check out the code for the gif above!
To automatically fit some variable length text somewhere you can use the package auto_size_text.

Flutter ListView - center items if not expanding beyond screen

I have a horizontal ListView and depending on the app state, there might be 1-2 items in the list or many.
Now, I want to have the items centered as long as there are only a few available and scrolling is not needed. So far my items always stick to the left side as you can see in the ListView with the lime background color. That one I wanted centered, even if 2 or 3 items are visible, but with more, it shall scroll and then start at the left side with listing the items.
return Container(
color: Colors.lime,
child: ListView(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
children: listItems));
the items itself are this:
Widget getPersonCircleWidget(BuildContext context, Person p) {
return Container(
color: Colors.transparent, //yellow,
child: Padding(
padding: const EdgeInsets.fromLTRB(10, 0, 10, 0),
child: CircularProfileAvatar('',
child: Image(
fit: BoxFit.cover,
image:
UserPhotoManager.singleton.getUserProfilePhoto(p.userId)),
borderColor: colorCirclePhotoBorder,
borderWidth: 2,
elevation: 10,
radius: 40,
onTap: () {
....
}),
),
);
}
Any idea how to solve this?
Just wrap your ListView with Center widget.
Center(
child: ListView(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
children: listItems.map<Widget>((a) => CircleAvatar(
child: Text(a))).toList(),
),
),
When ListView exceed available room on the screen it will automatically left aligned.
Try it on dartpad.

SingleChildScrollView not keeping scroll position

I have a SingleChildScrollView widget in my app that contains a Column as a child.
The Column has many children and the last one in the very bottom of the scrolled screen is a StreamBuilder that I use to change a child Image
The issue is that when I tap on the image, the logic of the StreamBuilder works and the image is changed, but then the SingleChildScrollView scrolls a bit up so that the image is not visible and forces the user to scroll down again to be able to see the new loaded image.
Widget _buildScroll() => SingleChildScrollView(
child: Container(
width: 2080,
child: Column(
children: <Widget>[
_buildTopBar(),
_buildMainContent(),
SizedBox(height: 30),
Container(
child: Image.asset(
"assets/images/chart_legend.png",
width: 300,
),
),
SizedBox(height: 30),
Image.asset("assets/images/road_map.png", width: 600),
StreamBuilder<int>(
initialData: 1,
stream: _compareStream,
builder: (context, snapshot) {
if (snapshot.hasData) {
if (snapshot.data == 1) {
return Padding(
padding: const EdgeInsets.only(right: 10),
child: GestureDetector(
child: Image.asset("assets/images/compare1.png"),
onTap: () => _compareSubject.add(2),
),
);
} else if (snapshot.data == 2) {
return Padding(
padding: const EdgeInsets.only(right: 10),
child: GestureDetector(
child: Image.asset("assets/images/compare2.png"),
onTap: () => _compareSubject.add(3),
),
);
} else {
return Padding(
padding: const EdgeInsets.only(right: 10),
child: GestureDetector(
child: Image.asset("assets/images/compare3.png"),
onTap: () => _compareSubject.add(1),
),
);
}
}
return Padding(
padding: const EdgeInsets.only(right: 10),
child: GestureDetector(
child: Image.asset("assets/images/compare1.png"),
onTap: () {},
),
);
}),
],
),
),
);
However, even more weird is that, once I have done tap on all images, they will be showed as expected without scrolling up...meaning that, if there is the second time i tap on a image, the second time the image is replaced the scrolling up in not happening.
The problem here is that the size of your children changes and the SingleChildScrollView cannot handle that.
I think there could be two solutions that might work here:
If you know the sizes of your images before they are loaded, you should enforce it using a SizedBox. This way, the scroll position will stay the same:
SizedBox(
width: 300,
height: 120,
child: StreamBuilder<int>(...),
)
Use ensureVisible that you trigger once the stream builder is updated, which lets you control exactly where the image should be displayed.
You would need to assign a ScrollController to your SingleChildScrollView (controller parameter). Then, you also need a GlobalKey for your StreamBuilder that you want to show (key parameter).
If you have saved instances of the two to variables, you will be able to call the following once your image is loaded:
scrollController.position.ensureVisible(
globalKey.currentContext.findRenderObject(),
alignment: 0.5, // Aligns the image in the middle.
duration: const Duration(milliseconds: 120), // So it does not jump.
);