How can we create a list UI to make it similar to the image. Like the notification centre in IOS
as your requirement, I've made this widget for you. This is an interesting concept and I've thought about experimenting with it but never had the will to, but thanks to your question I went my way and built this for you. Helped me gain new knowledge as well :)
Find the attached code
import "package:flutter/material.dart";
class StackedList extends StatefulWidget {
const StackedList({super.key});
#override
State<StackedList> createState() => _StackedListState();
}
class _StackedListState extends State<StackedList> {
#override
Widget build(BuildContext context) {
final width = MediaQuery.of(context).size.width * 0.85;
//list that holds the widgets to be showed in the stack.
//you can use a function to generate this list with respect to your data
//I've used a static list of Containers to simplify my process,
//feel free to tinker with it
final list = <Container>[
Container(
height: 75,
width: width,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
color: Colors.green,
),
),
Container(
height: 75,
width: width,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
color: Colors.red,
),
),
Container(
height: 75,
width: width,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
color: Colors.grey,
),
),
Container(
height: 75,
width: width,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
color: Colors.brown,
),
)
];
return Scaffold(
body: Stack(
alignment: Alignment.center,
children: [
...list.map((element) {
final index = list.indexOf(element);
return Positioned(
top: 200 + (index * 10),
child: Transform.scale(
scale: 1 - (index / 20),
child: element,
),
);
})
]
//using sublist as we only want to show the first 3 elements
.sublist(0, 3)
//using reversed list as Stack works on Last-Come-First-Out
.reversed
.toList(),
));
}
}
Happy Coding Navjot!
You can use the ListView widget with a Card widget as the child:
ListView.builder(
itemCount: 10,
itemBuilder: (BuildContext context, int index) {
return Card(
child: ListTile(
leading: CircleAvatar(
child: Text('${index + 1}'),
),
title: Text('Title ${index + 1}'),
subtitle: Text('Subtitle ${index + 1}'),
trailing: Icon(Icons.arrow_forward),
onTap: () {
// Handle tap on list item
},
),
);
},
)
You can view the docs for this wiget here
Related
I have a ListViewBuilder inside a container in my UI when I scroll the list tiles get out of the container like so :
This my code :
return Padding(
padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 5.0),
child: Container(
margin: const EdgeInsets.only(
left: 30, right: 30, bottom: 20),
width: getProportionateScreenWidth(600),
height: getProportionateScreenHeight(300),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(17),
topRight: Radius.circular(17),
bottomLeft: Radius.circular(17),
bottomRight: Radius.circular(17)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 5,
blurRadius: 7,
offset: Offset(0, 3), // changes position of shadow
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(child: ListView.builder(
shrinkWrap: true,
physics: const ScrollPhysics(),
itemCount: snapshot.data!.perimeters.length,
itemBuilder: (context,index){
return PerimListTile(
perimID: snapshot.data!.perimeters[index].perimeterId.toString(),
perimLabel: snapshot.data!.perimeters[index].label,
);
})),
],
),
),
)
I want the list tiles to stay inside the container even while scrolling , if anyone knows how to solve the issue I'd be grateful , thank you in advance.
Problem
I do not know the details about getProportionateScreenHeight you used but I assume that it returns a double value.
The ListView inside the Column is constrained by the height of the container through that.
Solution
Remove the height of the container and try mainAxisSize: MainAxisSize.min on Column.
Solve
[1] Use the clip behavior parameter in container Widget. And set it to hardEdge
clipBehavior: Clip.hardEdge,
[2] Wrap the ListTile Widget with Card widget to make the cards inside the Container.
Card(child: ListTile());
Demo
import 'package:flutter/material.dart';
void main(List<String> args) {
runApp(MyHome());
}
class MyHome extends StatelessWidget {
const MyHome({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Ans(),
);
}
}
class Ans extends StatefulWidget {
const Ans({super.key});
#override
State<Ans> createState() => _AnsState();
}
class _AnsState extends State<Ans> {
List<String> charName = [
'Rio',
'Tokyo',
'Berlin',
'Stockhome',
'Lisbon',
'Sergio',
'Martin'
];
List<String> charGender = [
'male',
'female',
'male',
'female',
'female',
'male',
'male'
];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Tile Wrapper"),
),
body: SizedBox(
height: double.infinity,
width: double.infinity,
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(top: 100.0),
child: Container(
height: 400,
width: 300,
/// Clipping the inner View Condition
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20.2),
border: Border.all(color: Colors.black),
color: Colors.amberAccent,
),
child: ListView.builder(
itemCount: charGender.length,
itemBuilder: ((BuildContext context, int index) {
return Padding(
padding: EdgeInsets.only(top: 20.0),
//// Wrap the ListTile with Card Widget...
child: Card(
child: ListTile(
title: Text(
charName[index],
style: TextStyle(fontSize: 18.0),
),
subtitle: Text(
charGender[index],
style: TextStyle(fontSize: 18.0),
),
tileColor: Colors.brown,
leading: CircleAvatar(
radius: 20, child: Icon(Icons.person_rounded)),
),
),
);
})),
),
),
],
),
),
);
}
}
Sample
I'm trying to create a listview of cards whose images get displayed in the listview only if the card is selected. The selection widget is the PopupSubscription() where I'm choosing which cards (SubscriptionCard) to display by setting the bool of that particular image to be true. But when the selections are applied, the selected images don't appear immediately even after doing setState().
However, they do appear when I switch tabs and return the screen again. What should I do in order to change the state of an object that's not in my current state class? I tried using Provider but it left me confused as to what I'm supposed to do.
This is the SubscriptionCard where the bool is set on tapping it:
return InkWell(
radius: 1.0,
borderRadius: BorderRadius.circular(8.0),
highlightColor: buttonBackground,
onTap: () {
setState(() {
widget.currentSubscription.selected = !widget.currentSubscription.selected;
});
},
child: Card(
elevation: 1.0,
borderOnForeground: true,
shape: widget.currentSubscription.selected ? RoundedRectangleBorder(
borderRadius: BorderRadius.circular(3.0),
side: BorderSide(color: buttonBackground, width: 2.0),
) : ContinuousRectangleBorder(),
color: bgDarkColor,
child: SizedBox(
width: SizeConfig.blockSizeHorizontal * 30,
child: Stack(
alignment: Alignment.topRight,
children: [
Row(
mainAxisSize: MainAxisSize.max,
children: [
Image.asset(this.widget.currentSubscription.logo, height: 35.0,),
Text(
' ${this.widget.currentSubscription.name}',
style: TextStyle(fontFamily: 'Muli', fontSize: 16.0)
),
],
),
widget.currentSubscription.selected ? Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: buttonBackground,
),
child: Padding(
padding: const EdgeInsets.all(2.0),
child: Icon(
Icons.check,
size: 10.0,
color: Colors.white,
),
),
) : Container()
],
),
),
),
);
This is the ListView where the selected cards' images are rendered:
Container(
height: 50,
width: 350,
child: ListView(
scrollDirection: Axis.horizontal,
children: [
IconButton(
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return PopupSubscription();
}
);
},
icon: Icon(Icons.add_box_rounded, size: 30.0,),
),
StatefulBuilder(
builder: (context, setState) {
return ListView.builder(
scrollDirection: Axis.horizontal,
shrinkWrap: true,
itemCount: subscriptionsList.length,
itemBuilder: (context, index) {
return Container(
margin: EdgeInsets.symmetric(horizontal: 5.0),
child: ClipRRect(
borderRadius: BorderRadius.circular(25.0),
child: Image.asset(
subscriptionsList[index].selected ? subscriptionsList[index].logo
: "assets/elements/streaming-services/netflix.jpeg",
),
),
);
},
);
}
),
],
)
),
Based on your current code, I'm guessing you've added the currentSubscription variable as final in the StatefulWidget above the actual State, like:
class MyClass extends StatefulWidget{
final currentSubscription;
// Rest of the code
}
class _MyClassState extends State<MyClass> {
// Your ListView Code and other stuff.
}
This wont work when you want to change the state onTap, I recommend making the variable you use in setState within the _MyClassState class and use setState in that. Something like:
class _MyClassState extends State<MyClass> {
bool _isSelected = false;
// And in your onTap method, something like:
setState(() {
_isSelected = !_isSelected;
});
}
Can someone help me get this layout with the first circle over the second?
image
I have this function:
Widget overlapped() {
final overlap = 25;
final items = [
Container(
padding: EdgeInsets.only(right: 2),
decoration: new BoxDecoration(
color: Colors.blue,
shape: BoxShape.circle,
),
child: CircleAvatar(
radius: 22,
backgroundImage: AssetImage('assets/example_logo.png'),
backgroundColor: Colors.black,
),
),
CircleAvatar(
radius: 22,
backgroundColor: Colors.white,
child: ClipOval(
child: Image.network(
"https://upload.wikimedia.org/wikipedia/commons/thumb/5/53/Google_%22G%22_Logo.svg/1200px-Google_%22G%22_Logo.svg.png",
errorBuilder: (context, exception, stackTrace) {
return Container(color: Colors.white);
},
height: 35,
))),
CircleAvatar(
child: Text('+2', style: TextStyle(color: Colors.white)),
backgroundColor: Theme.of(context).canvasColor),
];
List<Widget> stackLayers = List<Widget>.generate(items.length, (index) {
return Container(
padding: EdgeInsets.fromLTRB(index.toDouble() * overlap, 0, 0, 0),
child: items[index],
);
});
return Stack(children: stackLayers);
}
This function whenever I add an item to the array, it adds a widget on the right. But I want the first to be above the second, the second of the third, etc ...
you could use the Stack widget :
Stack(
children:<Widget>:[
Positioned(
right: 130.0,
child:Container(
shape: BoxShape.circle,
)
),
Positioned(
left: 130.0,
child:Container(
shape: BoxShape.circle,
)
),
]
)
Use Stack and Positioned together.
import 'package:flutter/material.dart';
class OverLap extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Overlap'),
),
body: Container(
padding: const EdgeInsets.all(8.0),
width: 500.0,
child: Stack(
children: <Widget>[
//Change according to your icon
Icon(
Icons.flaky,
size: 50.0,
color: Colors.red,
),
Positioned(
left: 20.0,
//Change according to your icon
child: Icon(
Icons.flaky,
size: 50.0,
color: Colors.blue,
),
),
],
),
),
);
}
}
I want to have more(in this example 4) items in Row. I tried use Expanded or Flexible or mainAxisAlignment: MainAxisAlignment.spaceEvenly to fill up all width of screen. But I want to heve items lets say specific width and height 50 pixels, and GestureDetector which is around item to be 1/4 of screen(4 items = full width). So everywhere I tap in the row something will be selected. My problem is that every my solution deforms items(circles) or circle must be very huge to width = height to not be deformed.
I also tried wrapping whole _ratingEmoji in Expanded and giving ratingItem.ratingIndicator constraints to max width but with same result.
Row of items:
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
for (var ratingItem in editGroceryRatingProvider.foodRatings)
_ratingEmoji(editGroceryRatingProvider, ratingItem, context),
],
),
Item:
Widget _ratingEmoji(EditGroceryRatingProvider editGroceryRatingProvider,
SelectedFoodRating ratingItem, BuildContext context) {
return GestureDetector(
child: ratingItem.selected
? Container(
width: MediaQuery.of(context).size.width / 8,
height: MediaQuery.of(context).size.width / 8,
padding: const EdgeInsets.all(2.0),
decoration: BoxDecoration(
border: Border.all(
color: Color(0xFF312ADB),
width: 3.0,
),
borderRadius: BorderRadius.circular(32.0)),
child: Center(
child: ratingItem.ratingIndicator,
),
)
: ratingItem.ratingIndicator,
onTap: () => editGroceryRatingProvider.updateSelectedRating(ratingItem),
);
}
RatingIndicator which is "circle" item to be on the screen:
Container(
decoration: BoxDecoration(
border: Border.all(
color: color,
width: 1.5,
),
borderRadius: BorderRadius.circular(64.0),
),
child: Container(
decoration: BoxDecoration(
border: Border.all(
color: Colors.white,
width: 3.0,
),
borderRadius: BorderRadius.circular(64.0),
),
child: ClipOval(
child: Container(
width: width ?? MediaQuery.of(context).size.width / 7,
height: height ?? MediaQuery.of(context).size.width / 7,
color: color,
),
),
),
);
Thanks a lot.
Update:
Items should stay as they are on screenshot, but "whole" row should be clickable(means even if you click between 2 items, one closer to click should be selected)
Check, please solution.
The whole area is clickable. You just need to add your item instead of mine.
#override
Widget build(BuildContext context) {
final colors = [Colors.red, Colors.blue, Colors.brown, Colors.cyan];
return Scaffold(
body: Container(
alignment: Alignment.center,
color: const Color(0xffFAFAFA),
child: Row(
children: List.generate(
colors.length,
(index) => Expanded(
child: GestureDetector(
onTap: () => print('Tapped:$index'),
child: Container(
height: 50,
color: Colors.transparent,
child: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: colors[index],
),
),
)),
)),
),
),
);
I'm not sure if I'll be able to implement what I want with the way the code is set up.
I have a home screen where at the top I declared a ListView, taking information from a list.dart file.
This horizontal scrolling screen brings me 5 images and a text in each of them.
I would like to insert an onPressed directing to other screens according to the information passed in this list.
Example: Chat, direct to chat screen.
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(children: <Widget>[
Container(
width: double.infinity,
height: MediaQuery.of(context).size.height * 4 / 7,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Color(0xff40dedf), Color(0xff0fb2ea)],
),
),
),
Positioned(
top: 100,
left: 20,
child: Container(
height: 100,
width: MediaQuery.of(context).size.width,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: categoryData.length,
itemBuilder: (context, index) {
bool isSelected = true;
if (index == 0) {
isSelected = true;
}
return Row(
children: <Widget>[
Column(
children: <Widget>[
Container(
width: 65,
height: 65,
decoration: BoxDecoration(
color: isSelected
? Colors.transparent
: Colors.transparent,
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: Colors.white,
width: 1,
),
boxShadow: isSelected
? [
BoxShadow(
color: Color(0x14000000),
blurRadius: 10)
]
: null),
child: Center(
child: Image.asset(categoryData[index].imageUrl),
),
),
SizedBox(
height: 10,
),
Text(
categoryData[index].name,
style: TextStyle(color: Colors.white, fontSize: 15),
),
],
),
SizedBox(
width: 20,
)
],
);
},
),
),
),
]));
}
}
This is the List.dart from which you get the information:
class MyList {
final String name;
final String count;
final String imageUrl;
MyList({this.imageUrl, this.name, this.count});
}
And this is the variable used for configuration:
List<MyList> categoryData = [
new MyList(imageUrl: "assets/page1/usuario.png", name: "Perfil", count: "1"),
new MyList(
imageUrl: "assets/page1/entregas.png", name: "Entregas", count: "2"),
new MyList(imageUrl: "assets/page1/msg.png", name: "Chat", count: "3"),
new MyList(
imageUrl: "assets/page1/configurações.png",
name: "Configuração",
count: "4"),
new MyList(imageUrl: "assets/page1/sair.png", name: "Sair", count: "5"),
];
You can make use of a GestureDetector as a parent for each Container you've created to be a button. The documentation shows that the GestureDetector can have an onTap function declared, where you can define your own routing logic.
You could then have a switch case created to determine to which screen the application needs to route, or even make use of the name parameter of your categoryData variable.
ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: categoryData.length,
itemBuilder: (context, index) {
bool isSelected = true;
if (index == 0) {
isSelected = true;
}
return Row(
children: <Widget>[
Column(
children: <Widget>[
// Here you add a GestureDetector
GestureDetector(
onTap: () {
// Here you add your navigation logic
},
child: Container(
width: 65,
height: 65,
decoration: BoxDecoration(
color: isSelected
? Colors.transparent
: Colors.transparent,
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: Colors.white,
width: 1,
),
boxShadow: isSelected
? [
BoxShadow(
color: Color(0x14000000),
blurRadius: 10)
]
: null),
child: Center(
child: Image.asset(categoryData[index].imageUrl),
),
),
),
SizedBox(
height: 10,
),
Text(
categoryData[index].name,
style: TextStyle(color: Colors.white, fontSize: 15),
),
],
),
SizedBox(
width: 20,
)
],
);
},
),
Note: You should always name your class variables UpperCamelCase in Flutter. Try changing the list class name to something more declarative ;)