1. Flutter Animation Widget列表和示例

252 阅读9分钟
  1. Flutter Animation Widget

Animation Widget按照能否管理其生命周期(AnimationController)分为显式动画和隐式动画,以及自定义动画

  1. 隐式动画

    1. 内置隐式动画
    2. 自定义隐式动画
  2. 显式动画

    1. 内置显式动画
    2. 自定义显式动画AnimationWidget、AnimationBuilder
  3. CustomPainter(Canvas动画)

  1. 内置隐式动画AnimatedFoo

    1. Foo为没有动画是Widget类的名字。
    2. 隐式动画是一次性动画,每当动画值改变后,会执行一次
    3. 不需要创建controller,不能控制生命周期

系统内置隐式动画列表:

  • AnimatedAlign

  • AnimatedContainer

  • AnimatedCrossFade

  • AnimatedDefaultTextStyle

  • AnimatedIcon(需要controller是显式动画)

  • AnimatedList

  • AnimatedModalBarrier

  • AnimatedOpacity

  • AnimatedPadding

  • AnimatedPhysicalModel

  • AnimatedPositioned

  • AnimatedPositionedDirectional

  • AnimatedSize

  • AnimatedSwitcher

示例AnimatedOpacity:

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

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MainPage(),
    );
  }
}

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

  @override
  State<MainPage> createState() => _MainPageState();
}

class _MainPageState extends State<MainPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('AnimatedFoo'),
      ),
      body: const Center(child: FooOpacity()),
    );
  }
}

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

  @override
  State<FooOpacity> createState() => _FooOpacityState();
}

class _FooOpacityState extends State<FooOpacity> {
  double _opacity = 0;

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: 400,
      height: 400,
      child: Column(
        children: [
          TextButton(
              onPressed: () {
                setState(() {
                  _opacity = _opacity > 0 ? 0 : 1;
                });
              },
              child: const Text("play")),
          Expanded(
              child: AnimatedOpacity(
            opacity: _opacity,
            duration: const Duration(seconds: 5),
            child: Container(
              color: Colors.red,
            ),
          ))
        ],
      ),
    );
  }
}

示例AnimatedAlign:

import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MainPage(),
    );
  }
}

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

  @override
  State<MainPage> createState() => _MainPageState();
}

class _MainPageState extends State<MainPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('AnimatedFoo'),
      ),
      body: const Center(child: FooAlign()),
    );
  }
}

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

  @override
  State<FooAlign> createState() => _FooAlignState();
}

class _FooAlignState extends State<FooAlign> {

  AlignmentGeometry _alignmentGeometry =  Alignment.topLeft;
  @override
  Widget build(BuildContext context) {

    return SizedBox(
      width: 400,
      height: 400,
      child: Column(
        children: [
          TextButton(
              onPressed: () {
                setState(() {
                  _alignmentGeometry = _alignmentGeometry == Alignment.topLeft?Alignment.bottomRight:Alignment.topLeft;
                });
              },
              child: const Text("play")),
          Expanded(
              child:AnimatedAlign(alignment: _alignmentGeometry, duration: const Duration(seconds: 1),child: Container(
                width: 40,
                height: 40,
                color: Colors.red,),))
        ],
      )



    );
  }
}
  1. 自定义隐式动画

系统提供的隐式动画不能满足的话,基于TweenAnimationBuilder进行自定义动画

示例自定义旋转:

import 'package:flutter/material.dart';
import 'dart:math';

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

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MyHomePage(),
    );
  }
}

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

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  double beginAngle = 0;
  double endAngle = 2 * pi;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('TweenAnimationBuilder'),
      ),
      body: RotationIcon(
        duration: const Duration(seconds: 2),
        beginRotate: beginAngle,
        endRotate: endAngle,
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // 点击事件,设置动画开始
          setState(() {
            double tmp = beginAngle;
            beginAngle = endAngle;
            endAngle = tmp;
          });
        },
        child: const Icon(Icons.play_arrow),
      ),
    );
  }
}

