首次加载列中的小部件的 Flutter 浮动和淡入动画

Flutter Float and Fade In Animation for Widgets in Column with Loading for the First Time

我正在尝试创建动画屏幕,例如,在加载屏幕时,每个屏幕都会包含一个带有小部件的列。我想要做的是,当加载屏幕时,让列中的每个小部件按照它们出现的顺序从屏幕外(从底部)浮动。

我一直在搜索,但似乎找不到解决方案。我怎样才能做到这一点?

我做了一些可能是你想要的事情。然而,为了让每个元素在最后一个元素之后为自己而不是全部在一起设置动画,我必须使用 ListView.builder 来获取 index。如果可以找到元素的 index,则可以使用 Column 小部件。

代码如下:

主屏幕:

import 'package:flutter/material.dart';
import 'package:testing/fade_in_from_bottom.dart';

void main() => runApp(App());

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  final List<Widget> children = <Widget>[
    Container(
      height: 32.0,
      color: Colors.amber,
    ),
    Container(
      height: 32.0,
      color: Colors.black,
    ),
    Container(
      height: 32.0,
      color: Colors.purple,
    ),
    Container(
      height: 32.0,
      color: Colors.green,
    ),
    Container(
      height: 32.0,
      color: Colors.indigo,
    ),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Expanded(
            child: ListView.builder(
              itemCount: children.length,
              itemBuilder: (BuildContext context, int index) {
                return FadeInFromBottom(
                  key: UniqueKey(), // this is very important
                  index: index,
                  child: children[index],
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}

这是我制作的 FadeInFromBottom 小部件:

import 'package:flutter/material.dart';

class FadeInFromBottom extends StatefulWidget {
  @override
  _FadeInFromBottomState createState() => _FadeInFromBottomState();
  final Key key;
  final Duration animationDuration;
  final Duration offsetDuration;
  final Widget child;
  final int index;
  FadeInFromBottom({
    @required this.key,
    @required this.child,
    @required this.index,
    this.animationDuration = const Duration(milliseconds: 400),
    this.offsetDuration = const Duration(milliseconds: 800),
  }) : super(key: key); // this line is important
}

// How to add AutomaticKeepAliveClientMixin? Follow steps 1, 2 and 3:

// 1. add AutomaticKeepAliveClientMixin to FadeInFromBottom widget State
class _FadeInFromBottomState extends State<FadeInFromBottom>
    with TickerProviderStateMixin, AutomaticKeepAliveClientMixin {
  bool get wantKeepAlive => true; // 2. add this line
  double progress = 0.0;

  Animation<double> animation;
  AnimationController controller;

  @override
  void initState() {
    super.initState();
    final Duration offsetDuration =
        widget.offsetDuration == null ? 0.0 : widget.offsetDuration;
    final int index = widget.index == null ? 0 : widget.index;
    
    // we await the future to create the animation delay
    Future.delayed(offsetDuration * index).then(
      (_) {
        controller = AnimationController(
            duration: widget.animationDuration, vsync: this);
        animation = Tween<double>(begin: 0.0, end: 1.0).animate(
          CurvedAnimation(
            parent: controller,
            curve: Curves.linear,
          ),
        )..addListener(() {
            setState(() => progress = animation.value);
          });

        controller.forward();
      },
    );
  }

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

  @override
  Widget build(BuildContext context) {
    super.build(context); // 3. add this line
    return Opacity(
      opacity: progress,
      child: Transform.translate(
        offset: Offset(
          0.0,
          (1.0 - progress) * 999.0,
        ),
        child: widget.child,
      ),
    );
  }
}

不要忘记添加 key: UniqueKey() 属性,没有它动画会乱七八糟。