Skip to content

Commit

Permalink
docs: add interactive animation demo and improve README
Browse files Browse the repository at this point in the history
  • Loading branch information
blaugold committed Sep 2, 2022
1 parent 97ec321 commit f2dd050
Show file tree
Hide file tree
Showing 5 changed files with 271 additions and 47 deletions.
118 changes: 72 additions & 46 deletions packages/fleet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,52 @@
> ⚠️ This package is in an early state of development. If you find any bugs or
> have any suggestions, please open an [issue][issues].
`fleet` is an animation framework that animates state changes instead of
**Fleet** is an animation framework that animates state changes instead of
individual values.

This framework has been heavily inspired by the animation framework of
# Getting started

1. Add Fleet to your `pubspec.yaml`:

```shell
flutter pub add fleet
```

2. Add `FleetBinding.ensureInitialized()` to your `main` function:

```dart
import 'package:fleet/fleet.dart';
import 'package:flutter/material.dart';
void main() {
FleetBinding.ensureInitialized();
runApp(MyApp());
}
```

You need to use this binding instead of `WidgetsFlutterBinding`.

This step is currently necessary because of functionality that Fleet requires,
but that is not available in Flutter. This step will become unnecessary if and
when the required functionality becomes available in Flutter. Unfortunately,
this also means that until that point, animations that use Fleet will not work
in tests. The reason is that the test bindings cannot be extended in the same
way that `WidgetsFlutterBinding` can be. You can still use Fleet in tests but
animated values will immediately jump to their final value.

Now you're ready to use Fleet in your Flutter app.

Take a look a the [examples][example_app] or continue to the introduction to
Fleet.

# Introduction

Fleet has been heavily inspired by the animation framework that comes with
[SwiftUI][swiftui animation framework]. If you are familiar with it, you will
find most concepts familiar.

To give you an idea of using this framework I'm going to show how to add a
simple animation to a widget. Below is the unanimated version of the widget:
I'm going to walk you through adding a simple animation to a widget. Below is
the unanimated version of the widget:

```dart
import 'package:fleet/fleet.dart';
Expand Down Expand Up @@ -44,7 +81,7 @@ class _MyWidgetState extends State<MyWidget> {
}
```

Now lets animate the state change:
Now lets animate the state change of `_active`:

```diff
class _MyWidgetState extends State<MyWidget> {
Expand All @@ -68,61 +105,40 @@ Now lets animate the state change:
}
```

All we did was replace `ColoredBox` with `AColoredBox` and use
`setStateWithAnimation` instead of `setState`.
All we did was replace `ColoredBox` with [`AColoredBox`][acoloredbox] and use
[`setStateWithAnimation`][setstatewithanimation] instead of `setState`.

The `AColoredBox` widget is a drop-in replacement for `ColoredBox` that supports
**state-based animation**. Any widget you want to animate through `fleet` needs
to support state-based animation. These widgets don't have any special
parameters related to animation and can be just as well used without animation.
**state-based animation**. Any widget you want to animate through Fleet needs to
support state-based animation. These widgets don't have any special parameters
related to animation and can be just as well used without animation.

`fleet` provides drop-in replacements for a number of generally useful Flutter
Fleet provides drop-in replacements for a number of generally useful Flutter
framework widgets (all with the prefix `A`). Any widget can be made to support
state-based animation through components provided by `fleet` (see
`AnimatableStateMixin`). Issues or PRs for adding support for more widgets are
welcome!
state-based animation through components provided by Fleet (see
[`AnimatableStateMixin`][animatablestatemixin]). Issues or PRs for adding
support for more widgets are welcome!

`setStateWithAnimation` is from an extension on `State`. All state changes
caused by executing the callback will be animated. Note that the callback is not
immediately executed like it is the case for `setState`. Instead, it is executed
as part of building the next frame. In practice this seldomly makes a
difference.

The `AnimationSpec` that we pass to `setStateWithAnimation` specifies how to
animate from the old to the new state. `const AnimationSpec()` is the default
animation spec, which uses `Curves.linear` and animates for 200ms.

# Getting started

1. Add `fleet` to your `pubspec.yaml`:

```shell
flutter pub add fleet
```

2. Add `FleetBinding.ensureInitialized()` to your `main` function:

```dart
import 'package:fleet/fleet.dart';
import 'package:flutter/material.dart';
The [`AnimationSpec`][animationspec] that we pass to `setStateWithAnimation`
specifies how to animate from the old to the new state. `const AnimationSpec()`
is the default animation spec, which uses `Curves.linear` and animates for
200ms.

void main() {
FleetBinding.ensureInitialized();
runApp(MyApp());
}
```

You need to use this binding instead of `WidgetsFlutterBinding`.
# Animatable widgets

This step is currently necessary because of functionality that `fleet` requires,
but is not yet available in Flutter. This step will become unnecessary once the
required functionality is available in Flutter. Unfortunately, this also means
that until that point animations with `fleet` will not work in tests because the
test bindings cannot be extended in the same way `WidgetsFlutterBinding` can.
You can still use the `fleet` framework in tests but animated values will just
jump to their final value.
The following drop-in replacements for Flutter framework widgets are provided
for animating with Fleet:

Now you're ready to use `fleet` in your Flutter app.
- AAlign
- AColoredBox
- AContainer
- ASizedBox

---

