如何在Flutter AnimatedSize小部件中侦听resize事件

时间:2018-04-24 07:50:15

标签: flutter

Flutter的AnimatedSize类根据其子的大小设置其大小。我需要知道如何监听大小的变化,最好是在调整大小完成时。

使用我的用例,这个小部件包含在ListView中,但我似乎只能用NotificationListener监听滚动事件(能够听取更改)可滚动的高度将解决我的问题)。

或者,能够监听诸如Column之类的小部件何时更改它的子女数量也会有效。

3 个答案:

答案 0 :(得分:4)

有一个专门为此案例制作的小部件。它被称为: 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)))

希望这能帮助所有将来找到这篇文章的人。

答案 1 :(得分:1)

这是不可能的。小工具不知道孩子的大小。他们唯一要做的就是对它们施加约束,但这与最终尺寸无关。

答案 2 :(得分:1)

我相信你问题的最后一行提供了你正在尝试做什么的提示。听起来你正在显示一系列事物,并且当事情列表发生变化时,你希望得到通知。如果我错了,请澄清=)。

有两种方法可以做到这一点;一个是你可以将回调函数传递给包含列表的小部件。当您向列表中添加内容时,您只需调用回调即可。

然而,这有点脆弱,如果你需要知道的地方和实际的列表之间有多层,它可能会变得混乱。

部分原因在于,在大多数情况下,数据下降(通过儿童)比上升更容易。听起来你可能想要做的是拥有一个包含项目列表的父窗口小部件,并将其传递给构建实际列表的任何内容。如果父级和子级之间存在多个小部件层,则可以使用InheritedWidget从子级获取信息而不直接传递它。

编辑:在OP的澄清下,这个答案只提供了原始目标的次优替代方案。请参阅下面的主要查询答案:

我不认为可以使用任何现有的颤动小部件来执行此操作。但是,因为颤动是开源的,所以完全可以根据做你需要的那个颤动来创建你自己的小部件。你只需要深入挖掘一下源代码。

请注意,我在下面粘贴的代码包含rendering animated_size.dartwidgets animated_size.dart中flutter实现的略微修改版本,因此它的使用必须遵守flutter {{3 }}。代码的使用受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");
              },
            )
          ]),
        ),
      ),
    );
  }
}