Animating CustomPainter is easy. We just need to repaint the whole canvas on each AnimationController.value change.

Create AnimationController in a StatefulWidget that is parent of the CustomPainter:

class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
  AnimationController _animationController;

  _MyAppState() {
    // Start the animation in 2 seconds after screen open.
    Future.delayed(Duration(seconds: 2))
        .then((_) => _animationController.forward());
  }

  @override
  void initState() {
    super.initState();
    _animationController = AnimationController(
      vsync: this,
      duration: Duration(seconds: 1),
    );
  }

  @override
  void dispose() {
    // Properly dispose the controller. This is important!
    _animationController.dispose();
    super.dispose();
  }

  ...
}

In our CustomPainter create Tween objects for each transform we want to apply. In this example, we transform object offset, scale and color. Therefore we create 3 Tweens.

class AnimatedCustomPainter extends CustomPainter {
  final _paint = Paint();
  final Animation<double> _size;
  final Animation<double> _offset;
  final Animation<Color> _color;

  AnimatedCustomPainter(Animation<double> animation)
      : _size = Tween<double>(begin: 50, end: 150).animate(animation),
        _offset = Tween<double>(begin: 200, end: 0).animate(animation),
        _color =
            ColorTween(begin: Colors.red, end: Colors.blue).animate(animation),
        super(repaint: animation);

  @override
  void paint(Canvas canvas, Size size) {
    _paint.color = _color.value;
    canvas.drawCircle(
      Offset(
        size.width / 2,
        size.height / 2 + _offset.value,
      ),
      _size.value,
      _paint,
    );
  }

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

Note that we call super(repaint: animation) when constructing AnimatedCustomPainter. The CustomPainter would then repaint the canvas on each animation.value change. If we don’t call super(repaint: animation), our painter won’t animate.

That’s it! The full source code can be found below:

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

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

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
  AnimationController _animationController;

  _MyAppState() {
    // Start the animation in 2 seconds after screen open.
    Future.delayed(Duration(seconds: 2))
        .then((_) => _animationController.forward());
  }

  @override
  void initState() {
    super.initState();
    _animationController = AnimationController(
      vsync: this,
      duration: Duration(seconds: 1),
    );
  }

  @override
  void dispose() {
    // Properly dispose the controller. This is important!
    _animationController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Container(
        color: Colors.white,
        child: CustomPaint(
          painter: AnimatedCustomPainter(
            _animationController.view,
          ),
        ),
      ),
    );
  }
}

class AnimatedCustomPainter extends CustomPainter {
  final _paint = Paint();
  final Animation<double> _size;
  final Animation<double> _offset;
  final Animation<Color> _color;

  AnimatedCustomPainter(Animation<double> animation)
      : _size = Tween<double>(begin: 50, end: 150).animate(animation),
        _offset = Tween<double>(begin: 200, end: 0).animate(animation),
        _color =
            ColorTween(begin: Colors.red, end: Colors.blue).animate(animation),
        super(repaint: animation);

  @override
  void paint(Canvas canvas, Size size) {
    _paint.color = _color.value;
    canvas.drawCircle(
      Offset(
        size.width / 2,
        size.height / 2 + _offset.value,
      ),
      _size.value,
      _paint,
    );
  }

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