i have this simple stateful widget:
class SomeListWidget extends StatefulWidget {
const SomeListWidget({super.key});
@override
State<SomeListWidget> createState() => _SomeListWidgetState();
}
class _SomeListWidgetState extends State<SomeListWidget> {
List<Color> colors = [
Colors.red,
Colors.green,
];
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Expanded(
child: ListView.builder(
itemCount: colors.length,
reverse: true,
itemBuilder: (context, index) {
return ColorTransitionBox(
targetColor: colors[index],
);
},
),
),
ElevatedButton(
onPressed: () {
setState(() {
final rnd = Random();
final color = Color.fromARGB(
255, rnd.nextInt(256), rnd.nextInt(256), rnd.nextInt(256));
colors.insert(0, color);
});
},
child: Text('Add color'),
),
],
),
);
}
}
The ColorTransitionBox widget changes its color linearly from white to the target:
class ColorTransitionBox extends StatefulWidget {
final Color targetColor;
const ColorTransitionBox({Key? key, required this.targetColor})
: super(key: key);
@override
State<ColorTransitionBox> createState() => _ColorTransitionBoxState();
}
class _ColorTransitionBoxState extends State<ColorTransitionBox> {
Color _currentColor = Colors.white;
@override
void initState() {
super.initState();
// Start the color transition after the first frame is rendered
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
_currentColor = widget.targetColor;
});
});
}
@override
Widget build(BuildContext context) {
return AnimatedContainer(
duration: const Duration(seconds: 1),
curve: Curves.linear, // Linear animation
color: _currentColor,
width: 100, // Example width
height: 100, // Example height
child: Center(
child: Text(
'Color Box',
style: TextStyle(
color: _currentColor == Colors.white ? Colors.black : Colors.white,
),
),
),
);
}
}
when I click “Add Color” I want a new box to appear at the bottom that linearly changes color from white to random in 1 second. But when I click, a box appears at the top and its color always matches the color of the last item in the list (green in this case). I need the ListView to be reversed and new widgets to appear at the bottom.
i tried use UniqueKey
Expanded(
child: ListView.builder(
itemCount: colors.length,
reverse: true,
itemBuilder: (context, index) {
return ColorTransitionBox(
key: UniqueKey(),
targetColor: colors[index],
);
},
),
),
it worked, but now when I add a new element, all the others start transitioning again
i also use ValueKey(colors[index]), but it turned out the same.
Bebra is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
0
This does the trick:
@override
void didUpdateWidget(covariant ColorTransitionBox oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.targetColor != oldWidget.targetColor) {
currentColor = widget.targetColor;
} // to sync the newly added dynamic color
}
Here’s the refactored code snippet solution to your problem:
import 'dart:math';
import 'package:flutter/material.dart';
class SomeListWidget extends StatefulWidget {
const SomeListWidget({super.key});
@override
State<SomeListWidget> createState() => _SomeListWidgetState();
}
class _SomeListWidgetState extends State<SomeListWidget> {
List<Color> colors = [
Colors.red,
Colors.green,
];
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Expanded(
child: ListView.builder(
itemCount: colors.length,
reverse: true,
itemBuilder: (context, index) {
return ColorTransitionBox(
targetColor: colors[index],
);
},
),
),
ElevatedButton(
onPressed: () {
setState(() {
final rnd = Random();
final color = Color.fromARGB(
255, rnd.nextInt(256), rnd.nextInt(256), rnd.nextInt(256));
colors.insert(0, color);
});
},
child: Text('Add color'),
),
],
),
);
}
}
class ColorTransitionBox extends StatefulWidget {
final Color? targetColor;
const ColorTransitionBox({super.key, required this.targetColor});
@override
State<ColorTransitionBox> createState() => _ColorTransitionBoxState();
}
class _ColorTransitionBoxState extends State<ColorTransitionBox> {
Color? currentColor;
@override
void initState() {
super.initState();
// Start the color transition after the first frame is rendered
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
setState(() {
currentColor = widget.targetColor;
});
}
}); // still needed this to initialize the color from the parent widget
}
@override
void didUpdateWidget(covariant ColorTransitionBox oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.targetColor != oldWidget.targetColor) {
currentColor = widget.targetColor;
} // to sync the newly added dynamic color
}
@override
Widget build(BuildContext context) {
return AnimatedContainer(
duration: const Duration(seconds: 1),
curve: Curves.linear, // Linear animation
color: currentColor,
width: 100, // Example width
height: 100, // Example height
child: Center(
child: Text(
'Color Box',
style: TextStyle(
color: currentColor == Colors.white ? Colors.black : Colors.white,
),
),
),
);
}
}
The output:
I hope it helps!