blob: 8b42690472e76b63d36d1bd2b07ecf4b7a47f794 [file] [log] [blame]
// Copyright 2016 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 'package:flutter/foundation.dart';
import 'package:flutter/animation.dart';
import 'basic.dart';
import 'framework.dart';
import 'ticker_provider.dart';
import 'transitions.dart';
class _AnimatedChildSwitcherChildEntry {
_AnimatedChildSwitcherChildEntry(this.widget, this.controller, this.animation);
Widget widget;
final AnimationController controller;
final Animation<double> animation;
}
/// A widget that automatically does a [FadeTransition] between a new widget and
/// the widget previously set on the [AnimatedChildSwitcher] as a child.
///
/// More than one previous child can exist and be fading out while the newest
/// one is fading in if they are swapped fast enough (i.e. before [duration]
/// elapses).
///
/// See also:
///
/// * [AnimatedCrossFade], which only fades between two children, but also
/// interpolates their sizes, and is reversible.
/// * [FadeTransition] which [AnimatedChildSwitcher] uses to perform the transition.
class AnimatedChildSwitcher extends StatefulWidget {
/// The [duration], [switchInCurve], and [switchOutCurve] parameters must not
/// be null.
const AnimatedChildSwitcher({
Key key,
this.child,
this.switchInCurve: Curves.linear,
this.switchOutCurve: Curves.linear,
@required this.duration,
}) : assert(switchInCurve != null),
assert(switchOutCurve != null),
assert(duration != null),
super(key: key);
/// The current child widget to display. If there was a previous child,
/// then that child will be cross faded with this child using a
/// [FadeTransition] using the [switchInCurve].
///
/// If there was no previous child, then this child will fade in over the
/// [duration].
final Widget child;
/// The animation curve to use when fading in the current widget.
final Curve switchInCurve;
/// The animation curve to use when fading out the previous widgets.
final Curve switchOutCurve;
/// The duration over which to perform the cross fade using [FadeTransition].
final Duration duration;
@override
_AnimatedChildSwitcherState createState() => new _AnimatedChildSwitcherState();
}
class _AnimatedChildSwitcherState extends State<AnimatedChildSwitcher> with TickerProviderStateMixin {
final Set<_AnimatedChildSwitcherChildEntry> _children = new Set<_AnimatedChildSwitcherChildEntry>();
_AnimatedChildSwitcherChildEntry _currentChild;
@override
void initState() {
super.initState();
addEntry(false);
}
void addEntry(bool animate) {
final AnimationController controller = new AnimationController(
duration: widget.duration,
vsync: this,
);
if (animate) {
if (_currentChild != null) {
_currentChild.controller.reverse();
_children.add(_currentChild);
}
controller.forward();
} else {
assert(_currentChild == null);
assert(_children.isEmpty);
controller.value = 1.0;
}
final Animation<double> animation = new CurvedAnimation(
parent: controller,
curve: widget.switchInCurve,
reverseCurve: widget.switchOutCurve,
);
final _AnimatedChildSwitcherChildEntry entry = new _AnimatedChildSwitcherChildEntry(
widget.child,
controller,
animation,
);
animation.addStatusListener((AnimationStatus status) {
if (status == AnimationStatus.dismissed) {
assert(_children.contains(entry));
setState(() {
_children.remove(entry);
});
controller.dispose();
}
});
_currentChild = entry;
}
@override
void dispose() {
if (_currentChild != null) {
_currentChild.controller.dispose();
}
for (_AnimatedChildSwitcherChildEntry child in _children) {
child.controller.dispose();
}
super.dispose();
}
@override
Widget build(BuildContext context) {
if (widget.child != _currentChild.widget) {
addEntry(true);
}
final List<Widget> children = <Widget>[];
for (_AnimatedChildSwitcherChildEntry child in _children) {
children.add(
new FadeTransition(
opacity: child.animation,
child: child.widget,
),
);
}
if (_currentChild != null) {
children.add(
new FadeTransition(
opacity: _currentChild.animation,
child: _currentChild.widget,
),
);
}
return new Stack(
children: children,
alignment: Alignment.center,
);
}
}