Closed Gastrolize closed 2 years ago
@Gastrolize The code you shared isn't runnable because it contains custom code and imports. In order to address this issue properly, please provide a runnable but complete minimal reproducible code sample that we can directly use and verify the behavior you are reporting. Also provide actual vs expected result upon running your code sample.
Thanks.
Hello yes sorry. Here a provided code without custom imports:
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'dart:math' as math;
class TestList extends StatefulWidget {
@override
_TestList createState() => _TestList();
}
class Cart {
final String index;
Cart({Key? key,required this.index});
}
class _TestList extends State<TestList> {
List<Cart> one = [
Cart(index: "1",key: UniqueKey()),
Cart(index: "2",key: UniqueKey()),
Cart(index: "3",key: UniqueKey()),
];
List<Cart> two = [];
GlobalKey<AnimatedListState> first = GlobalKey<AnimatedListState>();
GlobalKey<AnimatedListState> second = GlobalKey<AnimatedListState>();
putOver(int index,int list) async {
if(list == 0){
var item = one[index];
two.add(one[index]);
first.currentState?.removeItem(index, (context, animation) => ScaleThenSlideTransitionContinue(key:ObjectKey(item),animation: animation,
child: Card(key: ObjectKey(item),index: index,name: item.index,putOver: (_) => putOver(index, 0))));
second.currentState?.insertItem(0,duration: const Duration(milliseconds: 800));
one.removeAt(index);
} else if(list == 1){
var item = two[index];
one.add(two[index]);
second.currentState?.removeItem(index, (context, animation) => ScaleThenSlideTransitionContinue(key:ObjectKey(item),animation: animation,child: Card(key:ObjectKey(item),index: index,name: item.index,putOver: (_) => putOver(index, 1))));
first.currentState?.insertItem(0,duration: const Duration(milliseconds: 800));
two.removeAt(index);
}
setState(() {
});
}
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
backgroundColor: Colors.black,
body: Row(
children: [
Flexible(flex: 1, child: Container(
height: MediaQuery.of(context).size.height,
decoration: BoxDecoration(
color: Colors.white,
),
clipBehavior: Clip.antiAliasWithSaveLayer,
child: AnimatedList(
key: first,
initialItemCount: one.length,
itemBuilder: (context,index,animation){
return ScaleThenSlideTransition(key:ObjectKey(one[index]),child: Card(key:ObjectKey(one[index]),index: index,name: one[index].index,putOver: (_) => putOver(index, 0)),animation: animation,);
},
),
)),
SizedBox(width: 10,),
Flexible(flex: 1, child: Container(
height: MediaQuery.of(context).size.height,
decoration: BoxDecoration(
color: Colors.white,
),
clipBehavior: Clip.antiAliasWithSaveLayer,
child: AnimatedList(
key: second,
initialItemCount: two.length,
itemBuilder: (context,index,animation){
return ScaleThenSlideTransition(key:ObjectKey(two[index]),child: Card(key:ObjectKey(two[index]),index: index,name: two[index].index,putOver: (_) => putOver(index, 1)),animation: animation,);
},
),
)),
],
),
);
}
}
class Card extends StatefulWidget {
final int index;
final Function(int) putOver;
final String name;
const Card({Key? key, required this.index,required this.putOver,required this.name}) : super(key: key);
@override
_Card createState() => _Card();
}
class _Card extends State<Card> with SingleTickerProviderStateMixin, AutomaticKeepAliveClientMixin{
bool wasSet = false;
getColor(){
return wasSet ? Colors.blue : Colors.green;
}
@override
void initState() {
super.initState();
}
void putOver() async {
setState(() {
wasSet = true;
});
}
@override
void didUpdateWidget(covariant Card oldWidget) {
if(widget.name != oldWidget.name){
super.didUpdateWidget(oldWidget);
}
}
void putOverr() async {
widget.putOver(widget.index);
}
@override
Widget build(BuildContext context) {
// TODO: implement build
return GestureDetector(
onTap: () => putOver(),
onDoubleTap: () => putOverr(),
child: Container(
height: 100,
color: getColor(),
child: Center(
child: Text(widget.name.toString(),style: TextStyle(fontSize: 30,color: Colors.white),),
),
),
);
}
@override
bool get wantKeepAlive => true;
}
@immutable
class ScaleThenSlideTransition extends StatelessWidget {
final Animation<double> animation;
final Widget child;
const ScaleThenSlideTransition({
Key? key,
required this.animation,
required this.child,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return SlideTransition(
position: animation
.drive(CurveTween(curve: const Interval(0.5, 1)))
.drive(CurveTween(curve: Curves.easeOutQuint))
.drive(Tween<Offset>(
begin: const Offset(-1.0, 0.0),
end: const Offset(0.0,0.0),
)),
child: SizeNoClipTransition(
axis: Axis.vertical,
sizeFactor: animation
.drive(CurveTween(curve: const Interval(0, 0.5)))
.drive(CurveTween(curve: Curves.easeInOut)),
child: child,
),
);
}
}
@immutable
class ScaleThenSlideTransitionContinue extends StatelessWidget {
final Animation<double> animation;
final Widget child;
const ScaleThenSlideTransitionContinue({
Key? key,
required this.animation,
required this.child,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return SlideTransition(
position: animation
.drive(CurveTween(curve: Interval(0.5, 1)))
.drive(CurveTween(curve: Curves.easeOutQuint))
.drive(Tween<Offset>(
begin: Offset(1.0, 0.0),
end: Offset(0.0,0.0),
)),
child: SizeNoClipTransition(
axis: Axis.vertical,
sizeFactor: animation
.drive(CurveTween(curve: const Interval(0, 0.5)))
.drive(CurveTween(curve: Curves.easeInOut)),
child: child,
),
);
}
}
@immutable
class SizeNoClipTransition extends AnimatedWidget {
/// Creates a size transition.
///
/// The [axis], [sizeFactor], and [axisAlignment] arguments must not be null.
/// The [axis] argument defaults to [Axis.vertical]. The [axisAlignment]
/// defaults to 0.0, which centers the child along the main axis during the
/// transition.
const SizeNoClipTransition({
Key? key,
this.axis = Axis.vertical,
required Animation<double> sizeFactor,
this.axisAlignment = 0.0,
this.child,
}) : super(key: key, listenable: sizeFactor);
/// [Axis.horizontal] if [sizeFactor] modifies the width, otherwise
/// [Axis.vertical].
final Axis axis;
/// The animation that controls the (clipped) size of the child.
///
/// The width or height (depending on the [axis] value) of this widget will be
/// its intrinsic width or height multiplied by [sizeFactor]'s value at the
/// current point in the animation.
///
/// If the value of [sizeFactor] is less than one, the child will be clipped
/// in the appropriate axis.
Animation<double> get sizeFactor => listenable as Animation<double>;
/// Describes how to align the child along the axis that [sizeFactor] is
/// modifying.
///
/// A value of -1.0 indicates the top when [axis] is [Axis.vertical], and the
/// start when [axis] is [Axis.horizontal]. The start is on the left when the
/// text direction in effect is [TextDirection.ltr] and on the right when it
/// is [TextDirection.rtl].
///
/// A value of 1.0 indicates the bottom or end, depending upon the [axis].
///
/// A value of 0.0 (the default) indicates the center for either [axis] value.
final double axisAlignment;
/// The widget below this widget in the tree.
///
/// {@macro flutter.widgets.child}
final Widget? child;
@override
Widget build(BuildContext context) {
if (sizeFactor.value == 1) {
return child!;
}
AlignmentDirectional alignment;
if (axis == Axis.vertical)
// ignore: curly_braces_in_flow_control_structures
alignment = AlignmentDirectional(-1.0, axisAlignment);
else
// ignore: curly_braces_in_flow_control_structures
alignment = AlignmentDirectional(axisAlignment, -1.0);
return ClipRect(
child: Align(
alignment: alignment,
heightFactor:
axis == Axis.vertical ? math.max(sizeFactor.value, 0.0) : null,
widthFactor:
axis == Axis.horizontal ? math.max(sizeFactor.value, 0.0) : null,
child: child,
),
);
}
}
Also I attached the images of what actual happens:
Three items in first List
Then all items get clicked so they get blue
Then when removing first item, all items above gets rebuild its state ( they get green again but should stay blue
Thanks for the update. Can you try to use https://api.flutter.dev/flutter/widgets/AutomaticKeepAliveClientMixin-mixin.html which helps to keep elements in a list alive, ie, not re-render when you perform any action on them and see if it helps ?
Hello, no does not work at all.
Hi @Gastrolize, This seems to be working as intended. Animated Widgets are expected to rebuild more often than normal widgets.
looks similar to https://github.com/flutter/flutter/issues/72915 (closed)
Hi @maheshmnj, but these are StatefulWidgets which runs the remove Animation and Insert Animation. I fixed it by using GlobalKeys with State in List.
@Gastrolize
Thanks for the update. Since you are calling setState()
inside Stateful
widget, it in turn calls build()
method which ideally also rebuilds the descendant widgets and hence you are seeing the behavior of rebuilding all elements and plays the animation as applicable.
I fixed it by using GlobalKeys with State in List.
Does this mean we can close as resolved ?
@darshankawar Hi,
I know, the problem is not that the widget calls build multiple times. The problem is, that the animated list rebuilds the state to init state, when one item gets removed from list. SetState is whole different then resets the whole state.
The only way to resolve this was by setting a List with Statefulwidgets and give them initially a GlobalKey and using AutoList which includes the DifUtil.
Animated List needs to be fixed a little bit on the framework side.
You can close this issue sure, but i guess we need to improve the animated list for those issues
but i guess we need to improve the animated list for those issues
This sounds as an enhancement for which I suggest you to open a new issue for better tracking. I will close this issue as resolved and per your comment above.
This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v
and a minimal reproduction of the issue.
Hello,
I tried to create two AnimatedList with Stateful Widgets to interact with. When interacting at example with the first 3 widgets of in the first list (clicking on the item so background gets blue) and removing the first item to animate out, all items or most the item above in the first list gets rebuild to its init state. The items on the first List should have its state saved not reset to initState.
Here is the whole code to run. Also I tried with value keys, but do not work. It only works when not adding any animation.
code
``` import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:merchant/components/animated_list/scale_then_slide_transition.dart'; import '../../components/animated_list/autolist.dart'; class TestList extends StatefulWidget { @override _TestList createState() => _TestList(); } class _TestList extends StateFlutter Doctor: