Helllo
I have a Product Category screen on my Flutter App. I have added a IconButton for animation for 'Add To Cart' action. But if you click add to cart icon on product image, all icons are animating. I want to animate only one icon. Can you help me?
IconButton(
icon: AnimatedSwitcher(
duration: const Duration(milliseconds: 350),
transitionBuilder: (child, anim) => RotationTransition(
turns: child.key == ValueKey('icon1')
? Tween<double>(begin: 1, end: 0.75).animate(anim)
: Tween<double>(begin: 0.75, end: 1).animate(anim),
child: ScaleTransition(scale: anim, child: child),
),
child: _currIndex == 0
? Icon(Icons.close, key: const ValueKey('icon1'))
: Icon(
Icons.arrow_back,
key: const ValueKey('icon2'),
)),
onPressed: () {
setState(() {
_currIndex = _currIndex == 0 ? 1 : 0;
});
},
),
I think the issue is because you are using _currIndex in each IconButton, so when you change one you make setState to change the value then every product gets affected by this change.
what you have to do is separate each product from the other...
one way is to make _currIndex a local variable if you use ListView put the variable in its scope but I doubt this works
the other way is to make a list of Booleans of size equal to the number of products and you can check if the _currIndex of a product is 0 or 1 by accessing the List with the index of the ListView.
so it will look like this:
// at the beginning of the file you will have a list of booleans with all values set to zero by doing this
List<bool> isInCart = List.filled(numberOfProducts, false);
// then you will do your code but with the list not the _currIndex
IconButton(
icon: AnimatedSwitcher(
duration: const Duration(milliseconds: 350),
transitionBuilder: (child, anim) => RotationTransition(
turns: child.key == ValueKey('icon1')
? Tween<double>(begin: 1, end: 0.75).animate(anim)
: Tween<double>(begin: 0.75, end: 1).animate(anim),
child: ScaleTransition(scale: anim, child: child),
),
// if the element is true then this product is in the cart otherwise its not
child: isInCart[index]
? Icon(Icons.close, key: const ValueKey('icon1'))
: Icon(
Icons.arrow_back,
key: const ValueKey('icon2'),
)),
onPressed: () {
setState(() {
// you just invert the value here
isInCart[index] = !isInCart[index];
});
},
),
Related
I have a list of report items.
I initialised a scrollController here:
class ReportList extends StatelessWidget {
ReportList({Key? key}) : super(key: key);
final ScrollController scrollController = ScrollController();
scrollDown() {
scrollController.animateTo(scrollController.position.maxScrollExtent,
duration: const Duration(seconds: 1), curve: Curves.bounceOut);
}
I also have a list view with the controller, and I want the list view to stroll to bottom when the last item is selected:
ListView.separated(
controller: scrollController,
itemCount: 13,
itemBuilder: (_, index) {
return Column(
children: [
ReportItem(
onClick: () {
viewModel.setCommentReportStat(index);
if (index == 12 && viewModel.commentReportStat[12]) {
scrollDown();
}
debugPrint(viewModel.commentReportStat.toString());
},
isSelected: viewModel.commentReportStat[index],
title: CommentReport.items[index],
),
//show additional message field to the user
if (index == 12 && viewModel.commentReportStat[12])
AnimatedContainer(
duration: const Duration(seconds: 1),
child: MessageField(commentId: commentId),
)
],
);
},
separatorBuilder: (_, index) => const SizedBox(
height: 16,
),
)
Everything about the state management works since the message field appears as the user clicks on the last item.
My problem in general is that neither does message field appear with the animated container which is wrapped around it, nor does it scroll down!
How it is right now:
What I want to happen:
(If I scroll manually, I can see the entire message field, but I want this to be automated using the scroll controller)
instead of this
if (index == 12 && viewModel.commentReportStat[12])
AnimatedContainer(
duration: const Duration(seconds: 1),
child: MessageField(commentId: commentId),
)
use this
AnimatedCrossFade(
duration: const Duration(seconds: 1),
firstChild: const SizedBox(),
secondChild: MessageField(commentId: commentId),
crossFadeState: (index == 12 && viewModel.commentReportStat[12]) ? CrossFadeState.showSecond : CrossFadeState.showFirst,
)
You can also use this function if you want to scroll to the end of the page when an event occurs.
toMaxScroll() {
_scrollController.animateTo(_scrollController.position.maxScrollExtent,duration:const Duration(milliseconds:300),curve:Curves.easeIn);
}
I have a scaffold and on the leading of AppBar a custom back button.
I have a int refIndex that initially is 0, but changes to the same value as the index of Pageview.builder changes
I want the backbuttnn to disappear if the refIndex > 0. I have no idea why its not working.
Widget leadingWiget() {
if (refIndex == 0) {
return IconButton(
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
icon: const Icon(
Icons.chevron_left,
color: Colors.black,
size: 40,
),
onPressed: () {
// Navigator.of(context).pop();
print(refIndex);
},
);
}
return const SizedBox(height: 0, width: 0);
}
EDIT: I have the var declared right after the class class _QuestionsPageState extends State<QuestionsPage> {
in Pageview.builder, after itemBuilder: (ctx, index) { I have refIndex = index;
Is it wrong? I'm facing the same problem if I want to display the refIndex. The value itself changes when I change pages, but the displayed is the same as the initial, 0.
You can use the Visibilty Widget.
For example:
Visibilty(
visible: refIndex == 0,
child: IconButton(...),
)
I have a dialog box which is a stateful widget with multiple tabs wrapped inside Animated Switcher.
Inside it I have a button which on clicked calls a function switchPage() which has a switch statement with each case setting the state of Widget? currentTab variable to a different one.
The problem here arrives when I use this switchPage() function to change the value of currentTab to a different widget also wrapped in a different method getWidget2()
The code for the
Example on DartPad
Try clicking as I suggest...
Click Floating Button.
Click on the first checkbox.
Now click on PAGE 2 button.
Click the second checkbox only once. Now notice the checkbox doesn't work when clicked.
Click PAGE 1 again to go the working checkbox.
Now click on PAGE 2 button again. The Checkbox value and state did change but did not update the time it was clicked, but it has updated when we forcefully re visited the checkbox.
I cannot find solution to this anywhere and I really need the code structure to be as optimized as possible.
Please, if anyone has any explanation or any suggestions, it would be greatly appreciated..
Thanks, in advance.
This is a very rough working example, but you can build upon it.
In the code below, you can see that I have created two methods that handle setting the state of the checkbox for each widget. I have also reset the page once this method is triggered. What this does, is trigger the redrawing of the inner widgets (which is what I explained in my comment).
class _DemoDialogState extends State<DemoDialog> {
Widget? currentTab;
bool valueOfCheckbox1 = false;
bool valueOfCheckbox2 = false;
void switchPage(name) {
switch (name) {
case 1:
setState(() {
currentTab = getWidget1(setCheckbox1State); // <---- Notice the change here
});
break;
case 2:
setState(() {
currentTab = getWidget2(setCheckbox2State); // <---- Notice the change here
});
break;
}
}
void setCheckbox1State(bool? newState) {
if (newState != null) {
setState(() { // <---- Notice the change here
valueOfCheckbox1 = newState;
currentTab = getWidget1(setCheckbox1State);
});
}
}
void setCheckbox2State(bool? newState) {
if (newState != null) {
setState(() { // <---- Notice the change here
valueOfCheckbox2 = newState;
currentTab = getWidget2(setCheckbox2State);
});
}
}
Widget getWidget1(Function(bool?) checkboxFunction) {
return Container(
child: Row(
children: [
Text('Hello from widget 1'),
Checkbox(
value: valueOfCheckbox1,
onChanged: (value) { // <---- Notice the change here
checkboxFunction(value);
})
],
));
}
Widget getWidget2(Function(bool?) checkboxFunction) {
return Container(
child: Row(
children: [
Text('Hello from widget 2'),
Checkbox(
value: valueOfCheckbox2,
onChanged: (value) { // <---- Notice the change here
checkboxFunction(value);
})
],
));
}
#override
Widget build(BuildContext context) {
return Dialog(
child: Container(
width: 280,
height: 600,
color: Colors.white,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
AnimatedSwitcher(
duration: Duration(milliseconds: 250),
reverseDuration: Duration(milliseconds: 250),
transitionBuilder: (child, animation) {
var begin = Offset(0.5, 0);
var end = Offset.zero;
var curve = Curves.easeIn;
var tween = Tween(begin: begin, end: end)
.chain(CurveTween(curve: curve));
var begin2 = 0.0;
var end2 = 1.0;
var curve2 = Curves.easeIn;
var tween2 = Tween(begin: begin2, end: end2)
.chain(CurveTween(curve: curve2));
return SlideTransition(
position: animation.drive(tween),
child: FadeTransition(
opacity: animation.drive(tween2), child: child),
);
},
layoutBuilder: (widget, list) {
return Align(
alignment: Alignment.topCenter,
child: widget,
);
}, // <---- Notice the change here
child: currentTab == null ? getWidget1(setCheckbox1State) : currentTab,
),
TextButton(
onPressed: () {
switchPage(1);
},
child: Text('PAGE 1')),
TextButton(
onPressed: () {
switchPage(2);
},
child: Text('PAGE 2'))
],
),
),
);
}
}
This is just an example that makes things work, but it in no way represents how you should build things appropriately. I would look into separating the code into stateless and stateful widgets.
i have a SliverAnimatedList like this :
SliverAnimatedList(
key: _myListkey,
itemBuilder: (context, index, animation) {
return Container(
child: Column(
children: [
FlashcardCreateTile(
autocreate: autocreate,
entertomovefocus: entertomovefocus,
flashcard: flashcards[index],
islast:
(index + 1) == flashcards.length ? true : false,
plusmode: true,
promode: true,
uid: widget.uid,
focus: null,
animation: animation,
formKey: _formkey,
delete: () {
flashcards.removeAt(index);
SliverAnimatedList.of(context).removeItem(
index,
(context, animation) => FlashcardCreateTile(
autocreate: autocreate,
entertomovefocus: entertomovefocus,
flashcard:
Flashcard(recto: "", verso: ""),
islast: false,
plusmode: true,
promode: true,
uid: widget.uid,
focus: null,
animation: animation,
formKey: _formkey,
delete: () {},
add: () {}),
duration: const Duration(milliseconds: 100));
},
add: () {
int insertitem = index + 1;
print(insertitem);
setState(() {
flashcards.insert(
insertitem,
Flashcard(
recto: "",
verso: "",
mode: 0,
isrelearning: false,
easefactor: widget
.folder
.decklist[widget.deckindex]
.startingEase,
currentInterval:
Duration(microseconds: 0),
previousInterval:
Duration(microseconds: 0)));
SliverAnimatedList.of(context)
.insertItem(insertitem);
SliverAnimatedList.of(context).build(context);
});
},
),
Container(
child: (index + 1) == flashcards.length
? Container(
child: SizedBox(
height: 50,
),
)
: Container(),
)
],
),
);
},
initialItemCount: flashcards.length,
)
The flashcardcreatetile sends back the add function when i click and a button :
IconButton(
icon: Icon(
Icons.add,
color: Colors.red,
),
onPressed: widget.add)
Here's what it's doing :
As you can see, the item is indeed inserted, but the sliveranimatedlist only shows it when i scroll down and back up, so i presume it needs to rebuild itself..
I would like the new card to show directly, any ideas? Remove item is working fine by the way
You need to add a key to your item lists. I recommend you read this article as you will learn why do you need keys, what are they good for, and how to fix your problem.
You should add a key: Key(index) to your FlashcardCreateTile items to make them unique.
Flutter engine needs that to properly build your list
I have a grid of boxes using the Wrap widget (I did not use the GridView as this requires you stating how many items you need in each row ahead of time).
I want to remove the item when they are clicked, and have all the other items animate to their new position like this: https://vestride.github.io/Shuffle/adding-removing (click on the boxes to see what I mean).
Here is my code so far without any animation:
class Boxes extends StatefulWidget {
#override
_BoxesState createState() => _BoxesState();
}
class _BoxesState extends State<Boxes> {
var items = [
{'id': '25', 'name': 'A',},
{'id': '19', 'name': 'B',},
{'id': '35', 'name': 'C',},
{'id': '20', 'name': 'D',},
{'id': '958', 'name': 'E',},
{'id': '1278', 'name': 'F',},
{'id': '500', 'name': 'G',},
];
Widget build(BuildContext context) {
return Container(
width: double.infinity,
height: double.infinity,
child: Wrap(
alignment: WrapAlignment.center,
children: [
for (final item in items)
Box(
key: Key(item['id']),
name: item['name'],
onDelete: () {
setState(() {
items.remove(item);
});
},
)
],
),
);
}
}
class Box extends StatelessWidget {
String name;
Function onDelete;
Box({this.name, this.onDelete, Key key}):
super(key: key);
Widget build(BuildContext context) {
return GestureDetector(
onTap: onDelete,
child: Container(
color: Colors.lightBlue,
width: 90,
height: 90,
margin: EdgeInsets.all(8),
child: Center(
child: Text(name),
)
),
);
}
}
The closest built-in widget I could find is AnimatedList, but that does not work with a grid.
I also tried animating the width of the deleted box to 0, which did not work as the other boxes just jump into position instead of animating to the new position.
How would I go about doing this?
Simple but effective:
Create an async Function that handles deletes and declares wait
delays
Call the delete function to handle independent deletes on each element
Animate using an AnimatedContainer and an AnimatedOpacity based on the state
Here is a working example using Chips as widgets:
Note: I'll leave for you to decide the max width of the Custom Widget "Box" you will use and if needed calculate its dynamic width depending on its content
//declare an empty list that handles items to be deleted
List<String> deleteItems = [];
//define async Function that handles progressive deletes from the list
void deleteItem(String id) async {
setState(() {
deleteItems.add(id);
});
Future.delayed(Duration(milliseconds: 250)).whenComplete(() {
setState(() {
deleteItems.removeWhere((i) => i == id);
items.removeWhere((i) => i["id"] == id);
});
});
}
//Widget to be used to animated the List/Wrap
return SizedBox.expand(
child: Wrap(
alignment: WrapAlignment.center,
children: List.generate(items.length, (index) {
var item = items[index];
bool isMarkedForDelete =
deleteItems.where((i) => i == item["id"]).isNotEmpty;
return AnimatedContainer(
key: ObjectKey(item),
duration: Duration(milliseconds: 250),
//alignment: Alignment.centerLeft,
width: isMarkedForDelete ? 0 : 60, //change depending on font size or content
child: AnimatedOpacity(
duration: Duration(milliseconds: 250),
opacity: isMarkedForDelete ? 0 : 1,
child: Chip(
label: Text("${item["name"]}"),
backgroundColor:
isMarkedForDelete ? Colors.red : Colors.blue,
deleteIcon: Icon(Icons.close),
onDeleted: () {
if(!isMarkedForDelete) deleteItem(item["id"] ?? "");
})));
}),
));
Geetings.
You could give a try to use AnimatedContainer
I basically change the width from 100 to 0, when the tap event occurs.
I also change the list items a little.
AnimatedContainer detects that the property width changed and fires the animation.
Please try something like this (is not the same as your reference)
I commented the items.remove(item), you need to find a way to really remove the item once the animation is finished (maybe with a timer or future).
List<Map<String,dynamic>> items = [
{'id':'11','name':'A','width':100},
{'id':'12','name':'B','width':100},
{'id':'13','name':'C','width':100},
{'id':'14','name':'D','width':100},
{'id':'15','name':'E','width':100},
{'id':'16','name':'F','width':100},
{'id':'17','name':'G','width':100},
];
Widget build(BuildContext context) {
return Scaffold(body: Container(
width: double.infinity,
height: double.infinity,
child: Wrap(
alignment: WrapAlignment.center,
children: [
for (final item in items)
AnimatedContainer(
width: double.parse(item['width'].toString()),
duration: Duration(milliseconds: 600),
curve: Curves.easeInBack,
child: Box(
key: Key(item['id']),
name: item['name'],
onDelete: () {
setState(() {
//items.remove(item);
item['name'] = "";
item['width'] = 0;
});
},
),
)
],
),
));
}
Flutter video --> https://www.youtube.com/watch?v=yI-8QHpGIP4
You can use AnimatedList
Widget's video:
https://www.youtube.com/watch?v=ZtfItHwFlZ8
Some docs:
https://api.flutter.dev/flutter/widgets/AnimatedList-class.html