class RotationIcon extends StatelessWidget {
  final Duration duration;
  final double beginRotate;
  final double endRotate;
  const RotationIcon({Key? key, required this.duration, required this.beginRotate, required this.endRotate}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return TweenAnimationBuilder<double>(
        tween: Tween<double>(begin: beginRotate, end: endRotate),
        duration: const Duration(seconds: 2),
        builder: (BuildContext context, double angle, Widget? child) {
          return Transform.rotate(
            angle: angle,
            child: child,
          );
        },
        child: const FlutterLogo(
          size: 80,
        ));
  }
}
  1. 内置显式动画

    1. 需要创建controller,可以控制动画循环、暂停等
    2. 可创建线性动画TweenAnimation或者非线性动画CurveAnimation
  • RotationTransition

  • FadeTransition

  • ScaleTransition

  • SizeTransition

  • SideTransition

  • PositionedTransition

  • DecoratedBoxTransition

  • AnimatedIcon

  • DefaultTextStyleTransition

  • RelativePositionedTransition

示例RotationTransition 线性动画:

import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MyHomePage(),
    );
  }
}

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

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
  /// 会重复播放的控制器
  late final AnimationController _repeatController;

  /// 线性动画
  late final Animation<double> _animation;

  @override
  void initState() {
    super.initState();

    /// 动画持续时间是 3秒,此处的this指 TickerProviderStateMixin
    _repeatController = AnimationController(
      duration: const Duration(seconds: 3),
      vsync: this,
    )..repeat(); // 设置动画重复播放
    // 创建一个从0到360弧度的补间动画 v * 2 * π
    _animation = Tween<double>(begin: 0, end: 1).animate(_repeatController);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('RotationTransition--Tween'),
      ),
      body: Center(
        child: RotationTransition(
          turns: _animation,
          child: const Icon(Icons.arrow_drop_up, size: 180),
        ),
      ),
    );
  }
}

示例RotationTransition 非线性动画:

import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MyHomePage(),
    );
  }
}

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

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
  /// 会重复播放的控制器
  late final AnimationController _repeatController;

  /// 非线性动画
  late final Animation<double> _curveAnimation;

  @override
  void initState() {
    super.initState();
    _repeatController = AnimationController(
      duration: const Duration(seconds: 3),
      vsync: this,
    )..repeat();

    /// Curves 存在多种模式,具体的效果查看Curves源码,有链接展示动画效果
    _curveAnimation = CurvedAnimation(
      parent: _repeatController,
      curve: Curves.easeInCirc,
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('RotationTransition——CurvedAnimation'),
      ),
      body: Center(
        child: RotationTransition(
          turns: _curveAnimation,
          child: const Icon(Icons.arrow_drop_up, size: 180),
        ),
      ),
    );
  }
}
  1. 自定义显式动画

    1. 使用AnimatedWidget
    2. 使用AnimatedBuilder(继承自AnimatedWidget)

示例AnimatedWidget:

import 'package:flutter/material.dart';
import 'dart:math';

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

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MyHomePage(),
    );
  }
}

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

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('AnimatedWidget'),
      ),
      body: const AnimatedIconWidgetPage(),
    );
  }
}

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

  @override
  State<AnimatedIconWidgetPage> createState() => _AnimatedIconWidgetPageState();
}

class _AnimatedIconWidgetPageState extends State<AnimatedIconWidgetPage> with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late CurvedAnimation _curveAnimation;
  late Animation<double> _valueAnimation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(vsync: this, duration: const Duration(seconds: 5));
    _curveAnimation = CurvedAnimation(parent: _controller, curve: Curves.bounceIn);
    _valueAnimation = Tween(begin: 20.0, end: 300.0).animate(_curveAnimation);
    _controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    print("controller build");
    return Center(
      child: AnimatedIconWidget(
        listenable: _valueAnimation,
      ),
    );
  }

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

