sa_stateless_animation 1.0.1

  • Readme
  • Changelog
  • Example
  • Installing
  • 99

This project is part of the Simple Animations Framework

🚀 Stateless Animation #

Stateless Animation enables developers to craft custom animations with simple widgets.

🌞 Highlights #

  • Create beautiful animations within seconds
  • No struggeling with stateful widgets and AnimationControllers

⛏ Usage #

🛈 The following code snippets use supercharged for syntactic sugar.

Getting started #

Add Simple Animations to your project by following the instructions on the install page.

To learn how to use Stateless Animation:

PlayAnimation widget #

Create your animation by adding the PlayAnimation widget to your app. It takes two mandatory parameters tween and builder.

Tween

The tween is the description of your animation. Mostly it will change a value from A to B. Tweens describe what will happen but not how fast it will happen.

// Animate a color from red to blue
Animatable<Color> myTween = Colors.red.tweenTo(Colors.blue);

Builder

The builder is a function that is called for each new rendered frame of your animation. It takes three parameters: context, child and value.

  • context is your Flutter BuildContext, which should be familiar to you.

  • child is a placeholder for any widget that you can additionally pass in a PlayAnimation widget. It's usage is described further below.

  • value is "current value" of any animated variable. If your tween describes to interpolate from 0 to 100, the variable is a value somewhere between 0 and 100.

How often your builder function is called, depends on the animation duration and the framerate of the device used.

A simple PlayAnimation

The PlayAnimation<?> widget can be typed with the type of the animated variable. This enables us the code type-safe.

PlayAnimation<Color>( // <-- specify type of animated variable
  tween: Colors.red.tweenTo(Colors.blue), // <-- define tween of colors
  builder: (context, child, value) { // <-- builder function
    return Container(
        color: value, // <-- use animated value
        width: 100, 
        height: 100
    );
});

This snippet creates animation of a red square. It's color will fade to blue within one second.

Animation duration

By default the duration of the animation is one second. You set the optional parameter duration to refine that.

PlayAnimation<Color>(
  tween: Colors.red.tweenTo(Colors.blue),
  builder: (context, child, value) {
    return Container(color: value, width: 100, height: 100);
  },
  duration: 5.seconds, // <-- specify duration
);

Now the red square will fade it's color for 5 seconds.

Delay

By default animations will play automatically. You can set the delay parameter to make PlayAnimation wait for a given amount of time.

PlayAnimation<Color>(
  tween: Colors.red.tweenTo(Colors.blue),
  builder: (context, child, value) {
    return Container(color: value, width: 100, height: 100);
  },
  duration: 5.seconds,
  delay: 2.seconds, // <-- add delay
);

The red square will wait for 2 seconds before it starts fading it's color.

Non-linear animation

You can make your animation more interesting by applying a non-linear timing curve to it. By default the tween is animated constantly or linear.

Scenarios where the animation is faster at beginning and slower at the ending are called non-linear animations.

You can enrich your animation with non-linear behavior by supplying a Curve to the curve parameter. Flutter comes with a set of predefined curves inside the Curves class.

PlayAnimation<Color>(
  tween: Colors.red.tweenTo(Colors.blue),
  curve: Curves.easeInOut, // <-- specify curve
  builder: (context, child, value) {
    return Container(color: value, width: 100, height: 100);
  },
);

Working with child widgets

Animations are highly demanding because parts of your apps are recomputed many times per second. It's important to keep these computions as low as possible.

Image the following scenario: There is a Container with a colored background. Inside the Container is a Text. Now we want to animate the background color. There is no need to recompute the Text because the animation only effects the Container color.

In that scenario we have static Text widget. Only the Container need to be update on each frame. We can set the static widget as a child parameter. In our builder function we receive that child widget and can use it inside our animated scene. This way the child widget is only computed once.

PlayAnimation<Color>(
  tween: Colors.red.tweenTo(Colors.blue),
  child: Text("Hello World"), // <-- set child widget
  builder: (context, child, value) { // <-- get child passed into builder function
    return Container(
      child: child, // <-- use child
      color: value,
      width: 100,
      height: 100,
    );
  },
);

Using keys

