diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..4135297 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,4 @@ +analyzer: + strong-mode: + implicit-casts: false + implicit-dynamic: false diff --git a/example/lib/same_variable_multiple_animations.dart b/example/lib/same_variable_multiple_animations.dart index 59739b4..c816761 100755 --- a/example/lib/same_variable_multiple_animations.dart +++ b/example/lib/same_variable_multiple_animations.dart @@ -5,57 +5,58 @@ import 'package:flutter_sequence_animation/flutter_sequence_animation.dart'; class SameVariableAnimationPage extends StatefulWidget { @override - _SameVariableAnimationPageState createState() => new _SameVariableAnimationPageState(); + _SameVariableAnimationPageState createState() => + new _SameVariableAnimationPageState(); } -class _SameVariableAnimationPageState extends State with SingleTickerProviderStateMixin{ - +class _SameVariableAnimationPageState extends State + with SingleTickerProviderStateMixin { + static final colorTag = SequenceAnimationTag(); + static final widthTag = SequenceAnimationTag(); + static final heightTag = SequenceAnimationTag(); late AnimationController controller; late SequenceAnimation sequenceAnimation; - @override void initState() { super.initState(); - controller = new AnimationController(vsync: this, duration: const Duration(seconds: 5)); + controller = new AnimationController( + vsync: this, duration: const Duration(seconds: 5)); sequenceAnimation = new SequenceAnimationBuilder() - .addAnimatable( + .addAnimatable( animatable: new ColorTween(begin: Colors.red, end: Colors.yellow), - from: const Duration(seconds: 0), + from: const Duration(seconds: 0), to: const Duration(seconds: 4), - tag: "color" - ).addAnimatable( + tag: colorTag) + .addAnimatable( animatable: new Tween(begin: 50.0, end: 300.0), - from: const Duration(seconds: 0), + from: const Duration(seconds: 0), to: const Duration(milliseconds: 3000), - tag: "width", - curve: Curves.easeIn - ).addAnimatable( + tag: widthTag, + curve: Curves.easeIn) + .addAnimatable( animatable: new Tween(begin: 300.0, end: 100.0), - from: const Duration(milliseconds: 3000), + from: const Duration(milliseconds: 3000), to: const Duration(milliseconds: 3700), - tag: "width", - curve: Curves.decelerate - ).addAnimatable( + tag: widthTag, + curve: Curves.decelerate) + .addAnimatable( animatable: new Tween(begin: 50.0, end: 300.0), - from: const Duration(seconds: 0), + from: const Duration(seconds: 0), to: const Duration(milliseconds: 3000), - tag: "height", - curve: Curves.ease - ).addAnimatable( + tag: heightTag, + curve: Curves.ease) + .addAnimatable( animatable: new Tween(begin: 300.0, end: 450.0), - from: const Duration(milliseconds: 3000), + from: const Duration(milliseconds: 3000), to: const Duration(milliseconds: 3800), - tag: "height", - curve: Curves.decelerate - ).animate(controller); - - + tag: heightTag, + curve: Curves.decelerate) + .animate(controller); } - Future _playAnimation() async { try { await controller.forward().orCancel; @@ -86,9 +87,9 @@ class _SameVariableAnimationPageState extends State w builder: (context, child) { return new Center( child: new Container( - color: sequenceAnimation["color"].value, - height: sequenceAnimation["height"].value, - width: sequenceAnimation["width"].value, + color: sequenceAnimation.get(colorTag).value, + height: sequenceAnimation.get(heightTag).value, + width: sequenceAnimation.get(widthTag).value, ), ); }, @@ -97,5 +98,4 @@ class _SameVariableAnimationPageState extends State w ), ); } - } diff --git a/example/lib/sequence_page.dart b/example/lib/sequence_page.dart index 3893cff..ec432ca 100755 --- a/example/lib/sequence_page.dart +++ b/example/lib/sequence_page.dart @@ -9,7 +9,7 @@ class SequencePage extends StatefulWidget { } class _SequencePageState extends State with SingleTickerProviderStateMixin{ - + static final colorTag = SequenceAnimationTag(); late AnimationController controller; late SequenceAnimation sequenceAnimation; @@ -25,19 +25,19 @@ class _SequencePageState extends State with SingleTickerProviderSt animatable: new ColorTween(begin: Colors.red, end: Colors.yellow), from: const Duration(seconds: 0), to: const Duration(seconds: 2), - tag: "color" + tag: colorTag ).addAnimatable( animatable: new ColorTween(begin: Colors.yellow, end: Colors.blueAccent), from: const Duration(seconds: 2), to: const Duration(seconds: 4), - tag: "color", + tag: colorTag, curve: Curves.easeOut ).addAnimatable( animatable: new ColorTween(begin: Colors.blueAccent, end: Colors.pink), // animatable: new Tween(begin: 200.0, end: 40.0), from: const Duration(seconds: 5), to: const Duration(seconds: 6), - tag: "color", + tag: colorTag, curve: Curves.fastOutSlowIn ).animate(controller); @@ -75,7 +75,7 @@ class _SequencePageState extends State with SingleTickerProviderSt builder: (context, child) { return new Center( child: new Container( - color: sequenceAnimation["color"].value, + color: sequenceAnimation.get(colorTag).value, height: 200.0, width: 200.0, ), diff --git a/example/lib/staggered_animation_replication.dart b/example/lib/staggered_animation_replication.dart index 26c4d1f..836b64e 100755 --- a/example/lib/staggered_animation_replication.dart +++ b/example/lib/staggered_animation_replication.dart @@ -10,6 +10,13 @@ class StaggeredAnimationReplication extends StatefulWidget { class _StaggeredAnimationReplicationState extends State with SingleTickerProviderStateMixin{ + static final opacityKey = SequenceAnimationTag(); + static final widthKey = SequenceAnimationTag(); + static final heightKey = SequenceAnimationTag(); + static final paddingKey = SequenceAnimationTag(); + static final borderRadiusKey = SequenceAnimationTag(); + static final colorKey = SequenceAnimationTag(); + late AnimationController controller; late SequenceAnimation sequenceAnimation; @@ -24,37 +31,37 @@ class _StaggeredAnimationReplicationState extends State(begin: 50.0, end: 150.0), from: const Duration(milliseconds: 250), to: const Duration(milliseconds: 500), curve: Curves.ease, - tag: "width" + tag: widthKey ).addAnimatable( animatable: new Tween(begin: 50.0, end: 150.0), from: const Duration(milliseconds: 500), to: const Duration(milliseconds: 750), curve: Curves.ease, - tag: "height" + tag: heightKey ).addAnimatable( animatable: new EdgeInsetsTween(begin: const EdgeInsets.only(bottom: 16.0), end: const EdgeInsets.only(bottom: 75.0),), from: const Duration(milliseconds: 500), to: const Duration(milliseconds: 750), curve: Curves.ease, - tag: "padding" + tag: paddingKey ).addAnimatable( animatable: new BorderRadiusTween(begin: new BorderRadius.circular(4.0), end: new BorderRadius.circular(75.0),), from: const Duration(milliseconds: 750), to: const Duration(milliseconds: 1000), curve: Curves.ease, - tag: "borderRadius" + tag: borderRadiusKey ).addAnimatable( animatable: new ColorTween(begin: Colors.indigo[100], end: Colors.orange[400],), from: const Duration(milliseconds: 1000), to: const Duration(milliseconds: 1500), curve: Curves.ease, - tag: "color" + tag: colorKey ).animate(controller); } @@ -66,20 +73,20 @@ class _StaggeredAnimationReplicationState extends State { _AnimationInformation({ required this.animatable, required this.from, @@ -10,23 +9,52 @@ class _AnimationInformation { required this.tag, }); - final Animatable animatable; + final Animatable animatable; final Duration from; final Duration to; final Curve curve; - final Object tag; + final SequenceAnimationTag tag; + + IntervalAnimatable createIntervalAnimatable({ + required Animatable animatable, + required Animatable defaultAnimatable, + required double begin, + required double end, + }) => + IntervalAnimatable( + animatable: animatable, + defaultAnimatable: defaultAnimatable, + begin: begin, + end: end, + ); +} + +class SequenceAnimationTag { + SequenceAnimationTag() : id = _id++; + + const SequenceAnimationTag.id(this.id); + + static int _id = 0; + + final Object id; + @override + bool operator ==(Object other) => + identical(this, other) || + other is SequenceAnimationTag && + runtimeType == other.runtimeType && + id == other.id; + @override + int get hashCode => id.hashCode; } class SequenceAnimationBuilder { List<_AnimationInformation> _animations = []; - // Returns the duration of the current animation chain Duration getCurrentDuration() { return Duration(microseconds: _currentLengthInMicroSeconds()); } - /// Convenient wrapper to add an animatable after the last one with a specific tag finished is finished /// /// The tags must be comparable! Strings, enums work, when using objects, be sure to override the == method @@ -53,19 +81,29 @@ class SequenceAnimationBuilder { /// /// The animation with tag "animation" will start at second 3 and run until second 4. /// - SequenceAnimationBuilder addAnimatableAfterLastOneWithTag({ + SequenceAnimationBuilder addAnimatableAfterLastOneWithTag>({ required Object lastTag, - required Animatable animatable, + required A animatable, Duration delay: Duration.zero, required Duration duration, Curve curve: Curves.linear, - required Object tag, + required SequenceAnimationTag tag, }) { - assert(_animations.isNotEmpty, "Can not add animatable after last one if there is no animatable yet"); - var start = _animations.cast<_AnimationInformation?>().lastWhere((it) => it?.tag == lastTag, orElse: () => null)?.to; - assert(start != null, "Animation with tag $lastTag can not be found before $tag"); + assert(_animations.isNotEmpty, + "Can not add animatable after last one if there is no animatable yet"); + var start = _animations + .cast<_AnimationInformation?>() + .lastWhere((it) => it?.tag == lastTag, orElse: () => null) + ?.to; + assert(start != null, + "Animation with tag $lastTag can not be found before $tag"); start!; - return addAnimatable(animatable: animatable, from: start + delay, to: start + delay + duration, tag: tag, curve: curve); + return addAnimatable( + animatable: animatable, + from: start + delay, + to: start + delay + duration, + tag: tag, + curve: curve); } /// Convenient wrapper to add an animatable after the last one is finished @@ -91,29 +129,40 @@ class SequenceAnimationBuilder { /// /// The animation with tag "animation" will start at second 3 and run until second 4. /// - SequenceAnimationBuilder addAnimatableAfterLastOne({ - required Animatable animatable, + SequenceAnimationBuilder addAnimatableAfterLastOne>({ + required A animatable, Duration delay: Duration.zero, required Duration duration, Curve curve: Curves.linear, - required Object tag, + required SequenceAnimationTag tag, }) { - assert(_animations.isNotEmpty, "Can not add animatable after last one if there is no animatable yet"); + assert(_animations.isNotEmpty, + "Can not add animatable after last one if there is no animatable yet"); var start = _animations.last.to; - return addAnimatable(animatable: animatable, from: start + delay, to: start + delay + duration, tag: tag, curve: curve); + return addAnimatable( + animatable: animatable, + from: start + delay, + to: start + delay + duration, + tag: tag, + curve: curve); } /// Convenient wrapper around to specify an animatable using a duration instead of end point /// /// Instead of specifying from and to, you specify start and duration - SequenceAnimationBuilder addAnimatableUsingDuration({ - required Animatable animatable, + SequenceAnimationBuilder addAnimatableUsingDuration>({ + required A animatable, required Duration start, required Duration duration, Curve curve: Curves.linear, - required Object tag, - }) { - return addAnimatable(animatable: animatable, from: start, to: start + duration, tag: tag, curve: curve); + required SequenceAnimationTag tag, + }) { + return addAnimatable( + animatable: animatable, + from: start, + to: start + duration, + tag: tag, + curve: curve); } /// Adds an [Animatable] to the sequence, in the most cases this would be a [Tween]. @@ -137,15 +186,16 @@ class SequenceAnimationBuilder { /// .animate(controller); /// ``` /// - SequenceAnimationBuilder addAnimatable({ - required Animatable animatable, + SequenceAnimationBuilder addAnimatable>({ + required A animatable, required Duration from, required Duration to, Curve curve: Curves.linear, - required Object tag, + required SequenceAnimationTag tag, }) { + assert(T.toString() != 'Object'); assert(to >= from); - _animations.add(new _AnimationInformation( + _animations.add(new _AnimationInformation( animatable: animatable, from: from, to: to, curve: curve, tag: tag)); return this; } @@ -167,9 +217,9 @@ class SequenceAnimationBuilder { // Sets the duration of the controller controller.duration = new Duration(microseconds: longestTimeMicro); - Map animatables = {}; - Map begins = {}; - Map ends = {}; + Map animatables = {}; + Map begins = {}; + Map ends = {}; _animations.forEach((info) { assert(info.to.inMicroseconds <= longestTimeMicro); @@ -178,8 +228,7 @@ class SequenceAnimationBuilder { double end = info.to.inMicroseconds / longestTimeMicro; Interval intervalCurve = new Interval(begin, end, curve: info.curve); if (animatables[info.tag] == null) { - animatables[info.tag] = - IntervalAnimatable.chainCurve(info.animatable, intervalCurve); + animatables[info.tag] = info.animatable.chainCurve(intervalCurve); begins[info.tag] = begin; ends[info.tag] = end; } else { @@ -188,12 +237,10 @@ class SequenceAnimationBuilder { "When animating the same property you need to: \n" "a) Have them not overlap \n" "b) Add them in an ordered fashion\n" - "Animation with tag ${info.tag} ends at ${ends[info.tag]} but also begins at $begin" - ); - animatables[info.tag] = new IntervalAnimatable( + "Animation with tag ${info.tag} ends at ${ends[info.tag]} but also begins at $begin"); + animatables[info.tag] = info.createIntervalAnimatable( animatable: animatables[info.tag]!, - defaultAnimatable: - IntervalAnimatable.chainCurve(info.animatable, intervalCurve), + defaultAnimatable: info.animatable.chainCurve(intervalCurve), begin: begins[info.tag]!, end: ends[info.tag]!, ); @@ -201,7 +248,7 @@ class SequenceAnimationBuilder { } }); - Map result = {}; + Map result = {}; animatables.forEach((tag, animInfo) { result[tag] = animInfo.animate(controller); @@ -212,16 +259,17 @@ class SequenceAnimationBuilder { } class SequenceAnimation { - final Map _animations; + final Map _animations; /// Use the [SequenceAnimationBuilder] to construct this class. SequenceAnimation._internal(this._animations); /// Returns the animation with a given tag, this animation is tied to the controller. - Animation operator [](Object key) { - assert(_animations.containsKey(key), - "There was no animatable with the key: $key"); - return _animations[key]!; + Animation get(SequenceAnimationTag tag) { + assert(_animations.containsKey(tag), + "There was no animatable with the tag: $tag"); + + return _animations[tag]! as Animation; } } @@ -235,8 +283,8 @@ class IntervalAnimatable extends Animatable { required this.end, }); - final Animatable animatable; - final Animatable defaultAnimatable; + final Animatable animatable; + final Animatable defaultAnimatable; /// The relative begin to of [animatable] /// If your [AnimationController] is running from 0->1, this needs to be a value between those two @@ -246,12 +294,6 @@ class IntervalAnimatable extends Animatable { /// If your [AnimationController] is running from 0->1, this needs to be a value between those two final double end; - /// Chains an [Animatable] with a [CurveTween] and the given [Interval]. - /// Basically, the animation is being constrained to the given interval - static Animatable chainCurve(Animatable parent, Interval interval) { - return parent.chain(new CurveTween(curve: interval)); - } - @override T transform(double t) { if (t >= begin && t <= end) { @@ -261,3 +303,11 @@ class IntervalAnimatable extends Animatable { } } } + +extension Chain on Animatable { + /// Chains an [Animatable] with a [CurveTween] and the given [Interval]. + /// Basically, the animation is being constrained to the given interval + Animatable chainCurve(Interval interval) { + return chain(new CurveTween(curve: interval)); + } +} diff --git a/test/animation_sequence_tests.dart b/test/animation_sequence_tests.dart index 3c3143b..335e499 100755 --- a/test/animation_sequence_tests.dart +++ b/test/animation_sequence_tests.dart @@ -25,7 +25,7 @@ void main() { expect(controller.duration, isNull); - String seqKey = "color"; + final seqKey = SequenceAnimationTag(); SequenceAnimation sequenceAnimation = new SequenceAnimationBuilder() .addAnimatable( tag: seqKey, @@ -39,7 +39,7 @@ void main() { expect(controller.duration, isNotNull); - ValueKey key = new ValueKey("color"); + final key = new ValueKey("color"); // Build our app and trigger a frame. await tester.pumpWidget(new AnimatedBuilder(animation: controller, builder: (context, child) { @@ -47,7 +47,7 @@ void main() { key: key, width: 200.0, height: 200.0, - color: sequenceAnimation[seqKey].value, + color: sequenceAnimation.get(seqKey).value, ); })); @@ -74,10 +74,11 @@ void main() { expect(controller.duration, isNull); - String seqKey = "color"; + final seqKey = SequenceAnimationTag(); SequenceAnimation sequenceAnimation = new SequenceAnimationBuilder() .addAnimatable( tag: seqKey, + //animatable: Tween(), animatable: new ColorTween(begin: Colors.red, end: Colors.blue), from: const Duration(seconds: 0), to: const Duration(seconds: 1)) @@ -103,7 +104,7 @@ void main() { expect(controller.duration, equals(const Duration(seconds: 4))); - ValueKey key = new ValueKey("color"); + final key = new ValueKey("color"); // Build our app and trigger a frame. await tester.pumpWidget(new AnimatedBuilder(animation: controller, builder: (context, child) { @@ -111,7 +112,7 @@ void main() { key: key, width: 200.0, height: 200.0, - color: sequenceAnimation[seqKey].value, + color: sequenceAnimation.get(seqKey).value, ); })); @@ -137,7 +138,7 @@ void main() { expect(controller.duration, isNull); - String seqKey = "color"; + final seqKey = SequenceAnimationTag(); SequenceAnimation sequenceAnimation = new SequenceAnimationBuilder() .addAnimatable( tag: seqKey, @@ -163,7 +164,7 @@ void main() { expect(controller.duration, equals(const Duration(seconds: 4))); - ValueKey key = new ValueKey("color"); + final key = new ValueKey("color"); // Build our app and trigger a frame. await tester.pumpWidget(new AnimatedBuilder(animation: controller, builder: (context, child) { @@ -171,7 +172,7 @@ void main() { key: key, width: 200.0, height: 200.0, - color: sequenceAnimation[seqKey].value, + color: sequenceAnimation.get(seqKey).value, ); })); @@ -204,7 +205,7 @@ void main() { expect(controller.duration, equals(const Duration(seconds: 0))); try { - sequenceAnimation["doesntExit"]; + sequenceAnimation.get(SequenceAnimationTag()); } catch(e) { expect(e,isAssertionError); } @@ -224,8 +225,9 @@ void main() { expect(controller.duration, isNull); - String colorKey = "color"; - String widthKey = "width"; + final colorKey = SequenceAnimationTag(); + final widthKey = SequenceAnimationTag(); + SequenceAnimation sequenceAnimation = new SequenceAnimationBuilder() .addAnimatable( tag: colorKey, @@ -234,7 +236,7 @@ void main() { to: const Duration(seconds: 1)) .addAnimatable( tag: widthKey, - animatable: new Tween(begin: 50.0, end: 500.0), + animatable: new Tween(begin: 50.0, end: 500.0), from: const Duration(seconds: 1), to: const Duration(seconds: 5)) .addAnimatable( @@ -259,15 +261,15 @@ void main() { expect(controller.duration, equals(const Duration(seconds: 5))); - ValueKey key = new ValueKey("color"); + final key = new ValueKey("color"); // Build our app and trigger a frame. await tester.pumpWidget(new AnimatedBuilder(animation: controller, builder: (context, child) { return new Container( key: key, - width: sequenceAnimation[widthKey].value, + width: sequenceAnimation.get(widthKey).value, height: 200.0, - color: sequenceAnimation[colorKey].value, + color: sequenceAnimation.get(colorKey).value, ); })); @@ -299,15 +301,17 @@ void main() { AnimationController controller = new AnimationController(vsync: const TestVSync()); + final tag = SequenceAnimationTag(); + try { new SequenceAnimationBuilder() .addAnimatable( - tag: "s", + tag: tag, animatable: new ColorTween(begin: Colors.red, end: Colors.yellow), from: const Duration(seconds: 0), to: const Duration(seconds: 2)) .addAnimatable( - tag: "s", + tag: tag, animatable: new ColorTween(begin: Colors.red, end: Colors.yellow), from: const Duration(seconds: 1), to: const Duration(seconds: 2)) @@ -320,12 +324,12 @@ void main() { try { new SequenceAnimationBuilder() .addAnimatable( - tag: "s", + tag: tag, animatable: new ColorTween(begin: Colors.red, end: Colors.yellow), from: const Duration(seconds: 0), to: const Duration(milliseconds: 2000)) .addAnimatable( - tag: "s", + tag: tag, animatable: new ColorTween(begin: Colors.red, end: Colors.yellow), from: const Duration(milliseconds: 1999), to: const Duration(milliseconds: 2001)) @@ -334,74 +338,4 @@ void main() { expect(e, isAssertionError); } }); - - - testWidgets('Same tag but different types', (WidgetTester tester) async { - AnimationController controller = new AnimationController(vsync: const TestVSync()); - try { - new SequenceAnimationBuilder() - .addAnimatable( - tag: "s", - animatable: new ColorTween(begin: Colors.red, end: Colors.yellow), - from: const Duration(seconds: 0), - to: const Duration(seconds: 2)) - .addAnimatable( - tag: "s", - animatable: new Tween(begin: 0.0, end: 100.0), - from: const Duration(seconds: 3), - to: const Duration(seconds: 4)) - .animate(controller); - } catch(e) { - expect(e, isAssertionError); - } - }); - - - testWidgets('Uses object key', (WidgetTester tester) async { - - AnimationController controller = new AnimationController(vsync: const TestVSync()); - - Object seqKey = false; - SequenceAnimation sequenceAnimation = new SequenceAnimationBuilder() - .addAnimatable( - tag: seqKey, - animatable: new ColorTween(begin: Colors.red, end: Colors.yellow), - from: const Duration(seconds: 0), - to: const Duration(seconds: 1)) - .animate(controller); - - - - expect(controller.duration, isNotNull); - - - ValueKey key = new ValueKey("color"); - - // Build our app and trigger a frame. - await tester.pumpWidget(new AnimatedBuilder(animation: controller, builder: (context, child) { - return new Container( - key: key, - width: 200.0, - height: 200.0, - color: sequenceAnimation[seqKey].value, - ); - })); - - - expect(find.byKey(key), findsOneWidget); - Color color = tester.widget(find.byKey(key)).color!; - expect(color, Colors.red); - - - controller.forward(); - await tester.pumpAndSettle(); - - - color = tester.widget(find.byKey(key)).color!; - expect(color, Colors.yellow); - - - }); } - -