Flutter - 如何拥有任意数量的隐式动画

Posted

技术标签:

【中文标题】Flutter - 如何拥有任意数量的隐式动画【英文标题】:Flutter - How to have arbitrary number of implicit animations 【发布时间】:2019-01-27 11:04:35 【问题描述】:

我制作了一个小部件,其中包含children 的列表和gapsList<double>,它显示了孩子之间的各自差距。我已经做到了,所以传递一个新的 gaps 列表会导致小部件从旧间隙动画到新间隙(不支持更改间隙数)。

处理隐含动画间隙的最佳方法是什么?

这是我正在寻找的那种行为:

【问题讨论】:

*** 与代码审查无关。有关更多信息,请参阅meta.stackexchange.com/questions/199680/…。相反,您可能想提出“如何做 X?”之类的问题。然后添加您自己的解决方案作为答案。看看别人提供什么 好的,我改一下,谢谢 不错。您还可以在问题上添加所需视觉行为的 gif 吗? :) @RémiRousselet 完成! 【参考方案1】:

为避免不必要的重复,您可以将补间逻辑移动到自定义小部件。

您还可以将List<Widget> childrenList<double> gaps 与自定义Gap 小部件融合在一起。

最终您可以通过separated 构造函数继续使用ListView,并使用我们自定义的Gap 作为分隔符。


考虑到所有这些,最终您的Gap 小部件只是一个具有自定义高度的AnimatedContainer

class Gap extends StatelessWidget 
  final double gap;

  const Gap(this.gap, Key key)
      : assert(gap >= .0),
        super(key: key);

  @override
  Widget build(BuildContext context) 
    return AnimatedContainer(
      duration: const Duration(milliseconds: 250),
      curve: Curves.easeOut,
      height: gap,
    );
  

然后您可以通过以下方式使用它:

ListView.separated(
  itemCount: 42,
  addAutomaticKeepAlives: true,
  itemBuilder: (context, index) 
    return RaisedButton(onPressed: null, child: Text("Foo $index"));
  ,
  separatorBuilder: (context, index) 
    return Gap(10.0);
  ,
),

这里的addAutomaticKeppAlives: true 用于确保离开然后重新出现的项目不会重置其动画。但这不是必需品。

这是一个动态改变间隙大小的完整示例:

class Home extends StatefulWidget 
  @override
  HomeState createState() 
    return new HomeState();
  


class HomeState extends State<Home> 
  final rand = Random.secure();

  @override
  Widget build(BuildContext context) 
    return Scaffold(
      appBar: AppBar(),
      body: Column(
        children: <Widget>[
          RaisedButton(
            onPressed: () 
              setState(() );
            ,
            child: Text("Reroll random gaps"),
          ),
          Expanded(
            child: ListView.separated(
              addAutomaticKeepAlives: true,
              itemCount: 42,
              itemBuilder: (context, index) 
                print("Bar");
                return RaisedButton(onPressed: () , child: Text("Foo $index"));
              ,
              separatorBuilder: (context, index) 
                print("Foo $index");
                return Gap(rand.nextDouble() * 10.0);
              ,
            ),
          ),
        ],
      ),
    );
  

【讨论】:

这太好了,谢谢!总有一个像这样简单的小部件,诀窍是知道什么! 我喜欢认为在颤振中,如果你需要超过 100 行来做某事,那么你做错了:p【参考方案2】:

这是我的解决方案,但我的代码非常混乱。特别是,我不确定为动画、补间、控制器和曲线(这就是我现在正在做的事情)单独列出一个列表是做事的最佳方式。同样在build 函数中执行List&lt;int&gt;.generate(widget.gaps.length, (i) =&gt; i).forEach 似乎是错误的,但通常的for (var i; i&lt;x; i++) 似乎也不是很时髦。

有没有更好的方法来处理这两个问题?

class GappedList extends StatefulWidget 
  final List<Widget> children;
  final List<double> gaps;
  GappedList(@required this.children, @required this.gaps) :
    assert(children != null),
    assert(children.length > 0),
    assert(gaps != null),
    assert (gaps.length >= children.length - 1);
  @override
  GappedListState createState() 
    return new GappedListState();
  


class GappedListState extends State<GappedList> with TickerProviderStateMixin
  List<Animation> _animations = [];
  List<AnimationController> _controllers = [];
  List<CurvedAnimation> _curves = [];
  List<Tween<double>> _tweens;

  @override
  void initState() 
    super.initState();
    _tweens = widget.gaps.map((g) => Tween(
      begin: g ?? 0.0,
      end: g ?? 0.0,
    )).toList();
    _tweens.forEach((t) 
      _controllers.add(AnimationController(
        value: 1.0,
        vsync: this,
        duration: Duration(seconds: 1),
      ));
      _curves.add(CurvedAnimation(parent: _controllers.last, curve: Curves.ease));
      _animations.add(t.animate(_curves.last));
    );
  
  @override
  void dispose() 
    _controllers.forEach((c) => c.dispose());
    super.dispose();
  

  @override
  void didUpdateWidget(GappedList oldWidget) 
    super.didUpdateWidget(oldWidget);
    assert(oldWidget.gaps.length == widget.gaps.length);
    List<Tween<double>> oldTweens = _tweens;
    List<int>.generate(widget.gaps.length, (i) => i).forEach(
      (i) 
        _tweens[i] = Tween<double>(
          begin: oldTweens[i].evaluate(_curves[i]),
          end: widget.gaps[i] ?? 0.0,
        );
        _animations[i] = _tweens[i].animate(_curves[i]);
        if (_tweens[i].begin != _tweens[i].end) 
          _controllers[i].forward(from: 0.0);
        
      
    );
  

  @override
  Widget build(BuildContext context) 
    List<Widget> list = [];
    List<int>.generate(widget.children.length, (i) => i).forEach(
      (i) 
        list.add(widget.children[i]);
        if (widget.children[i] != widget.children.last) 
          list.add(
            AnimatedBuilder(
              animation: _animations[i],
              builder: (context, _) => ConstrainedBox(
                constraints: BoxConstraints.tightForFinite(
                  height: _animations[i].value,
                ),
              ),
            )
          );
        
      
    );
    return ListView(
      primary: true,
      shrinkWrap: true,
      children: list,
    );
  

【讨论】:

以上是关于Flutter - 如何拥有任意数量的隐式动画的主要内容,如果未能解决你的问题,请参考以下文章

CALayer“内容”属性上的隐式动画

ios隐式动画没有执行

如何检测 MySQL 中的隐式提交?

DAE 的隐式/后向欧拉

如何防止特定模板的隐式模板实例化?

如何确定两个优先于重载解析的隐式转换序列?