Expand All @@ -134,3 +150,13 @@ Now you're ready to use `fleet` in your Flutter app.
[issues]: https://github.com/blaugold/fleet/issues
[swiftui animation framework]:
https://developer.apple.com/documentation/swiftui/animations
[example_app]:
https://github.com/blaugold/fleet/tree/main/packages/fleet/example
[setstatewithanimation]:
https://pub.dev/documentation/fleet/latest/fleet/SetStateWithAnimationExtension/setStateWithAnimation.html
[animatablestatemixin]:
https://pub.dev/documentation/fleet/latest/fleet/AnimatableStateMixin-mixin.html
[animationspec]:
https://pub.dev/documentation/fleet/latest/fleet/AnimationSpec-class.html
[acoloredbox]:
https://pub.dev/documentation/fleet/latest/fleet/AColoredBox-class.html
23 changes: 22 additions & 1 deletion packages/fleet/example/README.md
Original file line number Diff line number Diff line change
@@ -1 +1,22 @@
Example app to demonstrate use of the `fleet` package.
This Flutter project contains a number of small self contained demos to
demonstrate the Fleet framework:

- [interactive_box_fleet.dart] and [interactive_box_flutter.dart] implement the
same interactive animation, one using the Fleet framework and the other using
the standard components from the Flutter framework.
- [simple_declarative_animation.dart] show how to use animate though a
declarative style with `Animated`.
- [simple_imperative_animation.dart] show how to use animate though a imperative
style with `setStateWithAnimation`.
- [stack.dart] show how to stagger animations through `AnimatedSpec.delay`.

[interactive_box_fleet.dart]:
https://github.com/blaugold/fleet/blob/main/packages/fleet/example/lib/interactive_box_fleet.dart
[interactive_box_flutter.dart]:
https://github.com/blaugold/fleet/blob/main/packages/fleet/example/lib/interactive_box_flutter.dart
[simple_declarative_animation.dart]:
https://github.com/blaugold/fleet/blob/main/packages/fleet/example/lib/simple_declarative_animation.dart
[simple_imperative_animation.dart]:
https://github.com/blaugold/fleet/blob/main/packages/fleet/example/lib/simple_imperative_animation.dart
[stack.dart]:
https://github.com/blaugold/fleet/blob/main/packages/fleet/example/lib/stack.dart
74 changes: 74 additions & 0 deletions packages/fleet/example/lib/interactive_box_fleet.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import 'package:fleet/fleet.dart';
import 'package:flutter/material.dart';

void main() {
FleetBinding.ensureInitialized();
runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({super.key});

@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: MyHomePage(),
);
}
}

class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});

@override
State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
static final _distanceColorTween =
ColorTween(begin: Colors.blue, end: Colors.green);

var _alignment = Alignment.center;
var _color = _distanceColorTween.begin!;

@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size / 2;
return Scaffold(
body: GestureDetector(
onPanUpdate: (details) {
setState(() {
_alignment += Alignment(
details.delta.dx / size.width,
details.delta.dy / size.height,
);
final distance = Offset(_alignment.x, _alignment.y).distance;
_color = _distanceColorTween.transform(distance)!;
});
},
onPanEnd: (_) {
setStateWithAnimation(Curves.ease.animation(300.ms), () {
_alignment = Alignment.center;
_color = _distanceColorTween.begin!;
});
},
child: AAlign(
alignment: _alignment,
child: SizedBox.square(
dimension: 400,
child: AColoredBox(
color: _color,
child: const Center(
child: Text(
'Drag me!',
style: TextStyle(color: Colors.white),
),
),
),
),
),
),
);
}
}
103 changes: 103 additions & 0 deletions packages/fleet/example/lib/interactive_box_flutter.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import 'package:fleet/fleet.dart';
import 'package:flutter/material.dart';

void main() {
FleetBinding.ensureInitialized();
runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({super.key});

@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: MyHomePage(),
);
}
}

class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});

@override
State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage>
with SingleTickerProviderStateMixin {
static final _distanceColorTween =
ColorTween(end: Colors.green, begin: Colors.blue);

late final _controller = AnimationController(duration: 300.ms, vsync: this);

final _alignmentTween = AlignmentTween(end: Alignment.center);
final _colorTween = ColorTween(end: _distanceColorTween.begin);

late var _alignment = _alignmentTween.end!;
late var _color = _distanceColorTween.begin!;

@override
void initState() {
super.initState();

final curveTween = CurveTween(curve: Curves.ease);
final alignmentAnimation =
_alignmentTween.chain(curveTween).animate(_controller);
final colorAnimation = _colorTween.chain(curveTween).animate(_controller);

_controller.addListener(() {
setState(() {
_alignment = alignmentAnimation.value;
_color = colorAnimation.value!;
});
});
}

@override
void dispose() {
_controller.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size / 2;
return Scaffold(
body: GestureDetector(
onPanUpdate: (details) {
_controller.stop();
setState(() {
_alignment += Alignment(
details.delta.dx / size.width,
details.delta.dy / size.height,
);
final distance = Offset(_alignment.x, _alignment.y).distance;
_color = _distanceColorTween.transform(distance)!;
});
},
onPanEnd: (_) {
_alignmentTween.begin = _alignment;
_colorTween.begin = _color;
_controller.forward(from: 0);
},
child: Align(
alignment: _alignment,
child: SizedBox.square(
dimension: 400,
child: ColoredBox(
color: _color,
child: const Center(
child: Text(
'Drag me!',
style: TextStyle(color: Colors.white),
),
),
),
),
),
),
);
}
}

0 comments on commit f2dd050

Please sign in to comment.