Simple reactive animations in your Flutter apps.

Overview

just.motion

Flutter package to create organic motion transitions.

Why?

Why not? the basic difference with Tweens is that the change in value looks much cooler, it feels more "organic", while Tweens are more... "mechanical".

just.motion is not based on Duration and interpolated percentage values from 0-1; but rather on distance between the current value and the target value.

On the other hand, it doesn't need a Ticker provider, nor Implicit widgets to compensate the code boilerplate. Also, no need for AnimationController.

There's a single internal Ticker, that manages it's own state based on the auto-subscribed MotionValues. When there're no active motions, it just stops.

Also, it has a very simple reactive support when MotionValues (base class for EaseValue and SpringValue) are consumed inside a Motion() or MotionBuilder() widgets.

Is a great option for your app's animations! as subscriptions and memory management are also automatic when using MotionValue with the provided Motion(s) widgets.

The Motion Value

Here's how it works:

Basic easing is a well known technique in games for light computation of movements, is based on proportional velocity, and his cousin, spring, is based on proportional acceleration.

You set a target value, MotionValue calculates the distance, and the movement it applies is proportional to the distance: bigger distance, faster the motion.

So, as acceleration is proportional to the distance, the further the target, the faster the value moves, as it gets closer and closer to the target, it hardly changes the value... that's why you can configure minDistance to tell the motion when is time to stop.

While on springs, the acceleration is proportional to the distance, if target is far away from value, a lot of acceleration is provided, increasing the velocity very quickly. Unlike easing, as the value approaches the target, less and less acceleration is applied, but still has an ongoing acceleration, as it flies pass the target, the acceleration pulls it back, while friction helps the value to settle down.

Status notifier

In MotionValues you can listen to status changes:

height.addStatusListener((){
  print(height.status);
});

Check MotionStatus.values to see the current available status. (Might slightly change in the near future).

Currently available status:

enum MotionStatus {
  idle, /// initial status, without target.
  target, /// when value reaches target.
  activate, /// when target != value, and object is added in the ticker.
  deactivate, /// when a `delay()` is called while `target!=value` (moving status)
  delayComplete,  /// when a `delay()` ends, and moving starts.
  moving, /// while `value` is moving towards `target`.
  disposed  /// when the `MotionValue` is disposed from memory (can't be used again).  
}

Stateless hot reload

When you create a MotionValue in a StatelessWidget, or inside a build(BuildContext) scope. You should notify just.motion to auto dispose the variable for hot-reload.

Use MotionValue.stateless for that. It will assure the disposal of the instance from the running Ticker:

final height = 10.ease( target: 20, stateless: true);

stateless only works when using Motion or MotionBuilder widgets, not with AnimatedBuilder.

Ease Motion

Declare a var that you will use to animate some widget property:

NOTE: When using the ease() extension, (like 10.ease()), int and double nums will use EaseValue which is based on double. Is up to you to cast the value to int: (example: height().round(), or height().toInt()).

final height = EaseValue( 30 );
///or the extension approach.
final height = 30.ease();

final bgColor = EaseColor( Colors.red );
final bgColor = Colors.red.ease();

You can configure the target value, and other motion properties right away in both declarations.

To change the target value after object initialization:

/// `MotionValue` is a callable instance. So can change target as if it was a method.
height(100);

/// If you need to change a motion property, you can use:
height.to( 100, ease: 30, minDistance: .1 );

/// or just modify the target property.
height.target = 100;

This is what makes just.motion shine. You can change the target anytime, and it will smoothly transition value towards it, without mechanic or abrupt visual cuts, like time based Tweens.

To read the current value:

print( height());
// or
print( height.value );

The motion objects detects when target is modified, and runs the simulation accordingly. A motion object is idle, when value reaches target, and will be unsubscribed from the ticker provider.

To stop the animation, set the motion.value = motion.target, otherwise the ticker will keep running until the values are closed enough to hit the minDistance threshold, and deactivate themselves.

You can set an absolute value to rebuild the widget, preventing the animation, with: height.value = height.target = 10;

or better yet: height.set( 10 );

Spring Motion

Much like EaseValue, SpringValue is another type of motion, just play with the parameters. Watch out the minDistance, probably for drastic bouncing, you will need to provide a very small number (like .0001)... use at discretion, and experiment with the values.

final height = SpringValue(10);

final height = 10.spring()

Here's an example of a bouncing button.

