Animated grid background in Flutter

Gleb Shalimov
5 min readMar 20, 2024

--

How to add an interesting background to your Flutter app?

I am currently in the process of developing a very interesting application on Flutter. I want to make it minimalistic but not boring. During the design process, I wanted to make an animated background for the main menu.

Grid

I knew how to do it with p5.js, but something generative on Flutter is new to me. I decided not to procrastinate and immediately started looking for something to make such an effect, which I managed to realize using p5.js.

Grid with p5.js
let grid = [];
let cols = 15;
let rows = 15;
let loc = 100;
function setup() {
createCanvas(800, 800);
let rowSize = height/rows;
let colSize = width/cols;

for (let i=0; i<cols; i++){
grid[i] = []
for (let j=0; j<rows; j++){
grid[i][j] = new Cell(colSize/2+i*colSize, rowSize/2+j*rowSize, rowSize/2, i*loc+j*loc);
}
}
}

function draw() {
background(225);
for (let i=0; i<cols; i++){
for (let j=0; j<rows; j++){
grid[i][j].update();
grid[i][j].display();
if (i < cols - 1) {
stroke(0);
line(grid[i][j].x0 + grid[i][j].x, grid[i][j].y0 + grid[i][j].y, grid[i+1][j].x0 + grid[i+1][j].x, grid[i+1][j].y0 + grid[i+1][j].y);
}
if (j < rows - 1) {
stroke(0);
line(grid[i][j].x0 + grid[i][j].x, grid[i][j].y0 + grid[i][j].y, grid[i][j+1].x0 + grid[i][j+1].x, grid[i][j+1].y0 + grid[i][j+1].y);
}
}
}
}

class Cell {
constructor(x0, y0, r, angle){
this.r = r;
this.angle = angle;
this.x0 = x0;
this.y0 = y0;
}

update(){
this.x = this.r*cos(this.angle);
this.y = this.r*sin(this.angle);
this.angle += 0.02;
}

display(){}
}

After that, I decided to read up on how you can implement this with Dart. And here are some cool articles and repos on the topic of generative art in Flutter

If you know of any other resources on this topic, be sure to send them in the comments

Well, it’s development time

This code creates an animated grid consisting of circles and lines that change their shape according to the animation. Let’s describe how it works step by step:

WaveGrid: This is a StatefulWidget that creates an instance of _WaveGridState. It contains an AnimationController that manages the animation.

_WaveGridState: This class is the state for WaveGrid. It initializes the AnimationController in the initState() method and then calls the dispose() method to release resources.

build(): The build() method creates a CustomPaint with animation, which is updated using AnimatedBuilder whenever the animation changes.

_WaveGridPainter: This class is a custom painter for drawing the grid. It receives animation as an argument and uses it to change the shapes of circles and lines based on the current animation value.

paint(): The paint() method draws the grid using the passed animation. It creates a list of points (Cell class), then draws circles and lines between the points.

Cell: This class represents an individual cell in the grid. It stores the coordinates, size, and angle of the point, and updates its coordinates based on the animation.

Now, if you want to customize this grid or make changes, you can consider the following possibilities:

  • Change the number of columns (cols) and rows (rows) to alter the grid size.
  • Adjust the color and thickness of lines (linePaint).
  • Modify the size and color of circles (paint).
  • Change the speed and duration of the animation by adjusting AnimationController parameters.
  • Experiment with parameters like loc and r to alter the shape and movement of points in the grid.

Here’s the resulting code:

return Scaffold(
backgroundColor: backgroundColor,
body: Stack(
children: [
// Background
const Positioned.fill(
child: WaveGrid(), // Animated Background
),
... // Other code
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:knights_graph/values/colors.dart';

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

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

class _WaveGridState extends State<WaveGrid> with TickerProviderStateMixin {
late AnimationController _controller;

@override
void initState() {
super.initState();
// Initializing animation controller
_controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 5),
)..repeat(); // Repeating the animation
}

@override
void dispose() {
_controller.dispose(); // Disposing the animation controller
super.dispose();
}

@override
Widget build(BuildContext context) {
return SizedBox(
// Using MediaQuery to get the screen size
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: AnimatedBuilder(
animation: _controller,
builder: (context, _) {
return CustomPaint(
painter: _WaveGridPainter(
waveAnimation:
_controller.drive(Tween(begin: 1 * pi, end: 5 * pi)),
),
size: Size.infinite,
);
},
),
);
}
}

class _WaveGridPainter extends CustomPainter {
final Animation<double> waveAnimation;

_WaveGridPainter({
required this.waveAnimation,
});

@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = waveGridColor
..style = PaintingStyle.fill;

final linePaint = Paint()
..color = waveGridColor
..strokeWidth = 3 // Line width
..style = PaintingStyle.stroke;

const cols = 15;
const rows = 25;
const loc = 50;
final grid = List.generate(cols, (i) {
return List.generate(rows, (j) {
return Cell(
colSize: size.width / cols,
rowSize: size.height / rows,
x0: size.width / cols * i,
y0: size.height / rows * j,
r: (size.width / cols) / 10, // Adjust point size here
angle: (size.width / cols) * loc / 100 * i + loc * j,
);
});
});

for (var i = 0; i < grid.length; i++) {
for (var j = 0; j < grid[i].length; j++) {
grid[i][j].update(waveAnimation.value);
canvas.drawCircle(
Offset(grid[i][j].x0 * 1.2 + grid[i][j].x,
grid[i][j].y0 * 1.2 + grid[i][j].y),
grid[i][j].r,
paint,
);

// Drawing lines to neighboring points
if (i > 0) {
canvas.drawLine(
Offset(grid[i][j].x0 * 1.2 + grid[i][j].x,
grid[i][j].y0 * 1.2 + grid[i][j].y),
Offset(grid[i - 1][j].x0 * 1.2 + grid[i - 1][j].x,
grid[i - 1][j].y0 * 1.2 + grid[i - 1][j].y),
linePaint,
);
}
if (j > 0) {
canvas.drawLine(
Offset(grid[i][j].x0 * 1.2 + grid[i][j].x,
grid[i][j].y0 * 1.2 + grid[i][j].y),
Offset(grid[i][j - 1].x0 * 1.2 + grid[i][j - 1].x,
grid[i][j - 1].y0 * 1.2 + grid[i][j - 1].y),
linePaint,
);
}
}
}
}

@override
bool shouldRepaint(_WaveGridPainter oldDelegate) => true;
}

class Cell {
final double r;
final double angle;
final double x0;
final double y0;
late double x;
late double y;

Cell({
required double colSize,
required double rowSize,
required this.x0,
required this.y0,
required this.r,
required this.angle,
}) {
x = r * cos(angle);
y = r * sin(angle);
}

void update(double waveAnimation) {
x = r * cos(angle + waveAnimation);
y = r * sin(angle + waveAnimation);
}
}

And this is the result I got

Animated grid background

Considering that my application has to do with chess theme, I am very satisfied with the result of such a grid.

I hope you enjoyed this article. Share your opinion in the comments ❤️

--

--

Gleb Shalimov

Hi 👋 I'm Gleb! I'm a student at IU, in my spare time I love creating cool IT products.