Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/rodydavis/flutter_implicit_animations

Flutter animations every frame
https://github.com/rodydavis/flutter_implicit_animations

animation flutter game-logic game-loop games

Last synced: 5 days ago
JSON representation

Flutter animations every frame

Awesome Lists containing this project

README

        

# Flutter implicit animations

[Live Demo](https://rodydavis.github.io/flutter_implicit_animations/)

![](/screenshots/cubes.png)

## Problem

There are a few options with flutter when creating animations:

- AnimationController
- AnimatedWidget and other AnimatedX classes
- Animations [package](https://pub.dev/packages/animations)

This solutions all solve a variety of problems and can solve certain use cases.

When building animations you want to do a piece of work every frame and animated it

The build method already does a great job of rendering the state synchronously and will cache widgets that have not changed.

## Solution

This example aims to build animations in a game-like loop with a update, paint and start callback.

```dart
class SimpleExample extends StatefulWidget {
const SimpleExample({Key? key}) : super(key: key);

@override
State createState() => _SimpleExampleState();
}

class _SimpleExampleState extends AnimatedState {
var x = 0.0;
var y = 0.0;
var z = 0.0;

@override
void update(Duration time) {
final t = delta.inMilliseconds / 1000;
x += t;
y += t;
z += t;
}

@override
Widget paint(BuildContext context, BoxConstraints constraints) {
return Material(
child: Center(
child: Container(
width: 100,
height: 100,
transform: Matrix4.identity()
..rotateX(x)
..rotateY(y)
..rotateZ(z),
child: const Text(
'Hello World',
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
),
),
),
),
);
}
}
```

By only changing `State` to `AnimatedState` and the build method to paint method with constraints it is possible to draw every frame.

This also makes it possible to animate multiple things at once and update asynchronously.

## Inline painter

There is also a helper class I use a lot to render a inline canvas.

```dart
CustomPaint(
painter: InlinePainter(
draw: (canvas, size) {
final paint = Paint()
..color = cube.color
..style = PaintingStyle.fill
..strokeWidth = 2;
final rect = Rect.fromLTWH(0, 0, size.width, size.height);
canvas.drawRect(rect, paint);
},
),
)
```

## Example

```dart
import 'dart:math';

import 'package:flutter/material.dart';

import '../widgets/animation.dart';
import '../widgets/inline_painter.dart';

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

@override
State createState() => _HomeScreenState();
}

class _HomeScreenState extends AnimatedState {
final List cubes = [];

double x = 0.0, y = 0.0, z = 0.0;
Color color = Colors.red;
Offset offset = Offset.zero;

void addCube(Size size) {
final randomOffset = Offset(
Random().nextDouble() * size.width,
Random().nextDouble() * size.height,
);
final randomDelta = Random().nextDouble() * 20;
final randomDirection = Offset(
Random().nextDouble() * 2 - 1,
Random().nextDouble() * 2 - 1,
);
final cube = Cube()
..offset = randomOffset
..delta = randomDelta
..direction = randomDirection;
cubes.add(cube);
debugPrint('Cube added: $randomOffset');
}

@override
void start(Duration time) {
final size = MediaQuery.of(context).size;
addCube(size);
super.start(time);
}

@override
void update(Duration time) {
for (final cube in cubes) {
cube.update(time, constraints.biggest);
}
}

@override
Widget paint(BuildContext context, BoxConstraints constraints) {
return Scaffold(
appBar: AppBar(
title: const Text('Animation Example'),
),
body: Stack(
fit: StackFit.expand,
children: [
for (final cube in cubes)
Positioned.fromRect(
rect: cube.rect,
child: CustomPaint(
painter: InlinePainter(
draw: (canvas, size) {
final paint = Paint()
..color = cube.color
..style = PaintingStyle.fill
..strokeWidth = 2;
final rect = Rect.fromLTWH(0, 0, size.width, size.height);
canvas.drawRect(rect, paint);
},
),
),
),
],
),
floatingActionButton: FloatingActionButton.extended(
onPressed: () => addCube(constraints.biggest),
icon: const Icon(Icons.add),
label: Text('Cubes: ${cubes.length}'),
),
);
}
}

class Cube {
Color color = Colors.black;
Offset offset = Offset.zero;
Size size = const Size(100, 100);
double delta = 10;
Offset direction = const Offset(0.1, 0.4);
Rect get rect => offset & size;

void update(Duration time, Size size) {
// Move cube and change direction if out of bounds
offset = offset + direction * delta;

// Top
if (offset.dy < 0) {
direction = Offset(direction.dx, 1);
}
// Bottom
if (offset.dy > size.height) {
direction = Offset(direction.dx, -1);
}
// Left
if (offset.dx < 0) {
direction = Offset(1, direction.dy);
}
// Right
if (offset.dx > size.width) {
direction = Offset(-1, direction.dy);
}

// Change color
color = Color.fromARGB(
255,
(offset.dx * 255 / size.width).round(),
(offset.dy * 255 / size.height).round(),
0,
);
}
}

```