135 lines
3.0 KiB
Markdown
135 lines
3.0 KiB
Markdown
---
|
||
name: lightweight-flutter-animations
|
||
description: Learn how to create a streamlined animation widget in Flutter that eliminates the need for `setState` by leveraging an abstract class and `SingleTickerProviderStateMixin` for efficient UI updates.
|
||
metadata:
|
||
url: https://rodydavis.com/posts/snippets/lightweight-flutter-animations
|
||
last_modified: Tue, 03 Feb 2026 20:04:28 GMT
|
||
---
|
||
|
||
# Lightweight Flutter Animations
|
||
|
||
|
||
## Overview
|
||
|
||
First we need to create the abstract class:
|
||
|
||
```
|
||
abstract class AnimationWidget<T extends StatefulWidget> extends State<T>
|
||
with SingleTickerProviderStateMixin {
|
||
Duration elapsed = Duration.zero;
|
||
Duration delta = Duration.zero;
|
||
late final Ticker ticker;
|
||
BoxConstraints constraints = const BoxConstraints.tightFor();
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
ticker = createTicker((elapsed) {
|
||
delta = elapsed - this.elapsed;
|
||
this.elapsed = elapsed;
|
||
update(elapsed);
|
||
if (mounted) setState(() {});
|
||
});
|
||
ticker.start();
|
||
WidgetsBinding.instance.addPostFrameCallback(start);
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
ticker.dispose();
|
||
super.dispose();
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return LayoutBuilder(builder: (context, dimens) {
|
||
constraints = dimens;
|
||
return paint(context, dimens);
|
||
});
|
||
}
|
||
|
||
void start(Duration time) {}
|
||
|
||
void update(Duration time);
|
||
|
||
Widget paint(BuildContext context, BoxConstraints constraints);
|
||
}
|
||
```
|
||
|
||
This will let us replace `State` with `AnimationWidget` and not need to call `setState` to rebuild the ui.
|
||
|
||
## Example
|
||
|
||
For the example we need an inline canvas painter:
|
||
|
||
```
|
||
class InlinePainter extends CustomPainter {
|
||
InlinePainter({
|
||
required this.draw,
|
||
super.repaint,
|
||
});
|
||
|
||
final void Function(Canvas canvas, Size size) draw;
|
||
|
||
@override
|
||
void paint(Canvas canvas, Size size) {
|
||
draw(canvas, size);
|
||
}
|
||
|
||
@override
|
||
bool shouldRepaint(CustomPainter oldDelegate) => true;
|
||
}
|
||
```
|
||
|
||
And the example using the new widget class:
|
||
|
||
```
|
||
import 'package:flutter/material.dart';
|
||
import 'package:flutter/scheduler.dart';
|
||
|
||
class SimpleExample extends StatefulWidget {
|
||
const SimpleExample({Key? key}) : super(key: key);
|
||
|
||
@override
|
||
State<SimpleExample> createState() => _SimpleExampleState();
|
||
}
|
||
|
||
class _SimpleExampleState extends AnimationWidget<SimpleExample> {
|
||
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,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|
||
```
|
||
|
||
## Demo |