class SpringyButton extends StatelessWidget {
  final Widget child;
  final double pressScale;
  SpringyButton({
    Key? key,
    required this.child,
    this.pressScale = 0.75,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final scaleValue = 1.spring(minDistance: .00025, spring: .1, stateless:true);
    return GestureDetector(
      onTapDown: (e) => scaleValue.to(pressScale, friction: .85),
      onTapUp: (e) => scaleValue.to(1, friction: .92),
      onTapCancel: () => scaleValue.to(1, friction: .92),
      child: MotionBuilder(
        builder: (BuildContext context, Widget? child) => Transform.scale(
          transformHitTests: false,
          scale: scaleValue(),
          child: child,
        ),
        child: child,
      ),
    );
  }
}

The Widgets

MotionValue is a ChangeNotifier, so you can use AnimationBuilder:

@override
  Widget build(BuildContext context) {
    /// Warning: you will not be able to dispose these variables on hot-reload. 
    final height = 24.0.ease(target: 120, ease: 23);
    final bgColor = Colors.black12.ease(ease: 45);
    bgColor.to(Colors.red);

    /// delay() is defined in seconds, will deactivate the ticker call until it hits the timeout. 
    height.delay(1);

    return Center(
      child: Material(
        child: AnimatedBuilder(
          animation: Listenable.merge([height, bgColor]),
          builder: (context, child) {
            return Container(
              height: height(),
              color: bgColor(),
              child: Center(
                child: Text('height: ${height().toStringAsFixed(2)}'),
              ),
            );
          },
        ),
      ),
    );
  }

But motion provides a simpler Widget to repaint your animation.

If you just need to paint a "leaf" widget, so have no need to use the child optimization of AnimatedBuilder, nor context, you can't got simpler than Motion:

return Motion(
  () => Container(
    height: height(),
    color: bgColor(),
    child: Center(
      child: Text('height: ${height().ringAsFixed(2)}'),
    ),
  ),
);

When you need to cache the child rebuild, like AnimatedBuilder, you can use MotionBuilder:

return MotionBuilder(
  builder: (context, child) => Container(
    height: height(),
    color: bgColor(),
    child: Center(
      child: child,
    ),
  ),
  child: Text('animating height'),
);

Both widgets will dispose the motion values when they are removed from the widget, if the object isn't consumed by another Listener.

Performance Considerations

  • As there's a single ticker provider running for all MotionValue instances, the lifecycle of this Ticker is persistent through the lifetime of your app. Is not tied up to a Widget's State, meaning that is totally possible to have lots of concurrent and actives MotionValue, even when nothing is consuming those values (you can inspect that in the Flutter Performance tab in your IDE). When you initialize a motion object, and set a target different than the value... the Ticker will start processing the object, no matter if you are consuming the value to repaint a Widget or not. This is not a big penalty on performance by any means, as Flutter does it all the time, but be sure to orchestrate properly the target assignment, when you actually will consume the value.

  • If you are using a StatefulWidget, or some other state management solution that provides you with Widget lifecycles, you can manually call motion.dispose(). Although memory is managed internally, didn't find any leaks so far.

  • If you are composing nested Animations, or reusable Widgets based on just.motion, is better to avoid child rebuilds in your tree. Prefer the usage of MotionBuilder() for those scenarios. And yes, you can deeply nest motion objects into Motion() and MotionBuilder() and they will take the appropiate values in their builder function scope.

  • Remember, you can help the Flutter Engine to decide where to cache some part of the Widget tree, that will rebuild independently from the rest of the screen with RepaintBoundry(), as setState() can propagate repainting up and down the tree. Which can lead to a percieved lost frames. Apparently this is more notorious on desktop targets, but is always cool to pay attention to those details... if you have a "big" area of your app that's animated somehow, and you see the performance isn't so great, try to enclose the widget with RepaintBoundry().

Installation

just_motion is in active developing and testing stages. In a couple of days it will be available in pub.dev

In the meantime, if you wanna use it and help me improve it, you should be using dart >= 2.12

  • Just use this repo url in your pubspec.yaml
dependencies:
  just_motion:
    git: https://github.com/roipeker/just_motion.git
dependencies:
  just_motion:
    git:
      url: https://github.com/roipeker/just_motion.git
      ref: c6ee99cbffce216e0c4587c1005f4104057d44a3
  • Run flutter pub get

