Flutter Transition Animations

Transition animations are pre-defined and reusable—similar to implicit animations, but you control how the animation affects the animated object—similar to explicit animations.

For example, you can reuse a SlideTransition animation widget and customize the way the animation target object becomes visible. You can push it into view from the left-side or the right-side of the display, or you can specify the type of curved animation applied to the object such as elasticIn, elasticOut, or bounceIn.

Using a transition animation widget

Transition animation widgets 1. Create your StatefulWidget.
2. Create the State class to hold the animation object, the AnimationController, and the transition animation widget.
3. Add the vsync, listener, and tween.
4. Specify the type of animation that you want to apply to the animation object.
5. Dispose of the controller.
6. Specify the BuildContext to return the _transition_ animation widget.

Building your own transition animations

Flutter includes many transition animations that you can reuse but you can build your own. To create a reusable transition animation, create a widget that extends AnimatedWidget.

Pre-defined transition animations already extend the AnimatedWidget class that allows you to separate the widget code from the animation code in the setState() call. The AnimatedWidget class doesn’t need to maintain a State object to hold the animation.

Transition animation examples

The RotationTransition and SlideTransition are two examples that use transition animation widgets included in the widget package.

RotationTransition example

The RotationTransition widget automatically rotates a widget.

The code for the RotationTransition example is shown below.

```Dart
import 'package:flutter/material.dart';

void main() {
  runApp(
    new MaterialApp(
      home: new HomePage(),
    ),
  );
}

class HomePage extends StatefulWidget {
  @override
  HomePageState createState() => new HomePageState();
}

class HomePageState extends State<HomePage> {
  bool selected = false;

  @override
  Widget build(BuildContext context) {
    return new RotationTransitionExample(
      selected: selected,
      onTap: (bool value) {
        setState(() {
          selected = value;
        });
      },
    );
  }
}

class RotationTransitionExample extends StatefulWidget {
  const RotationTransitionExample({
    Key key,
    this.selected = false,
    this.onTap,
  }) : super(key: key);

  final ValueChanged<bool> onTap;
  final bool selected;

  @override
  RotationTransitionExampleState createState() => new RotationTransitionExampleState();
}

class RotationTransitionExampleState extends State<RotationTransitionExample>
// The TickerProviderStateMixin makes it so that this state object can
// be used as the vsync for the AnimationController.
    with TickerProviderStateMixin<RotationTransitionExample> {
  AnimationController _controller;
  Animation<double> _rotationAnimation;

  @override
  void initState() {
    super.initState();
    // Create an animation controller, and rebuild the widget tree when we
    // listen to it, in order to generate frames.
    _controller = new AnimationController(
      duration: const Duration(seconds: 2),
      vsync: this,
    )..addListener(() {
      setState(() {
        // The value of the animation controller has changed, so we need to
        // rebuild the widget tree.
      });
    });

    // Set up the animation curve that we want to use.
    _rotationAnimation = new CurvedAnimation(
      parent: _controller,
      // There are many different types of curves.
      curve: Curves.elasticOut,
    );
  }

  @override
  void didUpdateWidget(RotationTransitionExample oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.selected == widget.selected) {
      return;
    }
    // Change the direction of the animation based on whether selected is true
    // or not.  Note that this means if you tap it twice quickly, the animation
    // will only just get started, and reverse to the original position.
    widget.selected ? _controller.forward() : _controller.reverse();
  }

  @override
  void dispose() {
    // The controller must be disposed of, so it is usually owned by a
    // StatefulWidget.
    _controller?.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        setState(() {
          widget.onTap(!widget.selected);
        });
      },
      child: Container(
        color: Colors.white,
        child: new RotationTransition(
          turns: _rotationAnimation,
          child: const FlutterLogo(),
        ),
      ),
    );
  }
}

```

RotationTransition widget code

