// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:math' as math; import 'package:flutter/rendering.dart'; import 'package:vector_math/vector_math_64.dart' show Matrix4; import 'basic.dart'; import 'container.dart'; import 'framework.dart'; export 'package:flutter/rendering.dart' show RelativeRect; /// A widget that rebuilds when the given [Listenable] changes value. /// /// [AnimatedWidget] is most commonly used with [Animation] objects, which are /// [Listenable], but it can be used with any [Listenable], including /// [ChangeNotifier] and [ValueNotifier]. /// /// [AnimatedWidget] is most useful for widgets widgets that are otherwise /// stateless. To use [AnimatedWidget], simply subclass it and implement the /// build function. /// /// For more complex case involving additional state, consider using /// [AnimatedBuilder]. /// /// See also: /// /// * [AnimatedBuilder], which is useful for more complex use cases. /// * [Animation], which is a [Listenable] object that can be used for /// [listenable]. /// * [ChangeNotifier], which is another [Listenable] object that can be used /// for [listenable]. abstract class AnimatedWidget extends StatefulWidget { /// Creates a widget that rebuilds when the given listenable changes. /// /// The [listenable] argument is required. const AnimatedWidget({ Key key, @required this.listenable }) : assert(listenable != null), super(key: key); /// The [Listenable] to which this widget is listening. /// /// Commonly an [Animation] or a [ChangeNotifier]. final Listenable listenable; /// Override this method to build widgets that depend on the state of the /// listenable (e.g., the current value of the animation). @protected Widget build(BuildContext context); /// Subclasses typically do not override this method. @override _AnimatedState createState() => new _AnimatedState(); @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(new DiagnosticsProperty<Listenable>('animation', listenable)); } } class _AnimatedState extends State<AnimatedWidget> { @override void initState() { super.initState(); widget.listenable.addListener(_handleChange); } @override void didUpdateWidget(AnimatedWidget oldWidget) { super.didUpdateWidget(oldWidget); if (widget.listenable != oldWidget.listenable) { oldWidget.listenable.removeListener(_handleChange); widget.listenable.addListener(_handleChange); } } @override void dispose() { widget.listenable.removeListener(_handleChange); super.dispose(); } void _handleChange() { setState(() { // The listenable's state is our build state, and it changed already. }); } @override Widget build(BuildContext context) => widget.build(context); } /// 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. class SlideTransition extends AnimatedWidget { /// Creates a fractional translation transition. /// /// The [position] argument must not be null. 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, ); } } /// Animates the scale of transformed widget. class ScaleTransition extends AnimatedWidget { /// Creates a scale transition. /// /// The [scale] argument must not be null. The [alignment] argument defaults /// to [Alignment.center]. const ScaleTransition({ Key key, @required Animation<double> scale, this.alignment: Alignment.center, this.child, }) : super(key: key, listenable: scale); /// The animation that controls the scale of the child. /// /// If the current value of the scale animation is v, the child will be /// painted v times its normal size. Animation<double> get scale => listenable; /// The alignment of the origin of the coordinate system in which the scale /// takes place, relative to the size of the box. /// /// For example, to set the origin of the scale to bottom middle, you can use /// an alignment of (0.0, 1.0). final Alignment alignment; /// The widget below this widget in the tree. /// /// {@macro flutter.widgets.child} final Widget child; @override Widget build(BuildContext context) { final double scaleValue = scale.value; final Matrix4 transform = new Matrix4.identity() ..scale(scaleValue, scaleValue, 1.0); return new Transform( transform: transform, alignment: alignment, child: child, ); } } /// Animates the rotation of a widget. 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, ); } } /// Animates its own size and clips and aligns the child. /// /// For a widget that automatically animates between the sizes of two children, /// fading between them, see [AnimatedCrossFade]. class SizeTransition extends AnimatedWidget { /// Creates a size transition. /// /// The [sizeFactor] argument must not be null. The [axis] argument defaults /// to [Axis.vertical]. The [axisAlignment] defaults to 0.0, which centers the /// child along the main axis during the transition. const SizeTransition({ Key key, this.axis: Axis.vertical, @required Animation<double> sizeFactor, this.axisAlignment: 0.0, this.child, }) : assert(axis != null), super(key: key, listenable: sizeFactor); /// [Axis.horizontal] if [sizeFactor] modifies the width, otherwise [Axis.vertical]. final Axis axis; /// The animation that controls the (clipped) size of the child. If the current value /// of sizeFactor is v then the width or height of the widget will be its intrinsic /// width or height multiplied by v. Animation<double> get sizeFactor => listenable; /// How to align the child along the axis that sizeFactor is modifying. final double axisAlignment; /// The widget below this widget in the tree. /// /// {@macro flutter.widgets.child} final Widget child; @override Widget build(BuildContext context) { AlignmentDirectional alignment; if (axis == Axis.vertical) alignment = new AlignmentDirectional(-1.0, axisAlignment); else alignment = new AlignmentDirectional(axisAlignment, -1.0); return new ClipRect( child: new Align( alignment: alignment, heightFactor: axis == Axis.vertical ? math.max(sizeFactor.value, 0.0) : null, widthFactor: axis == Axis.horizontal ? math.max(sizeFactor.value, 0.0) : null, child: child, ) ); } } /// Animates the opacity of a widget. /// /// For a widget that automatically animates between the sizes of two children, /// fading between them, see [AnimatedCrossFade]. class FadeTransition extends SingleChildRenderObjectWidget { /// Creates an opacity transition. /// /// The [opacity] argument must not be null. const FadeTransition({ Key key, @required this.opacity, Widget child, }) : super(key: key, child: child); /// The animation that controls the opacity of the child. /// /// If the current value of the opacity animation is v, the child will be /// painted with an opacity of v. For example, if v is 0.5, the child will be /// blended 50% with its background. Similarly, if v is 0.0, the child will be /// completely transparent. final Animation<double> opacity; @override RenderAnimatedOpacity createRenderObject(BuildContext context) { return new RenderAnimatedOpacity( opacity: opacity, ); } @override void updateRenderObject(BuildContext context, RenderAnimatedOpacity renderObject) { renderObject ..opacity = opacity; } @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(new DiagnosticsProperty<Animation<double>>('opacity', opacity)); } } /// An interpolation between two relative rects. /// /// This class specializes the interpolation of [Tween<RelativeRect>] to /// use [RelativeRect.lerp]. /// /// See [Tween] for a discussion on how to use interpolation objects. class RelativeRectTween extends Tween<RelativeRect> { /// Creates a [RelativeRect] tween. /// /// The [begin] and [end] properties may be null; the null value /// is treated as [RelativeRect.fill]. RelativeRectTween({ RelativeRect begin, RelativeRect end }) : super(begin: begin, end: end); /// Returns the value this variable has at the given animation clock value. @override RelativeRect lerp(double t) => RelativeRect.lerp(begin, end, t); } /// Animated version of [Positioned] which takes a specific /// [Animation<RelativeRect>] to transition the child's position from a start /// position to and end position over the lifetime of the animation. /// /// Only works if it's the child of a [Stack]. /// /// See also: /// /// * [RelativePositionedTransition]. class PositionedTransition extends AnimatedWidget { /// Creates a transition for [Positioned]. /// /// The [rect] argument must not be null. const PositionedTransition({ Key key, @required Animation<RelativeRect> rect, @required this.child, }) : super(key: key, listenable: rect); /// The animation that controls the child's size and position. Animation<RelativeRect> get rect => listenable; /// The widget below this widget in the tree. /// /// {@macro flutter.widgets.child} final Widget child; @override Widget build(BuildContext context) { return new Positioned.fromRelativeRect( rect: rect.value, child: child, ); } } /// Animated version of [Positioned] which transitions the child's position /// based on the value of [rect] relative to a bounding box with the /// specified [size]. /// /// Only works if it's the child of a [Stack]. /// /// See also: /// /// * [PositionedTransition]. class RelativePositionedTransition extends AnimatedWidget { /// Create an animated version of [Positioned]. /// /// Each frame, the [Positioned] widget will be configured to represent the /// current value of the [rect] argument assuming that the stack has the given /// [size]. Both [rect] and [size] must not be null. const RelativePositionedTransition({ Key key, @required Animation<Rect> rect, @required this.size, @required this.child, }) : super(key: key, listenable: rect); /// The animation that controls the child's size and position. /// /// See also [size]. Animation<Rect> get rect => listenable; /// The [Positioned] widget's offsets are relative to a box of this /// size whose origin is 0,0. final Size size; /// The widget below this widget in the tree. /// /// {@macro flutter.widgets.child} final Widget child; @override Widget build(BuildContext context) { final RelativeRect offsets = new RelativeRect.fromSize(rect.value, size); return new Positioned( top: offsets.top, right: offsets.right, bottom: offsets.bottom, left: offsets.left, child: child, ); } } /// Animated version of a [DecoratedBox] that animates the different properties /// of its [Decoration]. /// /// See also: /// /// * [DecoratedBox], which also draws a [Decoration] but is not animated. /// * [AnimatedContainer], a more full-featured container that also animates on /// decoration using an internal animation. class DecoratedBoxTransition extends AnimatedWidget { /// Creates an animated [DecoratedBox] whose [Decoration] animation updates /// the widget. /// /// The [decoration] and [position] must not be null. /// /// See also: /// /// * [new DecoratedBox]. const DecoratedBoxTransition({ Key key, @required this.decoration, this.position: DecorationPosition.background, @required this.child, }) : super(key: key, listenable: decoration); /// Animation of the decoration to paint. /// /// Can be created using a [DecorationTween] interpolating typically between /// two [BoxDecoration]. final Animation<Decoration> decoration; /// Whether to paint the box decoration behind or in front of the child. final DecorationPosition position; /// The widget below this widget in the tree. /// /// {@macro flutter.widgets.child} final Widget child; @override Widget build(BuildContext context) { return new DecoratedBox( decoration: decoration.value, position: position, child: child, ); } } /// Animated version of an [Align] that animates its [Align.alignment] property. class AlignTransition extends AnimatedWidget { /// Creates an animated [Align] whose [AlignmentGeometry] animation updates /// the widget. /// /// See also: /// /// * [new Align]. const AlignTransition({ Key key, @required Animation<AlignmentGeometry> alignment, @required this.child, this.widthFactor, this.heightFactor, }) : super(key: key, listenable: alignment); /// The animation that controls the child's alignment. Animation<AlignmentGeometry> get alignment => listenable; /// If non-null, the child's width factor, see [Align.widthFactor]. final double widthFactor; /// If non-null, the child's height factor, see [Align.heightFactor]. final double heightFactor; /// The widget below this widget in the tree. /// /// {@macro flutter.widgets.child} final Widget child; @override Widget build(BuildContext context) { return new Align( alignment: alignment.value, widthFactor: widthFactor, heightFactor: heightFactor, child: child, ); } } /// A general-purpose widget for building animations. /// /// AnimatedBuilder is useful for more complex widgets that wish to include /// an animation as part of a larger build function. To use AnimatedBuilder, /// simply construct the widget and pass it a builder function. /// /// For simple cases without additional state, consider using /// [AnimatedWidget]. /// /// ## Performance optimizations /// /// If your [builder] function contains a subtree that does not depend on the /// animation, it's more efficient to build that subtree once instead of /// rebuilding it on every animation tick. /// /// If you pass the pre-built subtree as the [child] parameter, the /// AnimatedBuilder will pass it back to your builder function so that you /// can incorporate it into your build. /// /// Using this pre-built child is entirely optional, but can improve /// performance significantly in some cases and is therefore a good practice. /// /// ## Sample code /// /// This code defines a widget called `Spinner` that spins a green square /// continually. It is built with an [AnimatedBuilder] and makes use of the /// [child] feature to avoid having to rebuild the [Container] each time. /// /// ```dart /// class Spinner extends StatefulWidget { /// @override /// _SpinnerState createState() => new _SpinnerState(); /// } /// /// class _SpinnerState extends State<Spinner> with SingleTickerProviderStateMixin { /// AnimationController _controller; /// /// @override /// void initState() { /// super.initState(); /// _controller = new AnimationController( /// duration: const Duration(seconds: 10), /// vsync: this, /// )..repeat(); /// } /// /// @override /// void dispose() { /// _controller.dispose(); /// super.dispose(); /// } /// /// @override /// Widget build(BuildContext context) { /// return new AnimatedBuilder( /// animation: _controller, /// child: new Container(width: 200.0, height: 200.0, color: Colors.green), /// builder: (BuildContext context, Widget child) { /// return new Transform.rotate( /// angle: _controller.value * 2.0 * math.pi, /// child: child, /// ); /// }, /// ); /// } /// } /// ``` class AnimatedBuilder extends AnimatedWidget { /// Creates an animated builder. /// /// The [animation] and [builder] arguments must not be null. const AnimatedBuilder({ Key key, @required Listenable animation, @required this.builder, this.child, }) : assert(builder != null), super(key: key, listenable: animation); /// Called every time the animation changes value. final TransitionBuilder builder; /// The child widget to pass to the [builder]. /// /// If a [builder] callback's return value contains a subtree that does not /// depend on the animation, it's more efficient to build that subtree once /// instead of rebuilding it on every animation tick. /// /// If the pre-built subtree is passed as the [child] parameter, the /// [AnimatedBuilder] will pass it back to the [builder] function so that it /// can be incorporated into the build. /// /// Using this pre-built child is entirely optional, but can improve /// performance significantly in some cases and is therefore a good practice. final Widget child; @override Widget build(BuildContext context) { return builder(context, child); } }