How to manipulate only one element of ListView.builder - flutter

I am using Animated Container to have a closing animation by manipulating the _height character of the animated container. I am using ListView.builder to create cards , with each having text that is a value in a list (the user can manipulate the list by adding and deleting items). The integrated GestureDetector looks for a double tap and removes an item of the list and manipulates the height. However , the problem is that , all the cards have their height manipulated and disapper.
How do we have the animated height manipulation occur only for the card the user double taps?
The listview.builder code is as follows :
ListView.builder(
padding: EdgeInsets.fromLTRB(0, 0, 0, 100),
shrinkWrap: true,
scrollDirection: Axis.vertical,
itemCount: items.length,
itemBuilder: (context, index) {
return AnimatedContainer(
duration: Duration(seconds: 2),
alignment: Alignment.center,
decoration: new BoxDecoration(
color: Color(0xFF324467),
shape: BoxShape.rectangle,
borderRadius: BorderRadius.all(Radius.circular(100.0)),
),
margin: EdgeInsets.fromLTRB(22, 15, 50, 9),
height: _height ? 80 : 0,
child: Stack(
children: <Widget>[
GestureDetector(
onDoubleTap: () {
setState(() {
_height = !_height;
});
setState(() {
items.removeAt(index);
});
},
child: Card(
color: Color(0xFF324467),
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
child: Row(
children: [
SizedBox(
height: 30,
width: 63,
child: RaisedButton(
onPressed: () {
setState(() {
_height = !_height;
});
setState(() {
items.removeAt(index);
});
},
elevation: 0,
color: Colors.transparent,
shape: CircleBorder(
side: BorderSide(
width: 2.3,
color: Colors.blue,
),
),
),
),
Padding(padding: EdgeInsets.fromLTRB(0, 0, 7, 0)),
Flexible(
child: Container(
child: Text(
items[index],
overflow: TextOverflow.fade,
style: TextStyle(
fontSize: 21,
fontWeight: FontWeight.w400,
color: Colors.white,
),
),
),
),
],
),
),
),
],
),
);
},
),

The problem is with height: _height ? 80 : 0, this line. _height is a variable which is being checked against all the list item. if you set _height = false all your item list will be gone.
Use AnimatedList for remove/add animation in the list. The page has reference code lab, which uses an AnimatedList to create an effect when items are removed or added to the list.
If you want solution in your current approach, then store the _height value against every list item. You can use Map<objectId,bool> or add a bool variable in your current model. then check the item status. Hope you will get the idea ;-)

Related

How do I get a black banner with text to dynamically show depending upon what a user does on another screen in Flutter?

I am tasked with the challenge of getting a black banner to show when a user returns from a screen on that same tab upon ordering an item on another screen. The current code below only shows the black banner appropriately if the user leaves by selecting another tab then returns to the screen. The part that I need to be dynamically presented starts with "if (OrderProvider.listOrderSummary.isNotEmpty)". I believe that this involves making a stateless widget stateful, but that results in errors that suggest that may not be the right approach.
class GridItems extends StatelessWidget {
GridItems({#required this.infinityPageController});
final InfinityPageController infinityPageController;
#override
Widget build(BuildContext context) {
final List<Item> itemList = Provider.of<List<Item>>(context);
if (itemList == null) {
return Center(
child: CupertinoActivityIndicator(
radius: 20,
),
);
}
final List<Item> mapItemList = itemList.toList();
return Column(
children: <Widget>[
TopMenuNavigation(
infinityPageController: infinityPageController,
title: 'ALL ITEMS',
),
Divider(
color: Colors.grey,
),
Expanded(
child: GridView.builder(
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
itemCount: itemList.length,
itemBuilder: (BuildContext contex, int index) {
return Center(
child: InkWell(
onTap: () {
Navigator.of(context)
.pushNamed('/order', arguments: mapItemList[index]);
},
child: Container(
width: 310,
margin: EdgeInsets.all(7),
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(width: 1, color: Colors.grey),
borderRadius: BorderRadius.all(
Radius.circular(7.0),
),
),
child: Column(
children: <Widget>[
Container(
padding: EdgeInsets.only(left: 10, top: 10),
width: double.infinity,
child: Text('${mapItemList[index].itemName}'),
),
Expanded(
child: Image.network(
'${mapItemList[index].imageUrl}',
),
),
],
),
),
),
);
},
),
),
if (OrderProvider.listOrderSummary.isNotEmpty)
GestureDetector(
onTap: () {
Navigator.of(context).pushNamed('/review_order');
},
child: Container(
height: 55,
color: Colors.black,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Spacer(),
Text(
'REVIEW ORDER (${OrderProvider.listOrderSummary.length} items)',
style: TextStyle(
fontSize: 17,
color: Colors.white,
fontFamily: 'OpenSans',
fontWeight: FontWeight.bold),
),
SizedBox(width: 8),
Icon(
Icons.navigate_next,
color: Colors.white,
size: 35,
),
SizedBox(
width: MediaQuery.of(context).size.height * 0.065,
),
],
),
),
)
else
SizedBox(),
],
);
}
}
You have to somehow rebuild your screen (or part of it) where you want to return to. This is where a state-management solution should come into place :).
Create a BLoC or a ValueNotifier and rebuild the widget with a StreamBuilder or a ValueListenableBuilder. If you want to make it simple just create a shared state inside a stateful widget between your tab screens and call setState if the black banner should appear