The code for the RotationTransition animation widget used in the example is shown below. Notice that this widget extends AnimatedWidget. When you want to create a new transition widget that you can reuse, extend AnimatedWidget.

```Dart
class RotationTransition extends AnimatedWidget {
  /// Creates a rotation transition.
  ///
  /// The [turns] argument must not be null.
  const RotationTransition({
    Key key,
    @required Animation<double> turns,
    this.child,
  }) : super(key: key, listenable: turns);

  /// The animation that controls the rotation of the child.
  ///
  /// If the current value of the turns animation is v, the child will be
  /// rotated v * 2 * pi radians before being painted.
  Animation<double> get turns => listenable;

  /// The widget below this widget in the tree.
  ///
  /// {@macro flutter.widgets.child}
  final Widget child;

  @override
  Widget build(BuildContext context) {
    final double turnsValue = turns.value;
    final Matrix4 transform = new Matrix4.rotationZ(turnsValue * math.pi * 2.0);
    return new Transform(
      transform: transform,
      alignment: Alignment.center,
      child: child,
    );
  }
}
```

SlideTransition example

The SlideTransition transition widget automatically animates the position of a widget relative to its normal position.

The code for the SlideTransition example is shown below.

```Dart
import 'package:flutter/material.dart';

void main() {
  runApp(
    new MaterialApp(
      home: new HomePage(),
    ),
  );
}

class HomePage extends StatefulWidget {
  @override
  HomePageState createState() => new HomePageState();
}

class HomePageState extends State<HomePage> {
  bool selected = false;

  @override
  Widget build(BuildContext context) {
    return new SlideTransitionExample(
      selected: selected,
      onTap: (bool value) {
        setState(() {
          selected = value;
        });
      },
    );
  }
}

class SlideTransitionExample extends StatefulWidget {
  const SlideTransitionExample({
    Key key,
    this.selected = false,
    this.onTap,
  }) : super(key: key);

  final ValueChanged<bool> onTap;
  final bool selected;

  @override
  SlideTransitionExampleState createState() => new SlideTransitionExampleState();
}

class SlideTransitionExampleState extends State<SlideTransitionExample>
// The TickerProviderStateMixin makes it so that this state object can
// be used as the vsync for the AnimationController.
    with TickerProviderStateMixin<SlideTransitionExample> {

  // Because this animation doesn't just drive something that's a double,
  // we need a Tween to do the mapping from a double to an Offset.
  static final Tween<Offset> _offsetTween = new Tween<Offset>(
    // Start with no offset.
    begin: Offset.zero,
    // End at this offset, which is down and to the right. The numbers are in
    // multiples of the size of the child widget, so it'll travel half of a
    // full width to the right, and a full height of the child down.
    end: const Offset(0.5, 1.0),
  );

  AnimationController _controller;
  Animation<Offset> _slideAnimation;

  @override
  void initState() {
    super.initState();
    // Create an animation controller, and rebuild the widget tree when we
    // listen to it, in order to generate frames.
    _controller = new AnimationController(
      duration: const Duration(seconds: 1),
      vsync: this,
    )..addListener(() {
      setState(() {
        // The value of the animation controller has changed, so we need to
        // rebuild the widget tree.
      });
    });

    // Use a curved animation to drive the Tween<Offset> to give interpolated
    // values between the start and end of the Tween.
    _slideAnimation = _offsetTween.animate(
      new CurvedAnimation(
        parent: _controller,
        curve: Curves.fastOutSlowIn,
      ),
    );
  }

  @override
  void didUpdateWidget(SlideTransitionExample oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.selected == widget.selected) {
      return;
    }
    // Change the direction of the animation based on whether selected is true
    // or not.  Note that this means if you tap it twice quickly, the animation
    // will only just get started, and reverse to the original position.
    widget.selected ? _controller.forward() : _controller.reverse();
  }

  @override
  void dispose() {
    // The controller must be disposed of, so it is usually owned by a
    // StatefulWidget.
    _controller?.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        setState(() {
          widget.onTap(!widget.selected);
        });
      },
      child: Container(
        color: Colors.white,
        child: new SlideTransition(
          position: _slideAnimation,
          child: const Padding(
            padding: const EdgeInsets.all(8.0),
            child: const FlutterLogo(size: 150.0),
          ),
        ),
      ),
    );
  }
}

```

