using System; using System.Collections.Generic; using Unity.UIWidgets.animation; using Unity.UIWidgets.foundation; using Unity.UIWidgets.painting; using Unity.UIWidgets.rendering; namespace Unity.UIWidgets.widgets { public enum CrossFadeState { showFirst, showSecond } public delegate Widget AnimatedCrossFadeBuilder(Widget topChild, Key topChildKey, Widget bottomChild, Key bottomChildKey); public class AnimatedCrossFade : StatefulWidget { public AnimatedCrossFade( Key key = null, Widget firstChild = null, Widget secondChild = null, Curve firstCurve = null, Curve secondCurve = null, Curve sizeCurve = null, Alignment alignment = null, CrossFadeState? crossFadeState = null, TimeSpan? duration = null, TimeSpan? reverseDuration = null, AnimatedCrossFadeBuilder layoutBuilder = null ) : base(key: key) { D.assert(firstChild != null); D.assert(secondChild != null); D.assert(crossFadeState != null); D.assert(duration != null); this.firstChild = firstChild; this.secondChild = secondChild; this.firstCurve = firstCurve ?? Curves.linear; this.secondCurve = secondCurve ?? Curves.linear; this.sizeCurve = sizeCurve ?? Curves.linear; this.alignment = alignment ?? Alignment.topCenter; this.crossFadeState = crossFadeState ?? CrossFadeState.showFirst; this.duration = duration; this.reverseDuration = reverseDuration; this.layoutBuilder = layoutBuilder ?? defaultLayoutBuilder; } public readonly Widget firstChild; public readonly Widget secondChild; public readonly CrossFadeState crossFadeState; public readonly TimeSpan? duration; public readonly TimeSpan? reverseDuration; public readonly Curve firstCurve; public readonly Curve secondCurve; public readonly Curve sizeCurve; public readonly Alignment alignment; public readonly AnimatedCrossFadeBuilder layoutBuilder; static Widget defaultLayoutBuilder(Widget topChild, Key topChildKey, Widget bottomChild, Key bottomChildKey) { return new Stack( overflow: Overflow.visible, children: new List { new Positioned( key: bottomChildKey, left: 0.0f, top: 0.0f, right: 0.0f, child: bottomChild), new Positioned( key: topChildKey, child: topChild) } ); } public override State createState() { return new _AnimatedCrossFadeState(); } public override void debugFillProperties(DiagnosticPropertiesBuilder properties) { base.debugFillProperties(properties); properties.add(new EnumProperty("crossFadeState", crossFadeState)); properties.add(new DiagnosticsProperty("alignment", alignment, defaultValue: Alignment.topCenter)); properties.add(new IntProperty("duration", duration?.Milliseconds, unit: "ms")); properties.add(new IntProperty("reverseDuration", reverseDuration?.Milliseconds, unit: "ms", defaultValue: null)); } } public class _AnimatedCrossFadeState : TickerProviderStateMixin { AnimationController _controller; Animation _firstAnimation; Animation _secondAnimation; public override void initState() { base.initState(); _controller = new AnimationController( duration: widget.duration, reverseDuration: widget.reverseDuration, vsync: this); if (widget.crossFadeState == CrossFadeState.showSecond) { _controller.setValue(1.0f); } _firstAnimation = _initAnimation(widget.firstCurve, true); _secondAnimation = _initAnimation(widget.secondCurve, false); _controller.addStatusListener((AnimationStatus status) => { setState(() => { }); }); } Animation _initAnimation(Curve curve, bool inverted) { Animation result = _controller.drive(new CurveTween(curve: curve)); if (inverted) { result = result.drive(new FloatTween(begin: 1.0f, end: 0.0f)); } return result; } public override void dispose() { _controller.dispose(); base.dispose(); } public override void didUpdateWidget(StatefulWidget oldWidget) { base.didUpdateWidget(oldWidget); AnimatedCrossFade _oldWidget = (AnimatedCrossFade) oldWidget; if (widget.duration != _oldWidget.duration) { _controller.duration = widget.duration; } if (widget.reverseDuration != _oldWidget.reverseDuration) _controller.reverseDuration = widget.reverseDuration; if (widget.firstCurve != _oldWidget.firstCurve) { _firstAnimation = _initAnimation(widget.firstCurve, true); } if (widget.secondCurve != _oldWidget.secondCurve) { _secondAnimation = _initAnimation(widget.secondCurve, false); } if (widget.crossFadeState != _oldWidget.crossFadeState) { switch (widget.crossFadeState) { case CrossFadeState.showFirst: _controller.reverse(); break; case CrossFadeState.showSecond: _controller.forward(); break; } } } bool _isTransitioning { get { return _controller.status == AnimationStatus.forward || _controller.status == AnimationStatus.reverse; } } public override Widget build(BuildContext context) { Key kFirstChildKey = new ValueKey(CrossFadeState.showFirst); Key kSecondChildKey = new ValueKey(CrossFadeState.showSecond); bool transitioningForwards = _controller.status == AnimationStatus.completed || _controller.status == AnimationStatus.forward; Key topKey; Widget topChild; Animation topAnimation; Key bottomKey; Widget bottomChild; Animation bottomAnimation; if (transitioningForwards) { topKey = kSecondChildKey; topChild = widget.secondChild; topAnimation = _secondAnimation; bottomKey = kFirstChildKey; bottomChild = widget.firstChild; bottomAnimation = _firstAnimation; } else { topKey = kFirstChildKey; topChild = widget.firstChild; topAnimation = _firstAnimation; bottomKey = kSecondChildKey; bottomChild = widget.secondChild; bottomAnimation = _secondAnimation; } bottomChild = new TickerMode( key: bottomKey, enabled: _isTransitioning, child: new FadeTransition( opacity: bottomAnimation, child: bottomChild ) ); topChild = new TickerMode( key: topKey, enabled: true, child: new FadeTransition( opacity: topAnimation, child: topChild ) ); return new ClipRect( child: new AnimatedSize( alignment: widget.alignment, duration: widget.duration, reverseDuration: widget.reverseDuration, curve: widget.sizeCurve, vsync: this, child: widget.layoutBuilder(topChild, topKey, bottomChild, bottomKey) ) ); } public override void debugFillProperties(DiagnosticPropertiesBuilder description) { base.debugFillProperties(description); description.add(new EnumProperty("crossFadeState", widget.crossFadeState)); description.add( new DiagnosticsProperty("controller", _controller, showName: false)); description.add(new DiagnosticsProperty("alignment", widget.alignment, defaultValue: Alignment.topCenter)); } } }