Flutter tends to recycle used widgets. If your app swaps out a PlayAnimation with another different PlayAnimation in the same second, it may recycle the first one. This may lead to a strange behavior.

All widgets mentioned here support keys to avoid such strange behavior. If you are not familiar with keys then watch this video.

LoopAnimation and MirrorAnimation #

Beside PlayAnimation there are two similar widgets LoopAnimation and MirrorAnimation.

It's configuration is pretty the same as the PlayAnimation.

LoopAnimation

A LoopAnimation repeatly plays the specified tween from the start to the end.

LoopAnimation<Color>(
  tween: Colors.red.tweenTo(Colors.blue), // <-- mandatory
  builder: (context, child, value) { // <-- mandatory
    return Container(child: child, color: value, width: 100, height: 100);
  },
  duration: 5.seconds, // <-- optional
  curve: Curves.easeInOut, // <-- optional
  child: Text("Hello World"), // <-- optional
);

MirrorAnimation

A MirrorAnimation repeatly plays the specified tween from the start to the end, then reverse to the start, then again forward and so on.

MirrorAnimation<Color>(
  tween: Colors.red.tweenTo(Colors.blue), // <-- mandatory
  builder: (context, child, value) { // <-- mandatory
    return Container(child: child, color: value, width: 100, height: 100);
  },
  duration: 5.seconds, // <-- optional
  curve: Curves.easeInOut, // <-- optional
  child: Text("Hello World"), // <-- optional
);

CustomAnimation #

Use CustomAnimation if the animation widgets discussed above aren't sufficient for you use case. Beside all parameters mentioned for PlayAnimation it allows you actively control the animation.

Take over control

The control parameter can be set to the following values:

CustomAnimationControl.VALUEDescription
STOPStops the animation at the current position.
PLAYPlays the animation from the current position reverse to the start.
PLAY_REVERSEPlays the animation from the current position reverse to the start.
PLAY_FROM_STARTReset the position of the animation to 0.0 and starts playing to the end.
PLAY_REVERSE_FROM_ENDReset the position of the animation to 1.0 and starts playing reverse to the start.
LOOPEndlessly plays the animation from the start to the end.
MIRROREndlessly plays the animation from the start to the end, then it plays reverse to the start, then forward again and so on.

You can bind the control value to state variable and change it during the animation. The CustomAnimation will adapt to that.

class _PageState extends State<Page> {
  CustomAnimationControl control = CustomAnimationControl.PLAY; // <-- state variable

  @override
  Widget build(BuildContext context) {
    return CustomAnimation<double>(
      control: control, // <-- bind state variable to parameter
      tween: (-100.0).tweenTo(100.0),
      builder: (context, child, value) {
        return Transform.translate( // <-- animation that moves childs from left to right
          offset: Offset(value, 0),
          child: child,
        );
      },
      child: MaterialButton( // <-- there is a button
        color: Colors.yellow,
        child: Text("Swap"),
        onPressed: toggleDirection, // <-- clicking button changes animation direction
      ),
    );
  }

  void toggleDirection() {
    setState(() { // toggle between control instructions
      control = (control == CustomAnimationControl.PLAY)
          ? CustomAnimationControl.PLAY_REVERSE
          : CustomAnimationControl.PLAY;
    });
  }
}

Start position

Each animation has an internal abstract position. This is a value ranging form 0.0 (start) to 1.0 end.

You can modify the initial position by setting the startPosition parameter.

CustomAnimation<Color>(
    control: CustomAnimationControl.PLAY, // <-- play forward
    startPosition: 0.5, // <-- set start position at 50%
    duration: 10.seconds, // <-- full duration is 10 seconds
    tween: Colors.red.tweenTo(Colors.blue),
    builder: (context, child, value) {
      return Container(color: value, width: 100, height: 100);
    });

This animation will start playing right in the middle of the specified animation and only will animate for 5 seconds.

Listen to AnimationStatus

Behind the scenes there is an AnimationController processing the animation. CustomAnimation exposes it's AnimationStatusListener to enable you to react to finished animations.

You can specify your own listener at the animationStatusListener parameter.