class AnimatedIconWidget extends AnimatedWidget {
  const AnimatedIconWidget({super.key, required Animation<double> listenable}) : super(listenable: listenable);
  @override
  Widget build(BuildContext context) {
    print("widget build");
    final anim = listenable as Animation<double>;
    return FlutterLogo(size: anim.value);
  }
}

示例AnimatedBuilder:

import 'package:flutter/material.dart';
import 'dart:math';

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

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MyHomePage(),
    );
  }
}

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

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('AnimatedBuilder'),
      ),
      body: const AnimatedIconWidgetPage(),
    );
  }
}

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

  @override
  State<AnimatedIconWidgetPage> createState() => _AnimatedIconWidgetPageState();
}

class _AnimatedIconWidgetPageState extends State<AnimatedIconWidgetPage> with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late CurvedAnimation _curveAnimation;
  late Animation<double> _valueAnimation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(vsync: this, duration: const Duration(seconds: 5));
    _curveAnimation = CurvedAnimation(parent: _controller, curve: Curves.bounceIn);
    _valueAnimation = Tween(begin: 20.0, end: 300.0).animate(_curveAnimation);
    _controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    print("controller build");
    return Center(
      child: AnimatedBuilder(
       animation: _valueAnimation,
        builder: (_context,_child){
         return SizedBox(
           width: _valueAnimation.value,
           height: _valueAnimation.value,
           child: _child,
         );
        },
        child: const FlutterLogo(),
      ),
    );
  }

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

  • drawColor 绘制背景色

  • drawPoints 绘制点/线

  • drawLine 绘制线

  • drawArc 绘制弧/饼

  • drawRect 绘制矩形

  • drawRRect 绘制圆角矩形

  • drawDRRect 绘制嵌套矩形

  • drawCircle 绘制圆形

  • drawOval 绘制椭圆

  • drawPath 绘制路径

  • drawShadow 绘制阴影

  • drawImage 绘制图片

  • drawImageRect 绘制矩形图片

  • drawImageNine 绘制九图

  • drawParagraph 绘制文字段落

  • clipRect 裁剪矩形

  • clipRRect 裁剪圆角矩形

  • clipPath 裁剪由线围成区域

示例CustomPainter:

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

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MyHomePage(),
    );
  }
}

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

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final List<String> titles = [    "drawColor 绘制背景色",    "drawPoints 绘制点/线",    "drawLine 绘制线",    "drawArc 绘制弧/饼",    "drawRect 绘制矩形",    "drawRRect 绘制圆角矩形",    "drawDRRect 绘制嵌套矩形",    "drawCircle 绘制圆形",    "drawOval 绘制椭圆",    "drawPath 绘制路径",    "drawShadow 绘制阴影",    "drawImage 绘制图片",    "drawImageRect 绘制矩形图片",    "drawImageNine 绘制九图",    "drawParagraph 绘制文字段落",    "clipRect 裁剪矩形",    "clipRRect 裁剪圆角矩形",    "clipPath 裁剪由线围成区域",  ];

  late ui.Image _uiImage;
  @override
  void initState() {
    prepareImage();
    super.initState();
  }

  void prepareImage() async {
    Uint8List imageData = base64.decode(base64Strawberry.split(',').last);
    ui.Codec codec = await ui.instantiateImageCodec(imageData);
    ui.FrameInfo frameInfo = await codec.getNextFrame();
    _uiImage = frameInfo.image;
  }

  Widget itemWidget(index) {
    CustomPainter getPainter() {
      final List<CustomPainter> painters = [        ColorPainter(),        PointsPainter(),        LinePainter(),        ArcPainter(),        RectPainter(),        RRectPainter(),        DRRectPainter(),        CirclePainter(),        OvalPainter(),        PathPainter(),        ShadowPainter(),        ImagePainter(_uiImage),        ImageRectPainter(_uiImage),        ImageNinePainter(_uiImage),        ParagraphPainter(),        RectPainter(),        RRectPainter(),        PathPainter(),      ];

      return painters[index];
    }

    return GestureDetector(
      onTap: () {
        Navigator.of(context).push(
          MaterialPageRoute<void>(
            builder: (BuildContext context) {
              return ColorCanvasPage(
                painter: getPainter(),
              );
            },
          ),
        );
      },
      child: Text(titles[index]),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('AnimatedBuilder'),
      ),
      body: ListView.separated(
          itemCount: titles.length,
          separatorBuilder: (_, index) {
            return const SizedBox(
              height: 10,
            );
          },
          itemBuilder: (_, index) {
            return itemWidget(index);
          }),
    );
  }
}

