如何在Flutter中实现将容器的高度从0扩展到其内容高度的动画效果?

10

我有一个容器,从零高度开始,并需要在用户交互后扩展。

  • 我尝试使用AnimatedContainer / AnimatedSize,并将子小部件的高度从0更改为null,但在这两种情况下,Flutter都会抱怨无法从0插值到null.
  • 我还尝试使用BoxConstraints(使用maxHeight = double.infinity进行扩展),而不是明确的高度,在这种情况下,Flutter抱怨无法从有限值插值到未确定的值。
  • 我还尝试将mainAxisSize设置为min / max,在这种情况下,Flutter抱怨vsyncnull

如何动画扩展小部件,以便它动态变大到足以包裹其内容?如果不能动态完成此操作,则安全调整大小的内容的方式是什么,以使其在屏幕大小之间有意义?在Web开发中,我知道像em这样的东西是相对大小,但在Flutter的上下文中,我看不到如何可靠地控制事物的大小。


更新:正如@pskink建议的那样,将子项包装在Align小部件中,并动画化Align的heightFactor参数可完成折叠。但是,我仍然无法使崩溃效果正常工作,即使折叠的子元素本身具有子元素也是如此。例如,Column小部件根本不使用ClipRect剪切(请参见https://github.com/flutter/flutter/issues/29357),即使我使用Wrap而不是Column,如果Wrap的孩子是行,则也无法正常工作。不确定如何使剪辑始终正常工作。


2
请查看 expansion_tile.dart 源文件 - 它使用了 Align.heightFactor,这是一个小技巧。 - pskink
@pskink,它基本上可以工作,但即使使用ClipRect包装它,它也没有进行剪切。如何让它进行剪切? - mwarrior
@pskink ClipRect在列/换行/行上无法正常工作。 - mwarrior
更新问题以反映新发现。仍然无法通过剪辑来实现一致的工作。 - mwarrior
@pskink将此内容的摘要移至下面的答案中。谢谢。 - mwarrior
显示剩余3条评论
3个回答

28

也许您可以使用SizeTransition来解决这个问题?

在此输入图片描述

class VariableSizeContainerExample extends StatefulWidget {
  VariableSizeContainerExample();

  @override
  _VariableSizeContainerExampleState createState() => _VariableSizeContainerExampleState();
}

class _VariableSizeContainerExampleState extends State<VariableSizeContainerExample> with TickerProviderStateMixin {
  AnimationController _controller;
  Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(seconds: 1),
      vsync: this,
    );
    _animation = CurvedAnimation(
      parent: _controller,
      curve: Curves.fastLinearToSlowEaseIn,
    );
  }

  _toggleContainer() {
    print(_animation.status);
    if (_animation.status != AnimationStatus.completed) {
      _controller.forward();
    } else {
      _controller.animateBack(0, duration: Duration(seconds: 1));
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: SafeArea(
          child: Column(
            children: [
              TextButton(
                onPressed: () => _toggleContainer(),
                child: Text("Toggle container visibility"),
              ),
              SizeTransition(
                sizeFactor: _animation,
                axis: Axis.vertical,
                child: Container(
                  child: Text(
                    "This can have variable size",
                    style: TextStyle(fontSize: 40),
                  ),
                ),
              ),
              Text("This is below the above container"),
            ],
          ),
        ),
      ),
    );
  }
}