Why is Gridview.count not filling out the space with my dynamic content

I am trying to create a gridview starting from the second column. The first card is just a static card with a button in it. So the second card starting should be dynamic.
All the cards have the same width and height. So basically they all should look like the first card (Add a new dog)
But it's not filling out the space as I expected it would.
Here is part of my code from the body section:
body: Stack(fit: StackFit.expand, children: [
//bg image
Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage(Images.bgYellow), fit: BoxFit.cover)),
),
//content
SafeArea(
bottom: false,
left: true,
right: true,
top: false,
child: Padding(
padding: EdgeInsets.all(3 * SizeConfig.safeBlockHorizontal),
child: GridView.count(
crossAxisCount: 2,
children: [
//add card
Container(
margin: EdgeInsets.symmetric(
vertical: 1 * SizeConfig.blockSizeVertical,
horizontal: 2 * SizeConfig.blockSizeHorizontal),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(30),
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 8,
offset: Offset(
0, 2), // changes position of shadow
),
],
),
child: FlatButton(
onPressed: null,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
const IconData(0xe901,
fontFamily: 'icDog'),
color: muddyBrown,
size: 20 * SizeConfig.safeBlockHorizontal,
),
SizedBox(height: 5),
Text(
"ADD A NEW DOG",
style: TextStyle(
color: muddyBrown,
fontWeight: FontWeight.bold,
fontSize: 4 *
SizeConfig.safeBlockHorizontal),
)
],
)),
),
//dynamic content
StateBuilder<PetState>(
observe: () => _petStateRM,
builder: (context, model) {
return Column(
children: [
...model.state.pets.map((pet) =>
GestureDetector(
onTap: () {
Navigator.pushNamed(
context, petDetailRoute);
},
child: Container(
margin: EdgeInsets.symmetric(
vertical: 1 *
SizeConfig
.blockSizeVertical,
horizontal: 2 *
SizeConfig
.blockSizeHorizontal),
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(30),
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.grey
.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 8,
offset: Offset(0,
2), // changes position of shadow
),
],
),
child: Column(
mainAxisAlignment:
MainAxisAlignment.center,
//dynamic data => Photo + Name
children: [
Container(
width: 100.0,
height: 100.0,
decoration: new BoxDecoration(
color:
const Color(0xff7c94b6),
image: new DecorationImage(
image: new NetworkImage(
"${pet.photo}"),
fit: BoxFit.cover,
),
borderRadius:
new BorderRadius.all(
new Radius.circular(
50.0)),
border: new Border.all(
color: muddyBrown,
width: 4.0,
),
),
),
SizedBox(height: 5),
Text(
"${pet.name}",
style: TextStyle(
fontSize: 4 *
SizeConfig
.safeBlockHorizontal,
color: muddyBrown,
fontWeight:
FontWeight.bold),
)
],
),
),
))
],
);
}),
],
)
)),
]));
What about simply adding the 'static' card to the 'dynamic' ones and then build one GridView with all of them together?
Widget newDogButton = Card(...);
//dynamic content
StateBuilder<PetState>(
observe: () => _petStateRM,
builder: (context, model) {
return Column(
children: [
newDogButton,
...model.state.pets.map((pet) => // ...
That should take care of most of your layout issues automatically.
Because StateBuilder return a widget. How about moving the Whole GridView inside it?
...
child: Padding(
padding: EdgeInsets.all(3 * SizeConfig.safeBlockHorizontal),
child: StateBuilder<PetState>(
observe: () => _petStateRM,
builder: (context, model) {
return GridView.count(
crossAxisCount: 2,
children: [
// The button "ADD A NEW DOG" here,
Container(...),
//dynamic content here
...model.state.pets.map((pet) =>
...
).toList(),
},
),
),
Every grid item in GridView will have same height and width, if you want different dynamic height or width for different items, use flutter_staggered_grid_view, in your case:
StaggeredGridView.countBuilder(
crossAxisCount: 2,
itemCount: 2,
itemBuilder: (BuildContext context, int index) => new Container(
color: Colors.green,
child: new Center(
child: new CircleAvatar(
backgroundColor: Colors.white,
child: new Text('$index'),
),
)),
staggeredTileBuilder: (int index) =>
new StaggeredTile.count(1, index.isEven ? 2 : 1),
mainAxisSpacing: 4.0,
crossAxisSpacing: 4.0,
)
The situation:
To me, this problem is not coming from your Dynamic content but from the height allowed to it to display it's content. Inside the GridView.count constructor, the default childAspectRatio value is 1.0, meaning that the default max height of each child of the GridView is deviceWidth / crossAxisCount (2 in your case).
The problem:
In order for each child to display correctly, it's height must not exceed this ratio (causing your overflowed error).
My opinion:
To solve this problem, I will either replace the dynamic content StateBuilder<PetState> with a static Widget which height will not exceed the ratio OR wrap the dynamic content StateBuilder<PetState> in a SingleChildScrollView to ensure that the overflowed error will not happen and the wrapper can produce the scroll effect to see the entire dynamic content.

How to put onpress on each item of a BoxDecoration pulled from a list view

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 ;)

how to make different layout for tablet in flutter

I'm thinking about making different layout for Tablet/iPad version but I'm new to Flutter so I'm not really sure how to make it. I'll be really appreciated if I can get any suggestion on how to do it.
Right now If I run on Tablet, my cards are stretch out and I'm trying to fix it right now. Also I'm trying to add a divider and column on the right side (attached pic of the design below) so that I can add more text.
This is my cards
Widget build(BuildContext context) {
return Container(
height: 180,
child: SingleChildScrollView(
child: Card(
elevation: 8.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
child: Column(children: [
Padding(
padding: const EdgeInsets.fromLTRB(10, 10, 10,0),
child: Row(children: [
Text(
title,
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
Spacer(),
Container(
child: IconButton(
icon: Icon(Icons.favorite),
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(builder: (context)=> DetailsPage("Favorite")));
},
))
]),
),
Divider(color: Colors.black),
Row(children: [
//Legend
tileWidget(TileItem(
topText: "Top",
middleText: "Middle",
bottomText: "Bottom")),
Container(
color: Colors.red,
height: 60, width: 2),
(tileItems.length < 1) ? null : tileWidget(tileItems[0]),
Container(
color: Colors.green,
height: 60, width: 2),
(tileItems.length < 2) ? null : tileWidget(tileItems[1]),
Container(
color: Colors.black26, height: 60, width: 2),
(tileItems.length < 3) ? null : tileWidget(tileItems[2])
]),
]),
),
),
);
}
To get the screen width, use MediaQuery.of(context).size.width
Make a widget for any width under 600dp (ie. the phone) and another for any width above (ie. the tablet).
Then your code will look something like this:
body: OrientationBuilder(builder: (context, orientation) {
if (MediaQuery.of(context).size.width > 600) {
isTablet= true;
} else {
isTablet= false;
} etc...
}
Here is a helpful resource: https://medium.com/flutter-community/developing-for-multiple-screen-sizes-and-orientations-in-flutter-fragments-in-flutter-a4c51b849434

Flutter animation on grid expand

I want animation on grid expand. At first gird shows 4 items only and later on click button grid shows all item i.e more than 4 (with an animation). In a row there are 4 items.
bool click = false;
return Container(
width: double.infinity,
child: GridView.builder(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
gridDelegate:
new SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 4),
itemBuilder: (BuildContext context, int index) {
return GestureDetector(
onTap: () {
if (index % 3 == 0) {
Navigator.of(context).push(SlideLeftRoute(
widget: Search(
"Restaurant",
isSearchFromTextField: false,
list: list,
)));
} else {
Navigator.of(context)
.push(SlideLeftRoute(widget: RestaurantDetail()));
}
},
child: Container(
child: Column(
children: <Widget>[
Container(
padding: EdgeInsets.all(10),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
color: HexColor(widget._homeScopedModel.homePageModel
.category[index].color),
),
child: SvgPicture.asset(
'assets/images/restaurant.svg',
width: 30,
height: 30,
color: CustomColor.white,
),
),
SizedBox(
height: 10,
),
Text(
widget._homeScopedModel.homePageModel.category[index].name,
style: TextStyle(
color: CustomColor.black,
fontSize: 12.0,
fontFamily: 'SFProDisplay',
fontWeight: FontWeight.w400,
),
textAlign: TextAlign.center,
),
],
),
),
);
},
itemCount:
click ? widget._homeScopedModel.homePageModel.category.length : 4,
),
);
//on click image
new GestureDetector(
child: RotatedBox(
quarterTurns: click ? 2 : 0,
child: SvgPicture.asset(
'assets/images/icon_down.svg',
width: 15,
height: 15,
color: CustomColor.grey,
),
),
onTap: () {
click = !click;
setState(() {});
},
),
On Click image the grid view will expand. I want slide down and slide up animation on click image. I tried Animated Container but it doesn't work for me. I found animation only on expanding other view like below. how to animate collapse elements in flutter
I understood your question. I think you should try to use expansionTile, expandable and there are much more things like that. Whether you want you can create your own animating expandable widget with animations in stateful widget