  • Then import just_motion in your code:

import 'package:just_motion/just_motion.dart';

Now go, and make your apps comes to life.

Happy coding!

How to contribute

just.motion is open for contributions:

  • Helping to fix typos, adjust, translate the readme into other languages.
  • Offering PRs for code/tests.
  • Making examples.
  • Making articles/videos.
  • Including new features or discussing the current API.

Any contribution is welcome!

Getting Started

This project is a starting point for a Dart package, a library module containing code that can be shared easily across multiple Flutter or Dart projects.

For help getting started with Flutter, view our online documentation, which offers tutorials, samples, guidance on mobile development, and a full API reference.

You might also like...

A flutter package that adds support for vector data based animations.

animated_vector Description and inspiration A package that adds support for vector data based animations. The data format is heavily inspired from the

Apr 26, 2022

Timer UI animation challenge from 'Flutter Animations Masterclass'

stopwatch_flutter An IOS stopwatch challenge from Flutter Animations Masterclass - Full Course What I learned; Use timer Use ticker Create custom shap

Jan 4, 2023

🐱‍👤 Flutter-Animation 🔥 🔥 List Animated Staggered Animations

 🐱‍👤 Flutter-Animation 🔥 🔥 List Animated Staggered Animations

🐱‍👤 Staggered Animations made with algeria ❤

Nov 22, 2022

A Flutter Log In Page using Flare Animations

A Flutter Log In Page using Flare Animations

Bear_log_in An example built using JCToon's Flare File as a custom UI component. Bear will follow the cursor as you type or move it around. Overview T

Oct 19, 2022

A package to create nice and smooth animations for flutter

A package to create nice and smooth animations for flutter

animation_director A package to create nice and smooth animations for flutter Introduction A simple package to build beautiful and smooth animations f

Nov 28, 2022

☀️ A Flutter package for some material design app intro screens with some cool animations.

☀️ A Flutter package for some material design app intro screens with some cool animations.

IntroViews is inspired by Paper Onboarding and developed with love from scratch. Checkout our fully responsive live example app. Table of contents Fea

Dec 30, 2022

Render After Effects animations natively on Flutter. This package is a pure Dart implementation of a Lottie player.

Lottie for Flutter Lottie is a mobile library for Android and iOS that parses Adobe After Effects animations exported as json with Bodymovin and rende

Jan 2, 2023

A flutter package which will help you to generate pin code fields with beautiful design and animations

A flutter package which will help you to generate pin code fields with beautiful design and animations

A flutter package which will help you to generate pin code fields with beautiful design and animations. Can be useful for OTP or pin code inputs 🤓 🤓

Dec 23, 2022

A set of transition patterns within the animations package using flutter.

A set of transition patterns within the animations package using flutter.

Flutter Motion Transitions A fultter app to demonstrate Material motion system. Material Motion System The four main Material transition patterns are

Oct 13, 2022
Comments
Releases(v0.0.6)
  • v0.0.6(Jun 18, 2021)

    0.0.6+24

    • improve readme.

    0.0.6+23

    • improve docs and readme.

    0.0.6+22

    • renamed MotionState to MotionStatus, to keep it idiomatic with Flutter's AnimationController.
    • renamed motion.state to motion.status.
    • added MotionState.disposed notification.
    • fix notifications for MotionState.moving
    • improved toString() on Ease and Spring motion variants, to describe better the motion instance.
    • removed the "microtask delay" for new states notifications with statusListener.
    • added missing hot-reload support code.
    • EXPERIMENTAL: added instance auto de-registration for MotionValue instances created inside build(BuildContext), this is still experimental, and still have to be tested in multiple use-cases of motion, to see if it brings collateral issues.
    • add some code docs.

    0.0.5+15

    • MotionValue forced to dispose on reassembling (hot-reload).
    • add some code docs.

    0.0.4+12

    • add some code docs.
    • add EaseRect, EaseBoxConstraints, EaseInsets with their extensions .
    • add EaseBase._easeValue() to facilitate easing in complex types.
    • add build number to version, to avoid modifying min version for docs and readme changes.
    • add flutter_lints package for analysis.

    0.0.3

    • fix for EaseColor using a "_dumb" flag to avoid expensive notifications.
    • fix default values for EaseColor
    • fix MotionTicker, to avoid removing MotionValues while looping.
    • changed some MotionStates (can be detected with addStatusListener() and using the state property.).