CustomAnimation<Color>(
  tween: Colors.red.tweenTo(Colors.blue),
  builder: (context, child, value) {
    return Container(color: value, width: 100, height: 100);
  },
  animationStatusListener: (AnimationStatus status) {
    if (status == AnimationStatus.completed) {
      print("Animation completed!");
    }
  },
);

1.0.1 #

  • Update: dependencies
  • Update: enforce stricter type rules
  • Update: upgraded to pedantic rule set 1.9.0

1.0.0 #

  • New: PlayAnimation
  • New: LoopAnimation
  • New: MirrorAnimation
  • New: CustomAnimation

example/example.md

📝 Examples #

🛈 Note: These examples uses supercharged for syntactic sugar.

Simple PlayAnimation widget #

Animates the size of a square within a stateless widget.

example1

import 'package:flutter/material.dart';
import 'package:simple_animations/simple_animations.dart';
import 'package:supercharged/supercharged.dart';

void main() => runApp(MaterialApp(home: Scaffold(body: Center(child: Page()))));

class Page extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return PlayAnimation<double>(
      tween: (50.0).tweenTo(200.0), // <-- specify tween (from 50.0 to 200.0)
      duration: 5.seconds, // <-- set a duration
      builder: (context, child, value) { // <-- use builder function
        return Container(
          width: value, // <-- apply animated value obtained from builder function parameter
          height: value, // <-- apply animated value obtained from builder function parameter
          color: Colors.green,
        );
      },
    );
  }
}

PlayAnimation widget with a child #

This example demonstrates the usage of a child widget along with PlayAnimation.

example2

import 'package:flutter/material.dart';
import 'package:simple_animations/simple_animations.dart';
import 'package:supercharged/supercharged.dart';

void main() => runApp(MaterialApp(home: Scaffold(body: Center(child: Page()))));

class Page extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return PlayAnimation<double>(
      tween: (50.0).tweenTo(200.0),
      duration: 5.seconds,
      child: Center(child: Text("Hello!")), // <-- specify widget called "child"
      builder: (context, child, value) { // <-- obtain child from builder function parameter
        return Container(
          width: value,
          height: value,
          child: child, // <-- place child inside your animation
          color: Colors.green,
        );
      },
    );
  }
}

PlayAnimation with non-linear animation #

This example demonstrates a non-linear animation. A pink square increases it's size. The easeOut curve applied to the animation makes it slow down at the end.

example3

import 'package:flutter/material.dart';
import 'package:simple_animations/simple_animations.dart';
import 'package:supercharged/supercharged.dart';

void main() => runApp(MaterialApp(home: Scaffold(body: Center(child: Page()))));

class Page extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return PlayAnimation<double>(
      tween: 0.0.tweenTo(200.0),
      duration: 2.seconds,
      curve: Curves.easeOut,
      builder: (context, child, value) {
        return Container(
          width: value,
          height: value,
          color: Colors.pink,
        );
      },
    );
  }
}

PlayAnimation with delay #

This example demonstrates an animation that waits for two seconds before it starts it's animation.

example4

import 'package:flutter/material.dart';
import 'package:simple_animations/simple_animations.dart';
import 'package:supercharged/supercharged.dart';

void main() => runApp(MaterialApp(home: Scaffold(body: Center(child: Page()))));

class Page extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return PlayAnimation<double>(
      tween: 100.0.tweenTo(200.0),
      duration: 2.seconds,
      delay: 1.seconds,
      curve: Curves.easeOut,
      builder: (context, child, value) {
        return Container(
          width: value,
          height: 50.0,
          color: Colors.orange,
        );
      },
    );
  }
}

LoopAnimation #

Animation that repeatly pops up a text.

example5

import 'package:flutter/material.dart';
import 'package:simple_animations/simple_animations.dart';
import 'package:supercharged/supercharged.dart';

void main() => runApp(MaterialApp(home: Scaffold(body: Center(child: Page()))));

class Page extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return LoopAnimation<double>(
        tween: 0.0.tweenTo(10.0),
        duration: 2.seconds,
        curve: Curves.easeOut,
        builder: (context, child, value) {
          return Transform.scale(
            scale: value,
            child: child,
          );
        },
        child: Text("Hello!"));
  }
}

MirrorAnimation #

This examples endlessly moves a green box from left to right.

example6

