如何在Flutter中制作Alert Dialog摇动动画

How to make Alert Dialog shaking animation in Flutter

我想像这样摇动警告对话框:(不仅是文本,还有整个弹出对话框)

https://www.youtube.com/watch?v=IaHMoifUBSw

如何在用户单击按钮时摇动整个警告对话框?

这可以通过 AnimatedBuilderTransform 小部件来完成。使用 dart:math 中的 sin 函数将 0.01.0 之间的 AnimationController 值映射为具有所需振幅的平滑正弦波。可以在 AnimationController 本身中使用 duration 直接指定周期。

要启动动画,您可以调用 controller.repeat() 使其无限期地 运行,直到您调用 controller.stop(),或者您可以使用 controller.forward() 来 运行一次。

让它摇晃3次然后停止,比如你可以这样做:

onPressed: () async {
  await _controller.forward(from: 0.0);
  await _controller.forward(from: 0.0);
  await _controller.forward(from: 0.0);
},

实际效果如下(请注意 GIF 的帧率限制):

下面附上了完整的源代码供您用作起点。您可以调整durationdistance来改变摇动动画的强度:

import 'dart:math';

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Test(),
    );
  }
}

class Test extends StatelessWidget {
  const Test({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Shaking Dialog Demo')),
      body: Center(
        child: ElevatedButton(
          child: Text('Show Dialog'),
          onPressed: () {
            showDialog(
              context: context,
              builder: (_) => ShakeableDialog(),
            );
          },
        ),
      ),
    );
  }
}

class ShakeableDialog extends StatefulWidget {
  final Duration duration; // how fast to shake
  final double distance; // how far to shake

  const ShakeableDialog({
    Key? key,
    this.duration = const Duration(milliseconds: 300),
    this.distance = 24.0,
  }) : super(key: key);

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

class _ShakeableDialogState extends State<ShakeableDialog>
    with SingleTickerProviderStateMixin {
  late final _controller = AnimationController(
    vsync: this,
    duration: widget.duration,
  );

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

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _controller,
      builder: (BuildContext context, Widget? child) {
        final dx = sin(_controller.value * 2 * pi) * widget.distance;
        return Transform.translate(
          offset: Offset(dx, 0),
          child: child,
        );
      },
      child: AlertDialog(
        title: Text('Alert Dialog Title'),
        content: Text('Try these buttons!'),
        actions: [
          TextButton(
            child: Text('SHAKE 3 TIMES'),
            onPressed: () async {
              await _controller.forward(from: 0.0);
              await _controller.forward(from: 0.0);
              await _controller.forward(from: 0.0);
            },
          ),
          TextButton(
            child: Text('KEEP SHAKING'),
            onPressed: () => _controller.repeat(),
          ),
          TextButton(
            child: Text('STOP SHAKING'),
            onPressed: () => _controller.stop(),
          ),
          TextButton(
            child: Text('CLOSE'),
            onPressed: () => Navigator.of(context).pop(),
          ),
        ],
      ),
    );
  }
}