Flutter's AnimatedSize class animates its size according to the size of its child. I need to know how to listen for changes to the size, ideally when the resizing has finished.
With my use-case, this widget is contained within a ListView, but I only seem to be able to listen to scroll events on this with a NotificationListener (being able to listen to changes in scrollable height would solve my problem).
Alternatively, being able to listen for when a widget such as a Column changes it's number of children would work too.
In Flutter, AnimatedSize is a widget that animates its size to match the size of its child. In this tutorial, I am going to explain how to use the widget with some examples. To use AnimatedSize, below is the constructor that you need to call. There are two required arguments: vsync and duration.
AnimatedSize Widget is a widget that automatically transitions its size over a given duration whenever the given child’s size changes. The AnimatedSize Widget takes a duration parameter, and you use the Duration class to specify 1 second.
However, because flutter is open-source it's entirely possible to simply create your own widget based on the flutter one that does do what you need. You just need to dig into the source code a bit.
If what you need is animating the size change of a widget, one of the alternatives is using the AnimatedContainer widget. The AnimatedContainer is a Container that can animate its child when any property changes. Key? key: The widget's key, used to control how a widget is replaced with another widget.
There was a widget specifically made for this case. It's called: SizeChangedLayoutNotifier (https://api.flutter.dev/flutter/widgets/SizeChangedLayoutNotifier-class.html)
You just have to wrap your widget with it and then listen with the NotificationListener widget (https://api.flutter.dev/flutter/widgets/NotificationListener-class.html) for changes.
An Example would be following:
NotificationListener(
onNotification: (SizeChangedLayoutNotification notification){
Future.delayed(Duration(milliseconds: 300),(){setState(() {
print('size changed');
_height++;
});});
return true;
},
child: SizeChangedLayoutNotifier( child: AnimatedContainer(width: 100, height: _height)))
Hope this will help all future people which will find this post.
I believe the last line of your question provides a hint as to what you're trying to do. It sounds like you're displaying a list of things, and you want something to be notified when that list of things changes. If I'm wrong about that, please clarify =).
There are two ways of doing this; one is that you could pass a callback function to the widget containing the list. When you added something to the list you could simply call the callback.
However, that is a little bit fragile and if you have multiple layers in between the place you need to know and the actual list it could get messy.
This is due in part to the fact that in flutter, for the most part, data goes downwards (through children) much easier than it goes up. It sounds like what you might want to do is have a parent widget that holds the list of items, and passes that down to whatever builds the actual list. If there are multiple layers of widgets between the parent and the child, you could use an InheritedWidget to get the information from the child without directly passing it.
EDIT: with clarification from the OP, this answer only provided an sub-optimal alternative to the original goal. See below for an answer to the main query:
I don't think that it is possible to do this with any existing flutter widgets. However, because flutter is open-source it's entirely possible to simply create your own widget based on the flutter one that does do what you need. You just need to dig into the source code a bit.
Please note that the code I'm pasting below contains a slightly modified version of the flutter implementation in rendering animated_size.dart and widgets animated_size.dart, and therefore usage of it must adhere to the flutter LICENSE file at the time of copying. Use of the code is governed by BSD style license, yada yada.
I've created a very slightly modified version of the AnimatedSize widget called NotifyingAnimatedSize (and the corresponding more-interesting NotifyingRenderAnimatedSize) in the code below, which simply calls a callback when it starts animated and when it's done animating. I've removed all of the comments from the source code as they made it even longer.
Look for notificationCallback
throughout the code as that's basically all I added.
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() => runApp(new MyApp());
enum NotifyingRenderAnimatedSizeState {
start,
stable,
changed,
unstable,
}
enum SizeChangingStatus {
changing,
done,
}
typedef void NotifyingAnimatedSizeCallback(SizeChangingStatus status);
class NotifyingRenderAnimatedSize extends RenderAligningShiftedBox {
NotifyingRenderAnimatedSize({
@required TickerProvider vsync,
@required Duration duration,
Curve curve: Curves.linear,
AlignmentGeometry alignment: Alignment.center,
TextDirection textDirection,
RenderBox child,
this.notificationCallback
}) : assert(vsync != null),
assert(duration != null),
assert(curve != null),
_vsync = vsync,
super(child: child, alignment: alignment, textDirection: textDirection) {
_controller = new AnimationController(
vsync: vsync,
duration: duration,
)..addListener(() {
if (_controller.value != _lastValue) markNeedsLayout();
});
_animation = new CurvedAnimation(parent: _controller, curve: curve);
}
AnimationController _controller;
CurvedAnimation _animation;
final SizeTween _sizeTween = new SizeTween();
bool _hasVisualOverflow;
double _lastValue;
final NotifyingAnimatedSizeCallback notificationCallback;
@visibleForTesting
NotifyingRenderAnimatedSizeState get state => _state;
NotifyingRenderAnimatedSizeState _state = NotifyingRenderAnimatedSizeState.start;
Duration get duration => _controller.duration;
set duration(Duration value) {
assert(value != null);
if (value == _controller.duration) return;
_controller.duration = value;
}
Curve get curve => _animation.curve;
set curve(Curve value) {
assert(value != null);
if (value == _animation.curve) return;
_animation.curve = value;
}
bool get isAnimating => _controller.isAnimating;
TickerProvider get vsync => _vsync;
TickerProvider _vsync;
set vsync(TickerProvider value) {
assert(value != null);
if (value == _vsync) return;
_vsync = value;
_controller.resync(vsync);
}
@override
void detach() {
_controller.stop();
super.detach();
}
Size get _animatedSize {
return _sizeTween.evaluate(_animation);
}
@override
void performLayout() {
_lastValue = _controller.value;
_hasVisualOverflow = false;
if (child == null || constraints.isTight) {
_controller.stop();
size = _sizeTween.begin = _sizeTween.end = constraints.smallest;
_state = NotifyingRenderAnimatedSizeState.start;
child?.layout(constraints);
return;
}
child.layout(constraints, parentUsesSize: true);
assert(_state != null);
switch (_state) {
case NotifyingRenderAnimatedSizeState.start:
_layoutStart();
break;
case NotifyingRenderAnimatedSizeState.stable:
_layoutStable();
break;
case NotifyingRenderAnimatedSizeState.changed:
_layoutChanged();
break;
case NotifyingRenderAnimatedSizeState.unstable:
_layoutUnstable();
break;
}
size = constraints.constrain(_animatedSize);
alignChild();
if (size.width < _sizeTween.end.width || size.height < _sizeTween.end.height) _hasVisualOverflow = true;
}
void _restartAnimation() {
_lastValue = 0.0;
_controller.forward(from: 0.0);
}
void _layoutStart() {
_sizeTween.begin = _sizeTween.end = debugAdoptSize(child.size);
_state = NotifyingRenderAnimatedSizeState.stable;
}
void _layoutStable() {
if (_sizeTween.end != child.size) {
_sizeTween.begin = size;
_sizeTween.end = debugAdoptSize(child.size);
_restartAnimation();
_state = NotifyingRenderAnimatedSizeState.changed;
} else if (_controller.value == _controller.upperBound) {
// Animation finished. Reset target sizes.
_sizeTween.begin = _sizeTween.end = debugAdoptSize(child.size);
notificationCallback(SizeChangingStatus.done);
} else if (!_controller.isAnimating) {
_controller.forward(); // resume the animation after being detached
}
}
void _layoutChanged() {
if (_sizeTween.end != child.size) {
// Child size changed again. Match the child's size and restart animation.
_sizeTween.begin = _sizeTween.end = debugAdoptSize(child.size);
_restartAnimation();
_state = NotifyingRenderAnimatedSizeState.unstable;
} else {
notificationCallback(SizeChangingStatus.changing);
// Child size stabilized.
_state = NotifyingRenderAnimatedSizeState.stable;
if (!_controller.isAnimating) _controller.forward(); // resume the animation after being detached
}
}
void _layoutUnstable() {
if (_sizeTween.end != child.size) {
// Still unstable. Continue tracking the child.
_sizeTween.begin = _sizeTween.end = debugAdoptSize(child.size);
_restartAnimation();
} else {
// Child size stabilized.
_controller.stop();
_state = NotifyingRenderAnimatedSizeState.stable;
}
}
@override
void paint(PaintingContext context, Offset offset) {
if (child != null && _hasVisualOverflow) {
final Rect rect = Offset.zero & size;
context.pushClipRect(needsCompositing, offset, rect, super.paint);
} else {
super.paint(context, offset);
}
}
}
class NotifyingAnimatedSize extends SingleChildRenderObjectWidget {
const NotifyingAnimatedSize({
Key key,
Widget child,
this.alignment: Alignment.center,
this.curve: Curves.linear,
@required this.duration,
@required this.vsync,
this.notificationCallback,
}) : super(key: key, child: child);
final AlignmentGeometry alignment;
final Curve curve;
final Duration duration;
final TickerProvider vsync;
final NotifyingAnimatedSizeCallback notificationCallback;
@override
NotifyingRenderAnimatedSize createRenderObject(BuildContext context) {
return new NotifyingRenderAnimatedSize(
alignment: alignment,
duration: duration,
curve: curve,
vsync: vsync,
textDirection: Directionality.of(context),
notificationCallback: notificationCallback
);
}
@override
void updateRenderObject(BuildContext context, NotifyingRenderAnimatedSize renderObject) {
renderObject
..alignment = alignment
..duration = duration
..curve = curve
..vsync = vsync
..textDirection = Directionality.of(context);
}
}
class MyApp extends StatefulWidget {
@override
State<StatefulWidget> createState() => MyAppState();
}
class MyAppState extends State<MyApp> with TickerProviderStateMixin<MyApp> {
double _containerSize = 100.0;
@override
Widget build(BuildContext context) {
return new MaterialApp(
home: new SafeArea(
child: new Container(
color: Colors.white,
child: new Column(children: [
new RaisedButton(
child: new Text("Press me to make the square change size!"),
onPressed: () => setState(
() {
if (_containerSize > 299.0)
_containerSize = 100.0;
else
_containerSize += 100.0;
},
),
),
new NotifyingAnimatedSize(
duration: new Duration(seconds: 2),
vsync: this,
child: new Container(
color: Colors.blue,
width: _containerSize,
height: _containerSize,
),
notificationCallback: (state) {
print("State is $state");
},
)
]),
),
),
);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With