#19 - Bubble Behaviour for Animated Backgrounds in Flutter

Date: 2018-07-28 12:00 - dart

This is an example of a simple behaviour for the Animated Background package in Flutter.

class Bubble {
  Offset position;
  double radius;
  double targetRadius;
  Color color;
}

class BubbleBehaviour extends Behaviour {
  static Random random = Random();
  List<Bubble> _bubbles;
  static const int numBubbles = 20;
  static const double minTargetRadius = 10.0;
  static const double maxTargetRadius = 50.0;
  static const double growthRate = 10.0;

  @override
  void init() {
    _bubbles = List<Bubble>.generate(numBubbles, (_) {
      Bubble bubble = Bubble();
      _initBubble(bubble);
      return bubble;
    });
  }

  void _initBubble(Bubble bubble) {
    bubble.position = Offset(
      random.nextDouble() * size.width,
      random.nextDouble() * size.height,
    );
    bubble.radius = 0.0;
    bubble.targetRadius = random.nextDouble() * (maxTargetRadius - minTargetRadius) + minTargetRadius;
    bubble.color = Colors.red.shade300;
  }

  @override
  void initFrom(Behaviour oldBehaviour) {
    if (oldBehaviour is BubbleBehaviour) {
      _bubbles = oldBehaviour._bubbles;
    }
  }

  @override
  bool get isInitialized => _bubbles != null;

  @override
  void paint(PaintingContext context, Offset offset) {
    var canvas = context.canvas;
    Paint paint = Paint()
      ..strokeWidth = 3.0
      ..style = PaintingStyle.stroke;
    for (var bubble in _bubbles) {
      paint.color = bubble.color;
      canvas.drawCircle(bubble.position, bubble.radius, paint);
    }
  }

  @override
  bool tick(double delta, Duration elapsed) {
    if (!isInitialized)
      return false;
    for (var bubble in _bubbles) {
      bubble.radius += growthRate * delta;

      if (bubble.radius >= bubble.targetRadius)
        _initBubble(bubble);
    }
    return true;
  }

  @override
  Widget builder(BuildContext context, BoxConstraints constraints, Widget child) {
    return GestureDetector(
      behavior: HitTestBehavior.translucent,
      onTapDown: (details) => _onTap(context, details.globalPosition),
      child: super.builder(context, constraints, child),
    );
  }

  void _onTap(BuildContext context, Offset globalPosition) {
    RenderBox renderBox = context.findRenderObject();
    var localPosition = renderBox.globalToLocal(globalPosition);
    for (var bubble in _bubbles) {
      if ((bubble.position - localPosition).distanceSquared < bubble.radius * bubble.radius * 1.2) {
        _initBubble(bubble);
      }
    }
  }
}

Previous snippet | Next snippet