1
这个很好用!@pskink的答案也可以,但需要更多的布线。这个以直接的方式完全符合我的要求。我还想提一下,如果有人想让它从底部折叠(而不是向中间),可以在SizeTransition参数中添加axisAlignment:-1 - mwarrior
有没有办法让它在变小时不完全到达0?在我的情况下,触发器是内容本身,如果用户点击它,它会增大以显示完整的内容,或者折叠自己直到高度达到50。这样,用户就有空间再次点击它以扩展它。 - enchance
@enchance 我已经代表你进行了一些(最小化的)测试。你不能使用_controller.animateBack(0.5, duration: Duration(seconds: 1)来实现这个目标。你必须使用"animateTo"来到达某个位置。也许你可以添加一个额外的变量来控制一个点击使小部件大小减少50%,第二次点击将完全关闭,最后一次点击将使用"animateBack()"。你也可以通过设置_controller.value = 0.5手动设置大小比例,但如果你在setState中这样做,它会重建小部件,使其“弹回”到50%的大小(看起来很糟糕)。 - FoxDonut
我最终进行了测试,并使用一个简单的int计数器变量使其工作。在第一个setState -> animateTo(1)中,第二个setState()调用-> animateTo(0.5),最后一个setState()调用-> animateBack(0)。我没有在这个评论中添加计数器递增,但我相信你能做到!~最好的祝愿。 - FoxDonut

2

为了保存,将@pskink的评论移动到答案中:

主要概念是Align小部件有一个名为heightFactor的属性,它接受0到1之间的double类型值来缩放其子对象的高度(也有类似的widthFactor属性用于宽度)。通过动画化这个属性,我们可以折叠/展开子对象。例如:

ClipRect(
      child: Align(
        alignment: alignment,
        child: Align(
          alignment: innerAlignment,
          widthFactor: constantValue,
          heightFactor: animatedValue.value,
          child: builder(context, animation),
        ),
      )
)

其中animatedValue的类型为Animation<double>ClipReact用于剪裁/截断子小部件。请注意,ClipReact需要在Align小部件外面包裹;当包裹Align的子小部件时,它无法正常工作。

编辑:接收动画的对象也需要是一个AnimatedWidget,以使事情顺利进行。请参阅所选答案,了解处理此问题的方法。


0

@kohjakob的答案有效,但它不是一个可重用的小部件。

所以我创建了AnimatedCollapse。它基于其他Animated[...]小部件的风格,并使用SizeTransition,监听collapsed属性的任何更改以反向或前进动画。

class AnimatedCollapse extends StatefulWidget {
  const AnimatedCollapse({
    Key? key,
    this.child,
    required this.collapsed,
    this.axis = Axis.vertical,
    this.axisAlignment = 0.0,
    this.curve = Curves.linear,
    required this.duration,
    this.reverseDuration,
  }) : super(key: key);


  final Widget? child;

  /// Show or hide the child
  final bool collapsed;

  /// See [SizeTransition]
  final Axis axis;

  /// See [SizeTransition]
  final double axisAlignment;
  final Curve curve;
  final Duration duration;
  final Duration? reverseDuration;

  @override
  _AnimatedCollapseState createState() => _AnimatedCollapseState();
}

class _AnimatedCollapseState extends State<AnimatedCollapse> with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: widget.duration,
      reverseDuration: widget.reverseDuration,
    );

    _animation = CurvedAnimation(
      parent: _controller,
      curve: widget.curve,
    );

    if (!widget.collapsed) {
      _controller.forward();
    }
  }

  @override
  void didUpdateWidget(covariant AnimatedCollapse oldWidget) {
    super.didUpdateWidget(oldWidget);

    if (widget.collapsed != oldWidget.collapsed) {
      if (widget.collapsed) {
        _controller.reverse();
      } else {
        _controller.forward();
      }
    }
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return SizeTransition(
      sizeFactor: _animation,
      axis: widget.axis,
      axisAlignment: widget.axisAlignment,
      child: widget.child,
    );
  }
}

如果您希望小部件在首次加载时不播放动画,请将initialValue: 1添加到AnimationController。 - IcyIcicle
initState中,将value: widget.collapsed ? 0.0 : 1.0添加到AnimationController中,并删除对_controller.forward()的调用。这样,构建时小部件要么完全展开,要么完全折叠。否则,未折叠的小部件将在构建时执行完整的“展示”动画,而不仅仅在状态改变时执行。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接