蒙太奇

用于 Flutter 的组织动画组件

快速入门

montage_example

// 1. Define your animations
const entrance = MontageAnimation(
  key: 'entrance',
  duration: Duration(seconds: 2), 
);

const exit = MontageAnimation(
  key: 'exit',
  duration: Duration(seconds: 2),
);

class Home extends StatefulWidget {
  const Home({
    Key? key,
  }) : super(key: key);

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

class _HomeState extends State<Home> with TickerProviderStateMixin {
  MontageController? controller;

  @override
  void initState() {
    controller = MontageController(
      vsync: this,
      initialAnimation: entrance,
    );
    super.initState();
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: ValueListenableBuilder<MontageAnimation?>(
          valueListenable: controller!.current,
          builder: (context, current, child) => Text(current?.key ?? 'none'),
        ),
      ),
      // 2. Wrap your animated widget in a `Montage` with its controller.
      body: Montage( 
        controller: controller!,
        child: BasicScene(),
      ),
      floatingActionButton: FloatingActionButton.extended(
        onPressed: () {
          /// Plays this sequence of animations
          controller!.play([
            entrance,
            exit,
          ]);
        },
        label: Text('Play'),
        icon: Icon(Icons.video_call),
      ),
    );
  }
}


class BasicScene extends StatelessWidget {
  const BasicScene({
    Key? key,
  }) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      crossAxisAlignment: CrossAxisAlignment.center,
      children: [
        // 3. Use the motion widgets that defiens motions for each one of your animations
        Motion(
          motion: {
            // You can use the provider motions or custom ones
            entrance: Motions.combine([
              Motions.rotate(startTurns: -1, endTurns: 0),
              Motions.fadeIn,
            ]),
            exit: Motions.combine([
              Motions.rotate(startTurns: 0, endTurns: 1),
              Motions.fadeOut,
            ]),
          },
          child: FlutterLogo(
            size: 50,
          ),
        ),
        MotionText(
          text: 'Hello world',
          style: TextStyle(
            fontSize: 64,
            color: Colors.black,
            fontWeight: FontWeight.bold,
          ),
          characterMotion: {
            entrance: Motions.fadeFromTop(),
            exit: Motions.fadeToTop(),
          },
          reversedCharacterAnimation: [exit],
          wordMotion: {
            entrance: Motions.fadeIn,
            exit: Motions.fadeOut,
          },
        ),
      ],
    );
  }
}

小部件

声明动画引用

您必须首先将场景的各种动画序列声明为MontageAnimation全局对象。

const entrance = MontageAnimation(
  duration: Duration(seconds: 2),
);

const idle = MontageAnimation(
  duration: Duration(seconds: 3),
);

const exit = MontageAnimation(
  duration: Duration(seconds: 2),
);

蒙太奇

蒙太奇充当您的运动组件的根配置。它需要一个专用的控制器,允许触发动画。

class _HomeState extends State<Home> with TickerProviderStateMixin {
  MontageController? controller;

  @override
  void initState() {
    controller = MontageController(
      vsync: this,
      initialAnimation: entrance,
    );
    super.initState();
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Montage(
        controller: controller!,
        child: Scene(),
      ),
      floatingActionButton: FloatingActionButton(
        icon: Icon(Icons.video_call),
        onPressed: () {
          /// Plays this sequence of animations
          controller!.play([
            entrance,
            exit,
            entrance,
            idle,
            exit,
          ]);
        },
      ),
    );
  }
}

运动

核心组件,可以为根Montage控制器的每个MotionAnimation以不同的方式为子widget设置动画。

Motion(
    motion: {
        entrance: Motions.fadeIn,
        exit: Motions.fadeOut,
    },
    child: FlutterLogo(
        size: 50,
    ),
)

交错动画

如果要逐步为一组小部件设置动画,可以使用Motion.staggered辅助方法。

提供了两个曲线:子项按原始顺序forward(向前)动画,或子项按反向顺序backwards(向后)动画。

如果您希望每个子项的动画在上一动画结束之前开始,您还可以配置overlap(重叠)。

Row(
    mainAxisSize: MainAxisSize.min,
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
    ...Motion.staggered(
        children: [
            Text("FL"),
            Text("UT"),
            Text("TER"),
        ],
        motion: (child, i, forward, backward) {
            return {
                entrance: Motions.curved(
                    forward,
                        Motions.combine([
                            Motions.rotate(startTurns: -1, endTurns: 0),
                            Motions.fadeIn,
                            Motions.scale(begin: 3, end: 1)
                        ]),
                ),
                exit: Motions.curved(
                    backward,
                        Motions.combine([
                        Motions.rotate(startTurns: 0, endTurns: -1),
                        Motions.fadeOut,
                        Motions.scale(begin: 1, end: 3)
                    ]),
                ),
            };
        },
    )
    ],
),

运动

内置

一组运动可以在motions.dart文件中找到。

自定义

运动只是一个组件构建器,它从当前的Animation<double>和要设置动画的子Widget接收。

Widget fadeIn(
  BuildContext context,
  MontageAnimation current,
  Animation<double> animation,
  Widget? child,
) {
    return FadeTransition(
        opacity: animation,
        child: child,
    );
}

GitHub

https://github.com/aloisdeniel/montage