    0.0.2

    • cleanup.
    • includes SpringValue
    • added support for timeDilation (Flutter's slow motion.)
    • renamed EulerValue to EaseValue.
    • renamed extension obj.euler() to obj.ease()
    • ease() will accept num (int or double) for value/target, but treats all as double. Make sure to convert double to int if you consume it that way (round(), ceil(), floor(), toInt(), etc).

    0.0.1

    • basic initial alpha release. MotionValue API will likely change.
    Source code(tar.gz)
    Source code(zip)
  • v0.0.3(Jun 15, 2021)

    0.0.3

    • fix for EaseColor using a "_dumb" flag to avoid expensive notifications.
    • fix default values for EaseColor
    • fix MotionTicker, to avoid removing MotionValues while looping.
    • changed some MotionStates (can be detected with addStatusListener() and using the state property.).

    0.0.2

    • cleanup.
    • includes SpringValue
    • added support for timeDilation (Flutter's slow motion.)
    • renamed EulerValue to EaseValue.
    • renamed extension obj.euler() to obj.ease()
    • ease() will accept num (int or double) for value/target, but treats all as double. Make sure to convert double to int if you consume it that way (round(), ceil(), floor(), toInt(), etc).

    0.0.1

    • basic initial alpha release. MotionValue API will likely change.
    Source code(tar.gz)
    Source code(zip)
  • v0.0.2(Jun 14, 2021)

    0.0.2

    • cleanup.
    • includes SpringValue
    • added support for timeDilation (Flutter's slow motion.)
    • renamed EulerValue to EaseValue.
    • renamed extension obj.euler() to obj.ease()
    • ease() will accept num (int or double) for value/target, but treats all as double. Make sure to convert double to int if you consume it that way (round(), ceil(), floor(), toInt(), etc).

    0.0.1

    • basic initial alpha release. MotionValue API will likely change.
    Source code(tar.gz)
    Source code(zip)
Owner
Roi Peker
Roi Peker is a enthusiastic software developer and designer. Always looking to learn something new, trying to follow the greek concept "meraki" always.
Roi Peker
Create powerful animations in Flutter and use the hero animation for complex animations

Hero Animation - Locations UI - Flutter Create powerful animations in Flutter and use the hero animation for complex animations. ⚡  Social Media  Twit

null 3 Dec 11, 2021
A reactive key-value store for Flutter projects. Like shared_preferences, but with Streams.

streaming_shared_preferences A reactive key-value store for Flutter projects. streaming_shared_preferences adds reactive functionality on top of share

Iiro Krankka 244 Dec 22, 2022
A collection of Animations that aims to improve the user experience for your next flutter project.

A collection of Animations that aims to improve the user experience for your next flutter project.

Ezaldeen Sahb 134 Dec 24, 2022
Easily add staggered animations to your ListView, GridView, Column and Row children.

Flutter Staggered Animations Easily add staggered animations to your ListView, GridView, Column and Row children as shown in Material Design guideline

null 1.2k Jan 6, 2023
Organize your animations.

montage Organize your animations. Quickstart // 1. Define your animations const entrance = MontageAnimation( key: 'entrance', duration: Duration(s

Aloïs Deniel 14 Oct 19, 2022
A Flutter package with a selection of simple yet very customizable set of loading animations.

Flutter Loading Animations A simple yet very customizable set of loading animations for Flutter projects. Installation Add the following to your pubsp

Andre Cytryn 171 Sep 23, 2022
Making drawings and animations in Flutter extremely simple

?? GraphX™ | rendering | prototype | design | Making drawings and animations in Flutter extremely simple. wiki-tips. To get some extended, boring expl

Roi Peker 382 Dec 21, 2022
🔔 A flutter package to create cool and beautiful text animations. [Flutter Favorite Package]

Animated Text Kit A flutter package which contains a collection of some cool and awesome text animations. Recommended package for text animations in C

Ayush Agarwal 1.4k Jan 6, 2023
Flutter package for creating awesome animations.

?? Simple Animations Simple Animations is a powerful package to create beautiful custom animations in no time. ?? fully tested ?? well documented ?? e

Felix Blaschke 879 Dec 31, 2022
Fun canvas animations in Flutter based on time and math functions.

funvas Flutter package that allows creating canvas animations based on time and math (mostly trigonometric) functions. The name "funvas" is based on F

null 472 Jan 9, 2023