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

问题描述 投票:0回答:3

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

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

如何以动画方式扩展小部件,使其动态增长到足以包裹其内容?如果这不能动态完成,那么有什么安全的方法来调整内容的大小,以便它们在不同的屏幕尺寸上都有意义?在 Web 开发中,我知道像

em
这样的东西是相对大小的,但在 Flutter 的背景下,我不知道如何可靠地控制东西的大小。


更新:按照@pskink的建议,将子项包装在Align小部件中并为Align的heightFactor参数设置动画以实现折叠。然而,当崩溃的孩子本身有孩子时,我仍然很难让崩溃工作。例如,Column 小部件根本不会使用 ClipRect 进行剪辑(请参阅https://github.com/flutter/flutter/issues/29357),即使我使用 Wrap 而不是 Column,如果Wrap 的子级是 Rows。不知道如何让剪辑始终如一地工作。

flutter flutter-animation flutter-container
3个回答
25
投票

也许你也可以用 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"),
            ],
          ),
        ),
      ),
    );
  }
}

2
投票

将 @pskink 的评论移至后代的答案:

主要概念是 Align 小部件有一个名为

heightFactor
的属性,它采用 0 到 1 之间的双精度值来缩放其子级的高度(还有一个类似的
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,
    );
  }
}
© www.soinside.com 2019 - 2024. All rights reserved.