SlideTransition widget code

The code for the SlideTransition animation widget used in the example, is shown below. Notice that this transition widget extends AnimatedWidget. When you want to create a new transition widget that you can reuse, extend AnimatedWidget.

```Dart
/// Animates the position of a widget relative to its normal position.
///
/// The translation is expressed as a [Offset] scaled to the child's size. For
/// example, an [Offset] with a `dx` of 0.25 will result in a horizontal
/// translation of one quarter the width of the child.
///
/// By default, the offsets are applied in the coordinate system of the canvas
/// (so positive x offsets move the child towards the right). If a
/// [textDirection] is provided, then the offsets are applied in the reading
/// direction, so in right-to-left text, positive x offsets move towards the
/// left, and in left-to-right text, positive x offsets move towards the right.  
/// Creates a fractional translation transition.
///
/// The [position] argument must not be null.  

  class SlideTransition extends AnimatedWidget {  
  const SlideTransition({
    Key key,
    @required Animation<Offset> position,
    this.transformHitTests = true,
    this.textDirection,
    this.child,
  }) : assert(position != null),
       super(key: key, listenable: position);

  /// The animation that controls the position of the child.
  ///
  /// If the current value of the position animation is `(dx, dy)`, the child
  /// will be translated horizontally by `width * dx` and vertically by
  /// `height * dy`, after applying the [textDirection] if available.
  Animation<Offset> get position => listenable;

  /// The direction to use for the x offset described by the [position].
  ///
  /// If [textDirection] is null, the x offset is applied in the coordinate
  /// system of the canvas (so positive x offsets move the child towards the
  /// right).
  ///
  /// If [textDirection] is [TextDirection.rtl], the x offset is applied in the
  /// reading direction such that x offsets move the child towards the left.
  ///
  /// If [textDirection] is [TextDirection.ltr], the x offset is applied in the
  /// reading direction such that x offsets move the child towards the right.
  final TextDirection textDirection;

  /// Whether hit testing should be affected by the slide animation.
  ///
  /// If false, hit testing will proceed as if the child was not translated at
  /// all. Setting this value to false is useful for fast animations where you
  /// expect the user to commonly interact with the child widget in its final
  /// location and you want the user to benefit from "muscle memory".
  final bool transformHitTests;

  /// The widget below this widget in the tree.
  ///
  /// {@macro flutter.widgets.child}
  final Widget child;

  @override
  Widget build(BuildContext context) {
    Offset offset = position.value;
    if (textDirection == TextDirection.rtl)
      offset = new Offset(-offset.dx, offset.dy);
    return new FractionalTranslation(
      translation: offset,
      transformHitTests: transformHitTests,
      child: child,
    );
  }
}

```

Transition animations widgets

The Flutter SDK widgets library includes the following transition animation widgets.

Transition widget Description
DecoratedBoxTransition This widget is the animated version of a DecoratedBox that animates the different properties of its Decoration.
FadeTransition This widget animates the opacity of a widget.
PositionedTransition This widget is the animated version of Positioned which takes a specific Animation to transition the child's position from a start position to and end position over the lifetime of the animation. This requires that the child is part of a Stack.
RelativePositionedTransition This widget is the animated version of Positioned which transitions the child's position based on the value of the rect property relative to a bounding box with the specified size. This requires that the child is part of a Stack.
RotationTransition This widget animates the rotation of a widget.
ScaleTransition This widget animates the scale of a widget.
SizeTransition This widget animates its own size and clips and aligns the child.
SlideTransition This widget animates the position of a widget relative to its normal position.