[英]How to listen for resize events in a Flutter AnimatedSize widget
Flutter 的AnimatedSize class 根据其子项的大小为其大小设置动画。 我需要知道如何监听大小的变化,最好是在调整大小完成后。
在我的用例中,这个小部件包含在ListView中,但我似乎只能使用NotificationListener来监听滚动事件(能够监听可滚动高度的变化将解决我的问题)。
或者,能够监听诸如Column之类的小部件何时更改其子项数量也可以。
有一个专门为此案例制作的小部件。 它被称为:SizeChangedLayoutNotifier( https://api.flutter.dev/flutter/widgets/SizeChangedLayoutNotifier-class.html )
你只需要用它包装你的小部件,然后用 NotificationListener 小部件( https://api.flutter.dev/flutter/widgets/NotificationListener-class.html )监听更改。
一个例子如下:
NotificationListener(
onNotification: (SizeChangedLayoutNotification notification){
Future.delayed(Duration(milliseconds: 300),(){setState(() {
print('size changed');
_height++;
});});
return true;
},
child: SizeChangedLayoutNotifier( child: AnimatedContainer(width: 100, height: _height)))
希望这会帮助所有将来会找到这篇文章的人。
我相信您问题的最后一行提供了有关您要做什么的提示。 听起来您正在显示一个事物列表,并且您希望在该事物列表发生变化时收到通知。 如果我错了,请澄清=)。
有两种方法可以做到这一点; 一是您可以将回调函数传递给包含列表的小部件。 当您向列表中添加内容时,您只需调用回调即可。
但是,这有点脆弱,如果您需要知道的地方和实际列表之间有多个层,它可能会变得混乱。
这部分是因为在 Flutter 中,在大多数情况下,数据向下传输(通过子项)比向上传输要容易得多。 听起来您可能想要做的是拥有一个包含项目列表的父小部件,并将其传递给构建实际列表的任何内容。 如果父子之间有多层小部件,则可以使用InheritedWidget从子部件获取信息,而无需直接传递它。
编辑:经过 OP 的澄清,此答案仅提供了原始目标的次优替代方案。 请参阅下面的主要查询的答案:
我认为使用任何现有的颤振小部件都无法做到这一点。 然而,由于颤振是开源的,这完全有可能基于扑一个,它做你需要简单地创建自己的控件。 你只需要深入研究一下源代码。
请注意,我在下面粘贴的代码包含在渲染 animation_size.dart和小部件animation_size.dart 时颤振实现的稍微修改版本,因此它的使用必须在复制时遵守颤振许可证文件。 代码的使用受 BSD 风格许可证 yada yada 的约束。
我在下面的代码中创建了一个名为 NotifyingAnimatedSize(以及相应的更有趣的 NotifyingRenderAnimatedSize)的 AnimatedSize 小部件的非常小的修改版本,它只是在开始动画时和完成动画时调用回调。 我已经从源代码中删除了所有注释,因为它们使它变得更长。
在整个代码中寻找notificationCallback
,因为这基本上是我添加的全部内容。
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");
},
)
]),
),
),
);
}
}
这是不可能的。 小部件不知道他们的孩子的大小。 他们所做的唯一一件事就是对它们施加约束,但这与最终大小无关。
在这里,我重新发布了 rmtmckenzie(归功于他)的答案,但安全性为 null。 我决定不编辑他的答案,以便在有和没有 null 安全的情况下提供他和我的两个答案。 您可以只在代码中使用NotifyingAnimatedSize
。
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
enum SizeChangingStatus {
changing,
done,
}
enum NotifyingRenderAnimatedSizeState {
start,
stable,
changed,
unstable,
}
typedef NotifyingAnimatedSizeCallback = void Function(SizeChangingStatus status);
class NotifyingRenderAnimatedSize extends RenderAligningShiftedBox {
NotifyingRenderAnimatedSize({
required TickerProvider vsync,
required Duration duration,
Duration? reverseDuration,
Curve curve = Curves.linear,
AlignmentGeometry alignment = Alignment.center,
required TextDirection textDirection,
RenderBox? child,
Clip clipBehavior = Clip.hardEdge,
required this.notificationCallback,
})
: _vsync = vsync,
_clipBehavior = clipBehavior,
super(textDirection: textDirection, alignment: alignment, child: child) {
_controller = AnimationController(
vsync: vsync,
duration: duration,
reverseDuration: reverseDuration,
)
..addListener(() {
if (_controller.value != _lastValue) {
markNeedsLayout();
}
});
_animation = CurvedAnimation(
parent: _controller,
curve: curve,
);
}
late final AnimationController _controller;
late final CurvedAnimation _animation;
final SizeTween _sizeTween = SizeTween();
late bool _hasVisualOverflow;
double? _lastValue;
final NotifyingAnimatedSizeCallback notificationCallback;
/// The state this size animation is in.
///
/// See [RenderAnimatedSizeState] for possible states.
@visibleForTesting
NotifyingRenderAnimatedSizeState get state => _state;
NotifyingRenderAnimatedSizeState _state = NotifyingRenderAnimatedSizeState.start;
/// The duration of the animation.
Duration get duration => _controller.duration!;
set duration(Duration value) {
if (value == _controller.duration) {
return;
}
_controller.duration = value;
}
/// The duration of the animation when running in reverse.
Duration? get reverseDuration => _controller.reverseDuration;
set reverseDuration(Duration? value) {
if (value == _controller.reverseDuration) {
return;
}
_controller.reverseDuration = value;
}
/// The curve of the animation.
Curve get curve => _animation.curve;
set curve(Curve value) {
if (value == _animation.curve) {
return;
}
_animation.curve = value;
}
/// {@macro flutter.material.Material.clipBehavior}
///
/// Defaults to [Clip.hardEdge], and must not be null.
Clip get clipBehavior => _clipBehavior;
Clip _clipBehavior = Clip.hardEdge;
set clipBehavior(Clip value) {
if (value != _clipBehavior) {
_clipBehavior = value;
markNeedsPaint();
markNeedsSemanticsUpdate();
}
}
/// Whether the size is being currently animated towards the child's size.
///
/// See [RenderAnimatedSizeState] for situations when we may not be animating
/// the size.
bool get isAnimating => _controller.isAnimating;
/// The [TickerProvider] for the [AnimationController] that runs the animation.
TickerProvider get vsync => _vsync;
TickerProvider _vsync;
set vsync(TickerProvider value) {
if (value == _vsync) {
return;
}
_vsync = value;
_controller.resync(vsync);
}
@override
void attach(PipelineOwner owner) {
super.attach(owner);
switch (state) {
case NotifyingRenderAnimatedSizeState.start:
case NotifyingRenderAnimatedSizeState.stable:
break;
case NotifyingRenderAnimatedSizeState.changed:
case NotifyingRenderAnimatedSizeState.unstable:
// Call markNeedsLayout in case the RenderObject isn't marked dirty
// already, to resume interrupted resizing animation.
markNeedsLayout();
break;
}
}
@override
void detach() {
_controller.stop();
super.detach();
}
Size? get _animatedSize => _sizeTween.evaluate(_animation);
@override
void performLayout() {
_lastValue = _controller.value;
_hasVisualOverflow = false;
final BoxConstraints constraints = this.constraints;
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);
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;
}
}
@override
Size computeDryLayout(BoxConstraints constraints) {
if (child == null || constraints.isTight) {
return constraints.smallest;
}
// This simplified version of performLayout only calculates the current
// size without modifying global state. See performLayout for comments
// explaining the rational behind the implementation.
final Size childSize = child!.getDryLayout(constraints);
switch (_state) {
case NotifyingRenderAnimatedSizeState.start:
return constraints.constrain(childSize);
case NotifyingRenderAnimatedSizeState.stable:
if (_sizeTween.end != childSize) {
return constraints.constrain(size);
} else if (_controller.value == _controller.upperBound) {
return constraints.constrain(childSize);
}
break;
case NotifyingRenderAnimatedSizeState.unstable:
case NotifyingRenderAnimatedSizeState.changed:
if (_sizeTween.end != childSize) {
return constraints.constrain(childSize);
}
break;
}
return constraints.constrain(_animatedSize!);
}
void _restartAnimation() {
_lastValue = 0.0;
_controller.forward(from: 0.0);
}
/// Laying out the child for the first time.
///
/// We have the initial size to animate from, but we do not have the target
/// size to animate to, so we set both ends to child's size.
void _layoutStart() {
_sizeTween.begin = _sizeTween.end = debugAdoptSize(child!.size);
_state = NotifyingRenderAnimatedSizeState.stable;
}
/// At this state we're assuming the child size is stable and letting the
/// animation run its course.
///
/// If during animation the size of the child changes we restart the
/// animation.
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
}
}
/// This state indicates that the size of the child changed once after being
/// considered stable.
///
/// If the child stabilizes immediately, we go back to stable state. If it
/// changes again, we match the child's size, restart animation and go to
/// unstable state.
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) {
// Resume the animation after being detached.
_controller.forward();
}
}
}
/// The child's size is not stable.
///
/// Continue tracking the child's size until is stabilizes.
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 && clipBehavior != Clip.none) {
final Rect rect = Offset.zero & size;
_clipRectLayer.layer = context.pushClipRect(
needsCompositing,
offset,
rect,
super.paint,
clipBehavior: clipBehavior,
oldLayer: _clipRectLayer.layer,
);
} else {
_clipRectLayer.layer = null;
super.paint(context, offset);
}
}
final LayerHandle<ClipRectLayer> _clipRectLayer = LayerHandle<ClipRectLayer>();
@override
void dispose() {
_clipRectLayer.layer = null;
super.dispose();
}
}
class NotifyingAnimatedSize extends StatefulWidget {
/// Creates a widget that animates its size to match that of its child.
///
/// The [curve] and [duration] arguments must not be null.
const NotifyingAnimatedSize({
required this.child,
this.alignment = Alignment.center,
this.curve = Curves.linear,
required this.duration,
this.reverseDuration,
required this.notificationCallback,
this.clipBehavior = Clip.hardEdge,
});
/// The widget below this widget in the tree.
///
/// {@macro flutter.widgets.ProxyWidget.child}
final Widget child;
/// The alignment of the child within the parent when the parent is not yet
/// the same size as the child.
///
/// The x and y values of the alignment control the horizontal and vertical
/// alignment, respectively. An x value of -1.0 means that the left edge of
/// the child is aligned with the left edge of the parent whereas an x value
/// of 1.0 means that the right edge of the child is aligned with the right
/// edge of the parent. Other values interpolate (and extrapolate) linearly.
/// For example, a value of 0.0 means that the center of the child is aligned
/// with the center of the parent.
///
/// Defaults to [Alignment.center].
///
/// See also:
///
/// * [Alignment], a class with convenient constants typically used to
/// specify an [AlignmentGeometry].
/// * [AlignmentDirectional], like [Alignment] for specifying alignments
/// relative to text direction.
final AlignmentGeometry alignment;
/// The animation curve when transitioning this widget's size to match the
/// child's size.
final Curve curve;
/// The duration when transitioning this widget's size to match the child's
/// size.
final Duration duration;
/// The duration when transitioning this widget's size to match the child's
/// size when going in reverse.
///
/// If not specified, defaults to [duration].
final Duration? reverseDuration;
/// {@macro flutter.material.Material.clipBehavior}
///
/// Defaults to [Clip.hardEdge], and must not be null.
final Clip clipBehavior;
/// Callback to trigger when animation ends
final NotifyingAnimatedSizeCallback notificationCallback;
@override
State<NotifyingAnimatedSize> createState() => _NotifyingAnimatedSizeState();
}
class _NotifyingAnimatedSizeState extends State<NotifyingAnimatedSize> with SingleTickerProviderStateMixin {
@override
Widget build(BuildContext context) =>
_NotifyingAnimatedSize(
alignment: widget.alignment,
curve: widget.curve,
duration: widget.duration,
vsync: this,
notificationCallback: widget.notificationCallback,
child: widget.child,
);
}
class _NotifyingAnimatedSize extends SingleChildRenderObjectWidget {
const _NotifyingAnimatedSize({
Key? key,
required Widget child,
this.alignment = Alignment.center,
this.curve = Curves.linear,
required this.duration,
required this.vsync,
required 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) =>
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);
}
}
像这样使用小部件:
NotifyingAnimatedSize(
duration: const Duration(milliseconds: 200),
notificationCallback: (status) {
if (status == SizeChangingStatus.done) {
//do something
}
},
child: Container(height: 50, width: 50, color: Colors.red),
);
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.