import 'package:flutter/material.dart';
import 'package:simple_animations/simple_animations.dart';
import 'package:supercharged/supercharged.dart';

void main() => runApp(MaterialApp(home: Scaffold(body: Center(child: Page()))));

class Page extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MirrorAnimation<double>(
      tween: (-100.0).tweenTo(100.0), // <-- value for offset x-coordinate
      duration: 2.seconds,
      curve: Curves.easeInOutSine, // <-- non-linear animation
      builder: (context, child, value) {
        return Transform.translate(
          offset: Offset(value, 0), // <-- use animated value for x-coordinate
          child: child,
        );
      },
      child: Container(
        width: 100,
        height: 100,
        color: Colors.green,
      ),
    );
  }
}

CustomAnimation in stateless environment #

Example of a pulsing square created with a fully configured CustomAnimation widget.

example7

import 'package:flutter/material.dart';
import 'package:simple_animations/simple_animations.dart';
import 'package:supercharged/supercharged.dart';

void main() => runApp(MaterialApp(home: Scaffold(body: Center(child: Page()))));

class Page extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return CustomAnimation<double>(
      control: CustomAnimationControl.MIRROR,
      tween: 100.0.tweenTo(200.0),
      duration: 2.seconds,
      delay: 1.seconds,
      curve: Curves.easeInOut,
      child: Center(
          child: Text(
        "Hello!",
        style: TextStyle(color: Colors.white, fontSize: 24),
      )),
      startPosition: 0.5,
      animationStatusListener: (status) {
        print("status updated: $status");
      },
      builder: (context, child, value) {
        return Container(
            width: value, height: value, color: Colors.blue, child: child);
      },
    );
  }
}

CustomAnimation in a stateful environment #

This example demonstrates the usage of CustomAnimation in a stateful widget.

example8

import 'package:flutter/material.dart';
import 'package:simple_animations/simple_animations.dart';
import 'package:supercharged/supercharged.dart';

void main() => runApp(MaterialApp(home: Scaffold(body: Center(child: Page()))));

class Page extends StatefulWidget {
  @override
  _PageState createState() => _PageState();
}

class _PageState extends State<Page> {
  CustomAnimationControl control = CustomAnimationControl.PLAY; // <-- state variable

  @override
  Widget build(BuildContext context) {
    return CustomAnimation<double>(
      control: control, // <-- bind state variable to parameter
      tween: (-100.0).tweenTo(100.0),
      builder: (context, child, value) {
        return Transform.translate( // <-- animation that moves childs from left to right
          offset: Offset(value, 0),
          child: child,
        );
      },
      child: MaterialButton( // <-- there is a button
        color: Colors.yellow,
        child: Text("Swap"),
        onPressed: toggleDirection, // <-- clicking button changes animation direction
      ),
    );
  }

  void toggleDirection() {
    setState(() { // toggle between control instructions
      control = (control == CustomAnimationControl.PLAY)
          ? CustomAnimationControl.PLAY_REVERSE
          : CustomAnimationControl.PLAY;
    });
  }
}

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:


dependencies:
  sa_stateless_animation: ^1.0.1

2. Install it

You can install packages from the command line:

with Flutter:


$ flutter pub get

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:


import 'package:sa_stateless_animation/sa_stateless_animation.dart';
  
Popularity:
Describes how popular the package is relative to other packages. [more]
98
Health:
Code health derived from static analysis. [more]
100
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
100
Overall:
Weighted score of the above. [more]
99
Learn more about scoring.

We analyzed this package on Jul 2, 2020, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.8.4
  • pana: 0.13.13
  • Flutter: 1.17.5

Analysis suggestions

Package not compatible with SDK dart

because of import path [sa_stateless_animation] that is in a package requiring null.

Dependencies

Package Constraint Resolved Available
Direct dependencies
Dart SDK >=2.7.0 <3.0.0
flutter 0.0.0
sa_anicoto ^1.0.0 1.0.2
supercharged ^1.4.0 1.6.0
Transitive dependencies
collection 1.14.12 1.14.13
meta 1.1.8
sky_engine 0.0.99
typed_data 1.1.6 1.2.0
vector_math 2.0.8
Dev dependencies
flutter_test
pedantic ^1.9.0