class ColorCanvasPage extends StatelessWidget {
  final CustomPainter painter;
  const ColorCanvasPage({Key? key, required this.painter}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        backgroundColor: Colors.grey,
        appBar: AppBar(),
        body: Center(
          child: CustomPaint(
            size: const Size(600, 600),
            painter: painter,
          ),
        ));
  }
}

class ColorPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    canvas.drawColor(Colors.orange, BlendMode.srcIn);
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return true;
  }
}

class PointsPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    // 绘制点
    canvas.drawPoints(ui.PointMode.points, [Offset(30.0, 30.0), Offset(60.0, 30.0), Offset(60.0, 60.0), Offset(30.0, 60.0)], Paint()..strokeWidth = 10.0);
    canvas.drawPoints(
        ui.PointMode.points,
        [Offset(160.0, 30.0), Offset(190.0, 30.0), Offset(190.0, 60.0), Offset(160.0, 60.0)],
        Paint()
          ..strokeWidth = 10.0
          ..strokeCap = StrokeCap.round);
    // 绘制线
    canvas.drawPoints(
        ui.PointMode.lines,
        [Offset(30.0, 100.0), Offset(60.0, 100.0), Offset(60.0, 130.0), Offset(30.0, 130.0)],
        Paint()
          ..strokeWidth = 4.0
          ..strokeCap = StrokeCap.round);
    // 绘制多边形
    canvas.drawPoints(
        ui.PointMode.polygon,
        [Offset(160.0, 100.0), Offset(190.0, 100.0), Offset(190.0, 130.0), Offset(160.0, 130.0)],
        Paint()
          ..strokeWidth = 4.0
          ..strokeCap = StrokeCap.round);
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return true;
  }
}

class LinePainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    canvas.drawLine(
        Offset(30.0, 30.0),
        Offset(200 - 30.0, 30.0),
        Paint()
          ..strokeWidth = 10.0
          ..strokeCap = StrokeCap.butt);
    canvas.drawLine(
        Offset(30.0, 60.0),
        Offset(200 - 30.0, 60.0),
        Paint()
          ..strokeWidth = 10.0
          ..strokeCap = StrokeCap.round);
    canvas.drawLine(
        Offset(30.0, 90.0),
        Offset(200 - 30.0, 90.0),
        Paint()
          ..strokeWidth = 10.0
          ..strokeCap = StrokeCap.square);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return true;
  }
}

class ArcPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    canvas.drawArc(
        Rect.fromCircle(center: Offset(50.0, 50.0), radius: 50.0),
        0.0,
        pi / 2,
        false,
        Paint()
          ..color = Colors.white
          ..strokeCap = StrokeCap.round
          ..strokeWidth = 6.0
          ..style = PaintingStyle.stroke);
    canvas.drawArc(
        Rect.fromCircle(center: Offset(150.0, 50.0), radius: 50.0),
        0.0,
        pi / 2,
        false,
        Paint()
          ..color = Colors.white
          ..strokeWidth = 6.0
          ..style = PaintingStyle.fill);
    canvas.drawArc(
        Rect.fromCircle(center: Offset(300.0, 100.0), radius: 50.0),
        -pi,
        pi / 2,
        true,
        Paint()
          ..color = Colors.white
          ..strokeWidth = 6.0
          ..style = PaintingStyle.stroke);
    canvas.drawArc(
        Rect.fromCircle(center: Offset(400.0, 100.0), radius: 50.0),
        -pi,
        pi / 3,
        true,
        Paint()
          ..color = Colors.white
          ..strokeWidth = 6.0
          ..style = PaintingStyle.fill);
    canvas.drawArc(
        Rect.fromLTWH(30.0, 150.0, 80.0, 50.0),
        0.0,
        pi * 2 * 2 / 3,
        true,
        Paint()
          ..color = Colors.white
          ..style = PaintingStyle.fill);
    canvas.drawArc(
        Rect.fromLTWH(150.0, 150.0, 80.0, 50.0),
        0.0,
        pi * 2 * 2 / 3,
        true,
        Paint()
          ..color = Colors.white
          ..strokeWidth = 6.0
          ..style = PaintingStyle.stroke);
    canvas.drawArc(
        Rect.fromPoints(Offset(250.0, 150.0), Offset(300.0, 200.0)),
        0.0,
        5.0,
        true,
        Paint()
          ..color = Colors.white
          ..style = PaintingStyle.fill);
    canvas.drawArc(
        Rect.fromPoints(Offset(350.0, 150.0), Offset(400.0, 200.0)),
        0.0,
        5.0,
        true,
        Paint()
          ..color = Colors.white
          ..strokeWidth = 6.0
          ..style = PaintingStyle.stroke);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return true;
  }
}

class RectPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    canvas.drawRect(
        Rect.fromPoints(Offset(30.0, 30.0), Offset(120.0, 60.0)),
        Paint()
          ..color = Colors.white
          ..strokeWidth = 6.0
          ..style = PaintingStyle.stroke);
    canvas.drawRect(
        Rect.fromPoints(Offset(160.0, 30.0), Offset(260.0, 80.0)),
        Paint()
          ..color = Colors.white
          ..style = PaintingStyle.fill);
    canvas.drawRect(
        Rect.fromLTRB(300.0, 30.0, 400.0, 50.0),
        Paint()
          ..color = Colors.white
          ..style = PaintingStyle.fill);
    canvas.drawRect(
        Rect.fromLTWH(30.0, 140.0, 100.0, 70.0),
        Paint()
          ..color = Colors.white
          ..strokeWidth = 6.0
          ..style = PaintingStyle.stroke);
    canvas.drawRect(
        Rect.fromCircle(center: Offset(200.0, 160.0), radius: 40.0),
        Paint()
          ..color = Colors.white
          ..strokeWidth = 6.0
          ..style = PaintingStyle.stroke);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return true;
  }
}

class RRectPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    // RRect.fromLTRBXY 方式
    canvas.drawRRect(
        RRect.fromLTRBXY(30.0, 30.0, 100.0, 80.0, 8.0, 8.0),
        Paint()
          ..color = Colors.white
          ..strokeWidth = 4.0
          ..style = PaintingStyle.stroke);
    canvas.drawRRect(
        RRect.fromLTRBXY(120.0, 30.0, 220.0, 80.0, 8.0, 18.0),
        Paint()
          ..color = Colors.white
          ..style = PaintingStyle.fill);
    // RRect.fromLTRBR 方式
    canvas.drawRRect(
        RRect.fromLTRBR(240.0, 30.0, 340.0, 80.0, Radius.circular(8.0)),
        Paint()
          ..color = Colors.white
          ..strokeWidth = 4.0
          ..style = PaintingStyle.stroke);
    // RRect.fromLTRBAndCorners 方式
    canvas.drawRRect(
        RRect.fromLTRBAndCorners(30.0, 120.0, 110.0, 160.0,
            topLeft: Radius.circular(20.0), topRight: Radius.circular(20.0), bottomRight: Radius.circular(5.0), bottomLeft: Radius.circular(5.0)),
        Paint()
          ..color = Colors.white
          ..strokeWidth = 4.0
          ..style = PaintingStyle.stroke);
    // RRect.fromRectAndCorners 方式
    canvas.drawRRect(
        RRect.fromRectAndCorners(Rect.fromLTWH(120.0, 120.0, 100.0, 70.0),
            topLeft: Radius.circular(20.0), topRight: Radius.circular(5.0), bottomRight: Radius.circular(5.0), bottomLeft: Radius.circular(20.0)),
        Paint()
          ..color = Colors.white
          ..strokeWidth = 4.0
          ..style = PaintingStyle.stroke);
    // RRect.fromRectAndRadius 方式
    canvas.drawRRect(
        RRect.fromRectAndRadius(Rect.fromLTWH(240.0, 120.0, 100.0, 80.0), Radius.elliptical(20.0, 18.0)),
        Paint()
          ..color = Colors.white
          ..strokeWidth = 4.0
          ..style = PaintingStyle.stroke);
    // RRect.fromRectXY 方式
    canvas.drawRRect(
        RRect.fromRectXY(Rect.fromCircle(center: Offset(70.0, 240.0), radius: 40.0), 8.0, 8.0),
        Paint()
          ..color = Colors.white
          ..strokeWidth = 4.0
          ..style = PaintingStyle.stroke);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return true;
  }
}

class DRRectPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    canvas.drawDRRect(
        RRect.fromRectXY(Rect.fromCircle(center: Offset(90.0, 120.0), radius: 60.0), 8.0, 8.0),
        RRect.fromRectXY(Rect.fromCircle(center: Offset(90.0, 120.0), radius: 50.0), 8.0, 8.0),
        Paint()
          ..color = Colors.white
          ..strokeWidth = 4.0
          ..style = PaintingStyle.stroke);
    canvas.drawDRRect(
        RRect.fromRectXY(Rect.fromCircle(center: Offset(270.0, 120.0), radius: 60.0), 8.0, 8.0),
        RRect.fromRectXY(Rect.fromCircle(center: Offset(270.0, 120.0), radius: 50.0), 8.0, 8.0),
        Paint()
          ..color = Colors.white
          ..strokeWidth = 4.0
          ..style = PaintingStyle.fill);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return true;
  }
}

class CirclePainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    canvas.drawCircle(
        Offset(90.0, 120.0),
        60.0,
        Paint()
          ..color = Colors.white
          ..strokeWidth = 4.0
          ..style = PaintingStyle.stroke);
    canvas.drawCircle(
        Offset(270.0, 120.0),
        60.0,
        Paint()
          ..color = Colors.white
          ..style = PaintingStyle.fill);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return true;
  }
}

class OvalPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    canvas.drawOval(
        Rect.fromLTRB(30.0, 30.0, 150.0, 80.0),
        Paint()
          ..color = Colors.white
          ..strokeWidth = 3.0
          ..style = PaintingStyle.stroke);
    canvas.drawOval(
        Rect.fromLTRB(210.0, 30.0, 330.0, 80.0),
        Paint()
          ..color = Colors.white
          ..strokeWidth = 3.0
          ..style = PaintingStyle.fill);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return true;
  }
}

class PathPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    canvas.drawPath(
        Path()
          ..moveTo(30.0, 30.0)
          ..lineTo(80.0, 30.0)
          ..lineTo(80.0, 80.0)
          ..lineTo(30.0, 80.0)
          ..close(),
        Paint()
          ..color = Colors.white
          ..strokeWidth = 3.0
          ..style = PaintingStyle.stroke);

    canvas.drawPath(
        Path()
          ..moveTo(100.0, 30.0)
          ..lineTo(100.0, 80.0)
          ..lineTo(150.0, 80.0)
          ..close(),
        Paint()
          ..color = Colors.white
          ..style = PaintingStyle.fill);
    canvas.drawPath(
        Path()
          ..moveTo(170.0, 30.0)
          ..lineTo(220.0, 30.0)
          ..lineTo(170.0, 80.0)
          ..lineTo(220.0, 80.0)
          ..close(),
        Paint()
          ..color = Colors.white
          ..strokeWidth = 3.0
          ..style = PaintingStyle.stroke);

    canvas.drawPath(
        Path()
          ..moveTo(240.0, 80.0)
          ..lineTo(290.0, 80.0)
          ..lineTo(265.0, 30.0)
          ..close(),
        Paint()
          ..color = Colors.white
          ..strokeWidth = 3.0
          ..style = PaintingStyle.stroke);

    canvas.drawPath(
        Path()
          ..moveTo(30.0, 120.0)
          ..lineTo(120.0, 120.0)
          ..lineTo(30.0, 160.0)
          ..lineTo(120.0, 160.0)
          ..addRect(Rect.fromLTWH(180.0, 130.0, 120.0, 70.0))
          ..addOval(Rect.fromLTWH(190.0, 140.0, 100.0, 50.0))
          ..moveTo(30.0, 230.0)
          ..lineTo(160.0, 230.0)
          ..close(),
        Paint()
          ..color = Colors.white
          ..strokeWidth = 3.0
          ..style = PaintingStyle.stroke);
    canvas.drawPath(
        Path()..arcTo(Rect.fromCircle(center: Offset(70, 270), radius: 50), 0.0, pi, false),
        Paint()
          ..color = Colors.white
          ..strokeWidth = 3.0
          ..style = PaintingStyle.stroke);
    canvas.drawPath(
        Path()
          ..moveTo(210.0, 260.0)
          ..cubicTo(210.0, 330.0, 270.0, 300.0, 330.0, 250.0),
        Paint()
          ..color = Colors.red
          ..strokeWidth = 3.0
          ..style = PaintingStyle.stroke);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return true;
  }
}

class ShadowPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    canvas.drawShadow(
        Path()
          ..moveTo(30.0, 30.0)
          ..lineTo(120.0, 30.0)
          ..lineTo(120.0, 60.0)
          ..lineTo(30.0, 60.0)
          ..close(),
        Colors.red,
        4,
        false);
    canvas.drawShadow(
        Path()
          ..moveTo(150.0, 30.0)
          ..lineTo(250.0, 30.0)
          ..lineTo(250.0, 60.0)
          ..lineTo(150.0, 60.0),
        Colors.red,
        10,
        false);
    canvas.drawShadow(
        Path()
          ..moveTo(280.0, 30.0)
          ..lineTo(380.0, 30.0)
          ..lineTo(380.0, 60.0)
          ..lineTo(280.0, 60.0)
          ..close(),
        Colors.red,
        4,
        true);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return true;
  }
}

class ImagePainter extends CustomPainter {
  final ui.Image image;
  ImagePainter(this.image);

  @override
  void paint(Canvas canvas, Size size) async {
    print("paint size:$size imageSize:${Size(image.width * 1.0, image.height * 1.0)}");
    canvas.drawColor(Colors.orange, BlendMode.srcIn);
    canvas.drawImage(image, const Offset(0.0, 0.0), Paint());
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return true;
  }
}

class ImageRectPainter extends CustomPainter {
  final ui.Image image;
  ImageRectPainter(this.image);

  @override
  void paint(Canvas canvas, Size size) {
    print("paint size:$size imageSize:${Size(image.width * 1.0, image.height * 1.0)}");
    canvas.drawImageRect(image, const Rect.fromLTWH(0, 0, 200, 100), Rect.fromLTWH(0, 0, image.width.toDouble(), image.height.toDouble()), Paint());
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return true;
  }
}

// 还没有弄清楚原理
class ImageNinePainter extends CustomPainter {
  final ui.Image image;
  ImageNinePainter(this.image);

  @override
  void paint(Canvas canvas, Size size) {
    print("paint size:$size imageSize:${Size(image.width * 1.0, image.height * 1.0)}");

    canvas.drawImageNine(image, Rect.fromCenter(center: Offset(100,100),width: 200,height: 100), Rect.fromLTWH(0, 0, 400, 200), Paint());
    //
    canvas.drawImageRect(image, Rect.fromCenter(center: Offset(100,100),width: 200,height: 100), Rect.fromLTWH(300, 300, 200, 100), Paint());
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return true;
  }
}

class ParagraphPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    ui.ParagraphBuilder pb = ui.ParagraphBuilder(ui.ParagraphStyle(
      textAlign: TextAlign.center,
      fontWeight: FontWeight.w600,
      fontStyle: FontStyle.normal,
      fontSize: 18,
    ))
      ..pushStyle(ui.TextStyle(color: Colors.blue))
      ..addText("画十个姑娘陪着你");

    ui.ParagraphConstraints pc = const ui.ParagraphConstraints(width: 320);
    ui.Paragraph paragraph = pb.build()..layout(pc);
    canvas.drawParagraph(paragraph, const Offset(30, 30));
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return true;
  }
}

class ClipRectPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {}

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return true;
  }
}

class ClipRRectPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {}

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return true;
  }
}

class ClipPathPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {}

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return true;
  }
}

其中图片的Base64字符串可以在线生成:

地址:tool.chinaz.com/tools/imgto…

  1. 第三方动画库

  1. lottie-flutter:

    1.    乐透动画,使用json数据来表示动画

pub.flutter-io.cn/packages/lo…

  1. shimmer:

    1.   一种阴影动画
    2.   pub.flutter-io.cn/packages/sh…
  2. simple_animations:

    1.   简单动画集合
    2.   pub.flutter-io.cn/packages/si…
  3. animations:

    1.   官方动画示例,主要是页面跳转导航效果
    2.   pub.flutter-io.cn/packages/an…
  1. 好玩的动画效果

  1. 文字闪烁效果

import 'package:flutter/material.dart';

main() {
  runApp(const Myapp());
}

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(home: ExamplePage());
  }
}

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

  @override
  State<ExamplePage> createState() => _ExamplePageState();
}

class _ExamplePageState extends State<ExamplePage> with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;
  int i = 0;

  @override
  void initState() {
    _controller = AnimationController(duration: const Duration(milliseconds: 1000), vsync: this);
    _animation = Tween<double>(begin: 0.0, end: 1.0).animate(_controller);
    super.initState();
  }

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

  @override
  Widget build(BuildContext context) {
    Size size = MediaQuery.of(context).size;
    return Scaffold(
      backgroundColor: Colors.white,
      body: GestureDetector(
        onTap: (){
          if(_controller.isAnimating){
            _controller.stop();
          }else{
            _controller.repeat();
          }
        },
        child: Center(
          child: SizedBox(
            width: size.width,
            height: size.height,
            child: AnimatedBuilder(
              animation: _animation,
              builder: (context, child) {
                double value = _animation.value;
                Gradient gradient = LinearGradient(
                  colors: const [Colors.grey, Colors.white, Colors.grey],
                  stops: [value - 0.2, value, value + 0.2],
                );
                Shader shader = gradient.createShader(Rect.fromLTWH(0, 0, size.width, size.height));
                return Column(
                  children: [
                    Text(
                      '文字闪烁效果',
                      style: TextStyle(
                        fontSize: 28.0,
                        foreground: Paint()..shader = shader,
                        // foreground: Paint()..color = Colors.red
                      ),
                    ),
                    const SizedBox(height: 20,),
                    Container(
                      width: size.width,
                      height: 40,
                      decoration: BoxDecoration(gradient:gradient ),
                    )
                  ],
                );
              },
            ),
          ),
        ),
      ),
    );
  }
}