From b68e3be48ceac0ffd8ab85735e6b5f4cc3ee0f20 Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Tue, 19 May 2020 16:03:46 -0700 Subject: [PATCH 01/12] Introduce and migrate to DualTransitionBuilder --- packages/animations/.gitignore | 49 +--- .../ios/Runner.xcodeproj/project.pbxproj | 20 +- .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../lib/src/dual_transition_builder.dart | 228 +++++++++++++++ .../lib/src/fade_scale_transition.dart | 164 +++-------- .../lib/src/fade_through_transition.dart | 177 +++--------- .../lib/src/shared_axis_transition.dart | 263 ++++++------------ 7 files changed, 406 insertions(+), 503 deletions(-) create mode 100644 packages/animations/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 packages/animations/lib/src/dual_transition_builder.dart diff --git a/packages/animations/.gitignore b/packages/animations/.gitignore index 3132dc5ff771..f3c205341e7d 100644 --- a/packages/animations/.gitignore +++ b/packages/animations/.gitignore @@ -22,52 +22,23 @@ # Flutter/Dart/Pub related **/doc/api/ +**/ios/Flutter/.last_build_id .dart_tool/ .flutter-plugins +.flutter-plugins-dependencies .packages .pub-cache/ .pub/ -build/ +/build/ -# Android related -**/android/**/gradle-wrapper.jar -**/android/.gradle -**/android/captures/ -**/android/gradlew -**/android/gradlew.bat -**/android/local.properties -**/android/**/GeneratedPluginRegistrant.java +# Web related +lib/generated_plugin_registrant.dart -# iOS/XCode related -**/ios/**/*.mode1v3 -**/ios/**/*.mode2v3 -**/ios/**/*.moved-aside -**/ios/**/*.pbxuser -**/ios/**/*.perspectivev3 -**/ios/**/*sync/ -**/ios/**/.sconsign.dblite -**/ios/**/.tags* -**/ios/**/.vagrant/ -**/ios/**/DerivedData/ -**/ios/**/Icon? -**/ios/**/Pods/ -**/ios/**/.symlinks/ -**/ios/**/profile -**/ios/**/xcuserdata -**/ios/.generated/ -**/ios/Flutter/App.framework -**/ios/Flutter/Flutter.framework -**/ios/Flutter/Generated.xcconfig -**/ios/Flutter/app.flx -**/ios/Flutter/app.zip -**/ios/Flutter/flutter_assets/ -**/ios/Flutter/flutter_export_environment.sh -**/ios/ServiceDefinitions.json -**/ios/Runner/GeneratedPluginRegistrant.* +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json # Exceptions to above rules. -!**/ios/**/default.mode1v3 -!**/ios/**/default.mode2v3 -!**/ios/**/default.pbxuser -!**/ios/**/default.perspectivev3 !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages diff --git a/packages/animations/example/ios/Runner.xcodeproj/project.pbxproj b/packages/animations/example/ios/Runner.xcodeproj/project.pbxproj index 30f054abca57..0ab5cd85b127 100644 --- a/packages/animations/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/animations/example/ios/Runner.xcodeproj/project.pbxproj @@ -9,11 +9,7 @@ /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; - 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; - 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; - 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; @@ -26,8 +22,6 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( - 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, - 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -38,13 +32,11 @@ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; - 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -57,8 +49,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, - 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -68,9 +58,7 @@ 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( - 3B80C3931E831B6300D905FE /* App.framework */, 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, - 9740EEBA1CF902C7004384FC /* Flutter.framework */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, @@ -201,7 +189,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; @@ -253,7 +241,6 @@ /* Begin XCBuildConfiguration section */ 249021D3217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -309,6 +296,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -330,7 +318,6 @@ }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -386,7 +373,6 @@ }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -443,6 +429,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -470,6 +457,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", diff --git a/packages/animations/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/animations/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000000..18d981003d68 --- /dev/null +++ b/packages/animations/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/animations/lib/src/dual_transition_builder.dart b/packages/animations/lib/src/dual_transition_builder.dart new file mode 100644 index 000000000000..68c968b366b6 --- /dev/null +++ b/packages/animations/lib/src/dual_transition_builder.dart @@ -0,0 +1,228 @@ +// Copyright 2020 The Flutter 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/widgets.dart'; + +/// Builder callback used by [DualTransitionBuilder]. +/// +/// The builder is expected to return a transition powered by the provided +/// `animation` and wrapping the provided `child`. +/// +/// The `animation` provided to the builder always runs forward from 0.0 to 1.0. +/// The builder will be called whenever the [Animation.value] of `animation` +/// has changed. +typedef TransitionBuilder = Widget Function( + BuildContext context, + Animation animation, + Widget child, +); + +/// A animated builder that animates its [child] with different transitions +/// based on the [AnimationStatus] of the provided [animation]. +/// +/// While the [animation] runs forward, the [child] is animated according to +/// [forwardBuilder] and while the [animation] is running in reverse, it is +/// animated according to [reverseBuilder]. +/// +/// This widget can be used to specify different enter and exit transitions for +/// a [child]. +class DualTransitionBuilder extends StatefulWidget { + /// Creates a [DualTransitionBuilder]. + /// + /// The [animation], [forwardBuilder], and [reverseBuilder] arguments are + /// required and must not be null. + const DualTransitionBuilder({ + Key key, + @required this.animation, + @required this.forwardBuilder, + @required this.reverseBuilder, + this.child, + }) : assert(animation != null), + assert(forwardBuilder != null), + assert(reverseBuilder != null), + super(key: key); + + /// The animation that drives the [child]'s transition. + /// + /// When this animation runs forward, the [child] transitions as specified by + /// [forwardBuilder]. When it runs in reverse, the child transitions according + /// to [reverseBuilder]. + final Animation animation; + + /// A builder for a transition that wraps [child]. + /// + /// The `animation` provided to this builder is running forward from 0.0 to + /// 1.0 when [animation] runs _forward_. When [animation] runs in reverse, + /// the given animation is set to [kAlwaysCompleteAnimation]. + /// + /// This builder should be used to create the animation that makes the [child] + /// appear on screen. The [child] should be fully visible when the `animation` + /// reaches 1.0. + /// + /// The builder will be called whenever the [Animation.value] of the given + /// `animation` changes. + /// + /// See also: + /// + /// * [reverseBuilder], which builds the transition for making the [child] + /// disappear from the screen. + final TransitionBuilder forwardBuilder; + + /// A builder for a transition that wraps [child]. + /// + /// The `animation` provided to this builder is running forward from 0.0 to + /// 1.0 when [animation] runs in _reverse_. When [animation] runs forward, + /// the given animation is set to [kAlwaysDismissedAnimation]. + /// + /// This builder should be used to create the animation that makes the [child] + /// disappear from the screen. The [child] should be fully invisible when the + /// `animation` reaches 1.0. + /// + /// The builder will be called whenever the [Animation.value] of the given + /// `animation` changes. + /// + /// See also: + /// + /// * [forwardBuilder], which builds the transition for making the [child] + /// appear on screen. + final TransitionBuilder reverseBuilder; + + /// The widget below this widget in the tree. + /// + /// This widget will be wrapped by the transitions built by + /// [forwardBuilder] and [reverseBuilder]. + final Widget child; + + @override + State createState() => _CompositeAnimationState(); +} + +class _CompositeAnimationState extends State { + AnimationStatus _effectiveAnimationStatus; + Animation _forwardAnimation; + Animation _reverseAnimation; + + @override + void initState() { + super.initState(); + _effectiveAnimationStatus = widget.animation.status; + widget.animation.addStatusListener(_animationListener); + _updateAnimations(); + } + + void _animationListener(AnimationStatus animationStatus) { + final AnimationStatus oldEffective = _effectiveAnimationStatus; + _effectiveAnimationStatus = _calculateEffectiveAnimationStatus( + lastEffective: _effectiveAnimationStatus, + current: animationStatus, + ); + if (oldEffective != _effectiveAnimationStatus) { + _updateAnimations(); + } + } + + @override + void didUpdateWidget(DualTransitionBuilder oldWidget) { + super.didUpdateWidget(oldWidget); + _updateAnimationListener( + oldWidget.animation, + widget.animation, + ); + } + + void _updateAnimationListener( + Animation oldAnimation, + Animation animation, + ) { + if (oldAnimation != animation) { + oldAnimation.removeStatusListener(_animationListener); + animation.addStatusListener(_animationListener); + _animationListener(animation.status); + } + } + + // When a transition is interrupted midway we just want to play the ongoing + // animation in reverse. Switching to the actual reverse transition would + // yield a disjoint experience since the forward and reverse transitions are + // very different. + AnimationStatus _calculateEffectiveAnimationStatus({ + @required AnimationStatus lastEffective, + @required AnimationStatus current, + }) { + assert(current != null); + assert(lastEffective != null); + switch (current) { + case AnimationStatus.dismissed: + case AnimationStatus.completed: + return current; + case AnimationStatus.forward: + switch (lastEffective) { + case AnimationStatus.dismissed: + case AnimationStatus.completed: + case AnimationStatus.forward: + return current; + case AnimationStatus.reverse: + return lastEffective; + } + break; + case AnimationStatus.reverse: + switch (lastEffective) { + case AnimationStatus.dismissed: + case AnimationStatus.completed: + case AnimationStatus.reverse: + return current; + case AnimationStatus.forward: + return lastEffective; + } + break; + } + return null; // unreachable + } + + void _updateAnimations() { + switch (_effectiveAnimationStatus) { + case AnimationStatus.dismissed: + case AnimationStatus.forward: + _forwardAnimation = widget.animation; + _reverseAnimation = kAlwaysDismissedAnimation; + break; + case AnimationStatus.reverse: + case AnimationStatus.completed: + _forwardAnimation = kAlwaysCompleteAnimation; + _reverseAnimation = ReverseAnimation(widget.animation); + break; + } + } + + @override + void dispose() { + widget.animation.removeStatusListener(_animationListener); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: _reverseAnimation, + builder: (BuildContext context, Widget child) { + return widget.reverseBuilder( + context, + _reverseAnimation, + child, + ); + }, + child: AnimatedBuilder( + animation: _forwardAnimation, + builder: (BuildContext context, Widget child) { + return widget.forwardBuilder( + context, + _forwardAnimation, + child, + ); + }, + child: widget.child, + ), + ); + } +} diff --git a/packages/animations/lib/src/fade_scale_transition.dart b/packages/animations/lib/src/fade_scale_transition.dart index 89bd42d78872..b01fa1e6b0f6 100644 --- a/packages/animations/lib/src/fade_scale_transition.dart +++ b/packages/animations/lib/src/fade_scale_transition.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; +import 'dual_transition_builder.dart'; import 'modal.dart'; import 'utils/curves.dart'; @@ -102,7 +103,7 @@ class FadeScaleTransitionConfiguration extends ModalConfiguration { /// /// This widget is not to be confused with Flutter's [FadeTransition] widget, /// which animates only the opacity of its child widget. -class FadeScaleTransition extends StatefulWidget { +class FadeScaleTransition extends StatelessWidget { /// Creates a widget that implements the Material fade transition. /// /// The fade pattern is used for UI elements that enter or exit from within @@ -136,143 +137,46 @@ class FadeScaleTransition extends StatefulWidget { /// [secondaryAnimation]. final Widget child; - @override - _FadeScaleTransitionState createState() => _FadeScaleTransitionState(); -} - -class _FadeScaleTransitionState extends State { - AnimationStatus _effectiveAnimationStatus; - - @override - void initState() { - super.initState(); - _effectiveAnimationStatus = widget.animation.status; - widget.animation.addStatusListener(_animationListener); - } - - void _animationListener(AnimationStatus animationStatus) { - _effectiveAnimationStatus = _calculateEffectiveAnimationStatus( - lastEffective: _effectiveAnimationStatus, - current: animationStatus, - ); - } - - // When a transition is interrupted midway we just want to play the ongoing - // animation in reverse. Switching to the actual reverse transition would - // yield a disjoint experience since the forward and reverse transitions are - // very different. - AnimationStatus _calculateEffectiveAnimationStatus({ - @required AnimationStatus lastEffective, - @required AnimationStatus current, - }) { - assert(current != null); - assert(lastEffective != null); - switch (current) { - case AnimationStatus.dismissed: - case AnimationStatus.completed: - return current; - case AnimationStatus.forward: - switch (lastEffective) { - case AnimationStatus.dismissed: - case AnimationStatus.completed: - case AnimationStatus.forward: - return current; - case AnimationStatus.reverse: - return lastEffective; - } - break; - case AnimationStatus.reverse: - switch (lastEffective) { - case AnimationStatus.dismissed: - case AnimationStatus.completed: - case AnimationStatus.reverse: - return current; - case AnimationStatus.forward: - return lastEffective; - } - break; - } - return null; // unreachable - } - - void _updateAnimationListener( - Animation oldAnimation, - Animation animation, - ) { - if (oldAnimation != animation) { - oldAnimation.removeStatusListener(_animationListener); - animation.addStatusListener(_animationListener); - _animationListener(animation.status); - } - } - - @override - void didUpdateWidget(FadeScaleTransition oldWidget) { - super.didUpdateWidget(oldWidget); - _updateAnimationListener( - oldWidget.animation, - widget.animation, - ); - } - - @override - void dispose() { - widget.animation.removeStatusListener(_animationListener); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return AnimatedBuilder( - animation: widget.animation, - builder: (BuildContext context, Widget child) { - assert(_effectiveAnimationStatus != null); - switch (_effectiveAnimationStatus) { - case AnimationStatus.forward: - return _EnterTransition( - animation: widget.animation, - child: child, - ); - case AnimationStatus.dismissed: - case AnimationStatus.reverse: - case AnimationStatus.completed: - return FadeTransition( - opacity: widget.animation, - child: child, - ); - } - return null; // unreachable - }, - child: widget.child, - ); - } -} - -class _EnterTransition extends StatelessWidget { - const _EnterTransition({ - this.animation, - this.child, - }); - - final Animation animation; - final Widget child; - - static Animatable fadeInTransition = CurveTween( + static final Animatable _fadeInTransition = CurveTween( curve: const Interval(0.0, 0.3), ); - static Animatable scaleInTransition = Tween( + static final Animatable _scaleInTransition = Tween( begin: 0.80, end: 1.00, ).chain(CurveTween(curve: decelerateEasing)); + static final Animatable _fadeOutTransition = Tween( + begin: 1.0, + end: 0.0, + ); @override Widget build(BuildContext context) { - return FadeTransition( - opacity: fadeInTransition.animate(animation), - child: ScaleTransition( - scale: scaleInTransition.animate(animation), - child: child, - ), + return DualTransitionBuilder( + animation: animation, + forwardBuilder: ( + BuildContext context, + Animation animation, + Widget child, + ) { + return FadeTransition( + opacity: _fadeInTransition.animate(animation), + child: ScaleTransition( + scale: _scaleInTransition.animate(animation), + child: child, + ), + ); + }, + reverseBuilder: ( + BuildContext context, + Animation animation, + Widget child, + ) { + return FadeTransition( + opacity: _fadeOutTransition.animate(animation), + child: child, + ); + }, + child: child, ); } } diff --git a/packages/animations/lib/src/fade_through_transition.dart b/packages/animations/lib/src/fade_through_transition.dart index de17ff0a45a5..281ae45b24f2 100644 --- a/packages/animations/lib/src/fade_through_transition.dart +++ b/packages/animations/lib/src/fade_through_transition.dart @@ -4,6 +4,8 @@ import 'package:flutter/material.dart'; +import 'dual_transition_builder.dart'; + /// Used by [PageTransitionsTheme] to define a page route transition animation /// in which the outgoing page fades out, then the incoming page fades in and /// scale up. @@ -150,7 +152,7 @@ class FadeThroughPageTransitionsBuilder extends PageTransitionsBuilder { /// ); /// } /// ``` -class FadeThroughTransition extends StatefulWidget { +class FadeThroughTransition extends StatelessWidget { /// Creates a [FadeThroughTransition]. /// /// The [animation] and [secondaryAnimation] argument are required and must @@ -186,152 +188,49 @@ class FadeThroughTransition extends StatefulWidget { final Widget child; @override - State createState() => _FadeThroughTransitionState(); -} - -class _FadeThroughTransitionState extends State { - AnimationStatus _effectiveAnimationStatus; - AnimationStatus _effectiveSecondaryAnimationStatus; - - @override - void initState() { - super.initState(); - _effectiveAnimationStatus = widget.animation.status; - _effectiveSecondaryAnimationStatus = widget.secondaryAnimation.status; - widget.animation.addStatusListener(_animationListener); - widget.secondaryAnimation.addStatusListener(_secondaryAnimationListener); - } - - void _animationListener(AnimationStatus animationStatus) { - _effectiveAnimationStatus = _calculateEffectiveAnimationStatus( - lastEffective: _effectiveAnimationStatus, - current: animationStatus, - ); - } - - void _secondaryAnimationListener(AnimationStatus animationStatus) { - _effectiveSecondaryAnimationStatus = _calculateEffectiveAnimationStatus( - lastEffective: _effectiveSecondaryAnimationStatus, - current: animationStatus, + Widget build(BuildContext context) { + return _ZoomedFadeInFadeOut( + animation: animation, + child: _ZoomedFadeInFadeOut( + animation: ReverseAnimation(secondaryAnimation), + child: child, + ), ); } +} - // When a transition is interrupted midway we just want to play the ongoing - // animation in reverse. Switching to the actual reverse transition would - // yield a disjoint experience since the forward and reverse transitions are - // very different. - AnimationStatus _calculateEffectiveAnimationStatus({ - @required AnimationStatus lastEffective, - @required AnimationStatus current, - }) { - assert(current != null); - assert(lastEffective != null); - switch (current) { - case AnimationStatus.dismissed: - case AnimationStatus.completed: - return current; - case AnimationStatus.forward: - switch (lastEffective) { - case AnimationStatus.dismissed: - case AnimationStatus.completed: - case AnimationStatus.forward: - return current; - case AnimationStatus.reverse: - return lastEffective; - } - break; - case AnimationStatus.reverse: - switch (lastEffective) { - case AnimationStatus.dismissed: - case AnimationStatus.completed: - case AnimationStatus.reverse: - return current; - case AnimationStatus.forward: - return lastEffective; - } - break; - } - return null; // unreachable - } - - @override - void didUpdateWidget(FadeThroughTransition oldWidget) { - super.didUpdateWidget(oldWidget); - if (oldWidget.animation != widget.animation) { - oldWidget.animation.removeStatusListener(_animationListener); - widget.animation.addStatusListener(_animationListener); - _animationListener(widget.animation.status); - } - if (oldWidget.secondaryAnimation != widget.secondaryAnimation) { - oldWidget.secondaryAnimation - .removeStatusListener(_secondaryAnimationListener); - widget.secondaryAnimation.addStatusListener(_secondaryAnimationListener); - _secondaryAnimationListener(widget.secondaryAnimation.status); - } - } - - @override - void dispose() { - widget.animation.removeStatusListener(_animationListener); - widget.secondaryAnimation.removeStatusListener(_secondaryAnimationListener); - super.dispose(); - } +class _ZoomedFadeInFadeOut extends StatelessWidget { + const _ZoomedFadeInFadeOut({Key key, this.animation, this.child}) + : super(key: key); - static final Tween _flippedTween = Tween( - begin: 1.0, - end: 0.0, - ); - static Animation _flip(Animation animation) { - return _flippedTween.animate(animation); - } + final Animation animation; + final Widget child; @override Widget build(BuildContext context) { - return AnimatedBuilder( - animation: widget.animation, - builder: (BuildContext context, Widget child) { - assert(_effectiveAnimationStatus != null); - switch (_effectiveAnimationStatus) { - case AnimationStatus.forward: - return _ZoomedFadeIn( - animation: widget.animation, - child: child, - ); - case AnimationStatus.dismissed: - case AnimationStatus.reverse: - case AnimationStatus.completed: - return _FadeOut( - animation: _flip(widget.animation), - child: child, - ); - } - return null; // unreachable + return DualTransitionBuilder( + animation: animation, + forwardBuilder: ( + BuildContext context, + Animation animation, + Widget child, + ) { + return _ZoomedFadeIn( + animation: animation, + child: child, + ); }, - child: Container( - color: Theme.of(context).canvasColor, - child: AnimatedBuilder( - animation: widget.secondaryAnimation, - builder: (BuildContext context, Widget child) { - assert(_effectiveSecondaryAnimationStatus != null); - switch (_effectiveSecondaryAnimationStatus) { - case AnimationStatus.forward: - return _FadeOut( - child: child, - animation: widget.secondaryAnimation, - ); - case AnimationStatus.dismissed: - case AnimationStatus.reverse: - case AnimationStatus.completed: - return _ZoomedFadeIn( - animation: _flip(widget.secondaryAnimation), - child: child, - ); - } - return null; // unreachable - }, - child: widget.child, - ), - ), + reverseBuilder: ( + BuildContext context, + Animation animation, + Widget child, + ) { + return _FadeOut( + child: child, + animation: animation, + ); + }, + child: child, ); } } diff --git a/packages/animations/lib/src/shared_axis_transition.dart b/packages/animations/lib/src/shared_axis_transition.dart index 52344ce9188a..5c538d1b0ad8 100644 --- a/packages/animations/lib/src/shared_axis_transition.dart +++ b/packages/animations/lib/src/shared_axis_transition.dart @@ -7,6 +7,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; +import 'dual_transition_builder.dart'; import 'utils/curves.dart'; /// Determines which type of shared axis transition is used. @@ -183,7 +184,7 @@ class SharedAxisPageTransitionsBuilder extends PageTransitionsBuilder { /// ); /// } /// ``` -class SharedAxisTransition extends StatefulWidget { +class SharedAxisTransition extends StatelessWidget { /// Creates a [SharedAxisTransition]. /// /// The [animation] and [secondaryAnimation] argument are required and must @@ -234,157 +235,61 @@ class SharedAxisTransition extends StatefulWidget { /// [secondaryAnimation]. final Widget child; - @override - _SharedAxisTransitionState createState() => _SharedAxisTransitionState(); -} - -class _SharedAxisTransitionState extends State { - AnimationStatus _effectiveAnimationStatus; - AnimationStatus _effectiveSecondaryAnimationStatus; - - @override - void initState() { - super.initState(); - _effectiveAnimationStatus = widget.animation.status; - _effectiveSecondaryAnimationStatus = widget.secondaryAnimation.status; - widget.animation.addStatusListener(_animationListener); - widget.secondaryAnimation.addStatusListener(_secondaryAnimationListener); - } - - void _animationListener(AnimationStatus animationStatus) { - _effectiveAnimationStatus = _calculateEffectiveAnimationStatus( - lastEffective: _effectiveAnimationStatus, - current: animationStatus, - ); - } - - void _secondaryAnimationListener(AnimationStatus animationStatus) { - _effectiveSecondaryAnimationStatus = _calculateEffectiveAnimationStatus( - lastEffective: _effectiveSecondaryAnimationStatus, - current: animationStatus, - ); - } - - // When a transition is interrupted midway we just want to play the ongoing - // animation in reverse. Switching to the actual reverse transition would - // yield a disjoint experience since the forward and reverse transitions are - // very different. - AnimationStatus _calculateEffectiveAnimationStatus({ - @required AnimationStatus lastEffective, - @required AnimationStatus current, - }) { - assert(current != null); - assert(lastEffective != null); - switch (current) { - case AnimationStatus.dismissed: - case AnimationStatus.completed: - return current; - case AnimationStatus.forward: - switch (lastEffective) { - case AnimationStatus.dismissed: - case AnimationStatus.completed: - case AnimationStatus.forward: - return current; - case AnimationStatus.reverse: - return lastEffective; - } - break; - case AnimationStatus.reverse: - switch (lastEffective) { - case AnimationStatus.dismissed: - case AnimationStatus.completed: - case AnimationStatus.reverse: - return current; - case AnimationStatus.forward: - return lastEffective; - } - break; - } - return null; // unreachable - } - - @override - void didUpdateWidget(SharedAxisTransition oldWidget) { - super.didUpdateWidget(oldWidget); - if (oldWidget.animation != widget.animation) { - oldWidget.animation.removeStatusListener(_animationListener); - widget.animation.addStatusListener(_animationListener); - _animationListener(widget.animation.status); - } - if (oldWidget.secondaryAnimation != widget.secondaryAnimation) { - oldWidget.secondaryAnimation - .removeStatusListener(_secondaryAnimationListener); - widget.secondaryAnimation.addStatusListener(_secondaryAnimationListener); - _secondaryAnimationListener(widget.secondaryAnimation.status); - } - } - - @override - void dispose() { - widget.animation.removeStatusListener(_animationListener); - widget.secondaryAnimation.removeStatusListener(_secondaryAnimationListener); - super.dispose(); - } - - static final Tween _flippedTween = Tween( - begin: 1.0, - end: 0.0, - ); - static Animation _flip(Animation animation) { - return _flippedTween.animate(animation); - } - @override Widget build(BuildContext context) { - return AnimatedBuilder( - animation: widget.animation, - builder: (BuildContext context, Widget child) { - assert(_effectiveAnimationStatus != null); - switch (_effectiveAnimationStatus) { - case AnimationStatus.forward: - return _EnterTransition( - animation: widget.animation, - transitionType: widget.transitionType, - child: child, - ); - case AnimationStatus.dismissed: - case AnimationStatus.reverse: - case AnimationStatus.completed: - return _ExitTransition( - animation: _flip(widget.animation), - transitionType: widget.transitionType, - reverse: true, - fillColor: widget.fillColor, - child: child, - ); - } - return null; // unreachable + return DualTransitionBuilder( + animation: animation, + forwardBuilder: ( + BuildContext context, + Animation animation, + Widget child, + ) { + return _EnterTransition( + animation: animation, + transitionType: transitionType, + fillColor: fillColor ?? Theme.of(context).canvasColor, + child: child, + ); }, - child: AnimatedBuilder( - animation: widget.secondaryAnimation, - builder: (BuildContext context, Widget child) { - assert(_effectiveSecondaryAnimationStatus != null); - switch (_effectiveSecondaryAnimationStatus) { - case AnimationStatus.forward: - return _ExitTransition( - animation: widget.secondaryAnimation, - transitionType: widget.transitionType, - fillColor: widget.fillColor, - child: child, - ); - case AnimationStatus.dismissed: - case AnimationStatus.reverse: - case AnimationStatus.completed: - return _EnterTransition( - animation: _flip(widget.secondaryAnimation), - transitionType: widget.transitionType, - reverse: true, - child: child, - ); - } - return null; // unreachable + reverseBuilder: ( + BuildContext context, + Animation animation, + Widget child, + ) { + return _ExitTransition( + animation: animation, + transitionType: transitionType, + reverse: true, + child: child, + ); + }, + child: DualTransitionBuilder( + animation: ReverseAnimation(secondaryAnimation), + forwardBuilder: ( + BuildContext context, + Animation animation, + Widget child, + ) { + return _EnterTransition( + animation: animation, + transitionType: transitionType, + reverse: true, + fillColor: fillColor ?? Theme.of(context).canvasColor, + child: child, + ); + }, + reverseBuilder: ( + BuildContext context, + Animation animation, + Widget child, + ) { + return _ExitTransition( + animation: animation, + transitionType: transitionType, + child: child, + ); }, - child: widget.child, + child: child, ), ); } @@ -395,6 +300,7 @@ class _EnterTransition extends StatelessWidget { this.animation, this.transitionType, this.reverse = false, + this.fillColor, this.child, }); @@ -402,6 +308,7 @@ class _EnterTransition extends StatelessWidget { final SharedAxisTransitionType transitionType; final Widget child; final bool reverse; + final Color fillColor; static final Animatable _fadeInTransition = CurveTween( curve: decelerateEasing, @@ -428,9 +335,12 @@ class _EnterTransition extends StatelessWidget { return FadeTransition( opacity: _fadeInTransition.animate(animation), - child: Transform.translate( - offset: slideInTransition.evaluate(animation), - child: child, + child: Container( + color: fillColor, + child: Transform.translate( + offset: slideInTransition.evaluate(animation), + child: child, + ), ), ); break; @@ -442,19 +352,25 @@ class _EnterTransition extends StatelessWidget { return FadeTransition( opacity: _fadeInTransition.animate(animation), - child: Transform.translate( - offset: slideInTransition.evaluate(animation), - child: child, + child: Container( + color: fillColor, + child: Transform.translate( + offset: slideInTransition.evaluate(animation), + child: child, + ), ), ); break; case SharedAxisTransitionType.scaled: return FadeTransition( opacity: _fadeInTransition.animate(animation), - child: ScaleTransition( - scale: (!reverse ? _scaleUpTransition : _scaleDownTransition) - .animate(animation), - child: child, + child: Container( + color: fillColor, + child: ScaleTransition( + scale: (!reverse ? _scaleUpTransition : _scaleDownTransition) + .animate(animation), + child: child, + ), ), ); break; @@ -468,14 +384,12 @@ class _ExitTransition extends StatelessWidget { this.animation, this.transitionType, this.reverse = false, - this.fillColor, this.child, }); final Animation animation; final SharedAxisTransitionType transitionType; final bool reverse; - final Color fillColor; final Widget child; static final Animatable _fadeOutTransition = FlippedCurveTween( @@ -503,12 +417,9 @@ class _ExitTransition extends StatelessWidget { return FadeTransition( opacity: _fadeOutTransition.animate(animation), - child: Container( - color: fillColor ?? Theme.of(context).canvasColor, - child: Transform.translate( - offset: slideOutTransition.evaluate(animation), - child: child, - ), + child: Transform.translate( + offset: slideOutTransition.evaluate(animation), + child: child, ), ); break; @@ -520,25 +431,19 @@ class _ExitTransition extends StatelessWidget { return FadeTransition( opacity: _fadeOutTransition.animate(animation), - child: Container( - color: fillColor ?? Theme.of(context).canvasColor, - child: Transform.translate( - offset: slideOutTransition.evaluate(animation), - child: child, - ), + child: Transform.translate( + offset: slideOutTransition.evaluate(animation), + child: child, ), ); break; case SharedAxisTransitionType.scaled: return FadeTransition( opacity: _fadeOutTransition.animate(animation), - child: Container( - color: fillColor ?? Theme.of(context).canvasColor, - child: ScaleTransition( - scale: (!reverse ? _scaleUpTransition : _scaleDownTransition) - .animate(animation), - child: child, - ), + child: ScaleTransition( + scale: (!reverse ? _scaleUpTransition : _scaleDownTransition) + .animate(animation), + child: child, ), ); break; From 619c78e1d48ad6346448b6c706969ff16ab9d114 Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Tue, 19 May 2020 16:51:10 -0700 Subject: [PATCH 02/12] ++ --- .../ios/Runner.xcodeproj/project.pbxproj | 13 ++-- packages/animations/example/lib/main.dart | 42 ++++++++++- .../lib/src/dual_transition_builder.dart | 13 ++-- .../lib/src/shared_axis_transition.dart | 71 ++++++++++--------- 4 files changed, 91 insertions(+), 48 deletions(-) diff --git a/packages/animations/example/ios/Runner.xcodeproj/project.pbxproj b/packages/animations/example/ios/Runner.xcodeproj/project.pbxproj index 0ab5cd85b127..87070b88ee4c 100644 --- a/packages/animations/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/animations/example/ios/Runner.xcodeproj/project.pbxproj @@ -140,6 +140,7 @@ TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; + DevelopmentTeam = 6Z5R667H2S; LastSwiftMigration = 1100; }; }; @@ -296,7 +297,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = 6Z5R667H2S; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -308,7 +309,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.packages.animations.example; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.packages.animations.examplefd; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -429,7 +430,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = 6Z5R667H2S; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -441,7 +442,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.packages.animations.example; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.packages.animations.examplefd; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -457,7 +458,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = 6Z5R667H2S; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -469,7 +470,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.packages.animations.example; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.packages.animations.examplefd; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; diff --git a/packages/animations/example/lib/main.dart b/packages/animations/example/lib/main.dart index e29ecad870ac..be9c66a48d67 100644 --- a/packages/animations/example/lib/main.dart +++ b/packages/animations/example/lib/main.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:animations/animations.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; @@ -18,7 +19,8 @@ void main() { ).copyWith( pageTransitionsTheme: const PageTransitionsTheme( builders: { - TargetPlatform.android: ZoomPageTransitionsBuilder(), + TargetPlatform.android: FadeThroughPageTransitionsBuilder(), + TargetPlatform.iOS: FadeThroughPageTransitionsBuilder(), }, ), ), @@ -96,6 +98,15 @@ class _TransitionsHomePageState extends State<_TransitionsHomePage> { ); }, ), + _TransitionListTile( + title: 'Fade', + subtitle: 'FadeScaleTransition', + onTap: () { + Navigator.of(context).push( + _getRoute(), + ); + }, + ), ], ), ), @@ -123,6 +134,35 @@ class _TransitionsHomePageState extends State<_TransitionsHomePage> { } } +int count = 0; + +Route _getRoute() { + Color color = count++ % 2 == 0 ? Colors.red : Colors.blue; + return MaterialPageRoute( + builder: (BuildContext context) { + return Container( + color: color, + child: Row( + children: [ + RaisedButton( + child: const Text('pop'), + onPressed: () { + Navigator.pop(context); + }, + ), + RaisedButton( + child: const Text('push'), + onPressed: () { + Navigator.push(context, _getRoute()); + }, + ) + ], + ), + ); + }, + ); +} + class _TransitionListTile extends StatelessWidget { const _TransitionListTile({ this.onTap, diff --git a/packages/animations/lib/src/dual_transition_builder.dart b/packages/animations/lib/src/dual_transition_builder.dart index 68c968b366b6..8045fa49c644 100644 --- a/packages/animations/lib/src/dual_transition_builder.dart +++ b/packages/animations/lib/src/dual_transition_builder.dart @@ -204,23 +204,24 @@ class _CompositeAnimationState extends State { @override Widget build(BuildContext context) { return AnimatedBuilder( - animation: _reverseAnimation, + animation: _forwardAnimation, builder: (BuildContext context, Widget child) { - return widget.reverseBuilder( + return widget.forwardBuilder( context, - _reverseAnimation, + _forwardAnimation, child, ); }, child: AnimatedBuilder( - animation: _forwardAnimation, + animation: _reverseAnimation, builder: (BuildContext context, Widget child) { - return widget.forwardBuilder( + return widget.reverseBuilder( context, - _forwardAnimation, + _reverseAnimation, child, ); }, + child: widget.child, ), ); diff --git a/packages/animations/lib/src/shared_axis_transition.dart b/packages/animations/lib/src/shared_axis_transition.dart index 5c538d1b0ad8..da2b2d755f84 100644 --- a/packages/animations/lib/src/shared_axis_transition.dart +++ b/packages/animations/lib/src/shared_axis_transition.dart @@ -80,9 +80,9 @@ enum SharedAxisTransitionType { class SharedAxisPageTransitionsBuilder extends PageTransitionsBuilder { /// Construct a [SharedAxisPageTransitionsBuilder]. const SharedAxisPageTransitionsBuilder({ - this.transitionType, + @required this.transitionType, this.fillColor, - }); + }) : assert(transitionType != null); /// Determines which [SharedAxisTransitionType] to build. final SharedAxisTransitionType transitionType; @@ -237,6 +237,7 @@ class SharedAxisTransition extends StatelessWidget { @override Widget build(BuildContext context) { + final Color color = fillColor ?? Theme.of(context).canvasColor; return DualTransitionBuilder( animation: animation, forwardBuilder: ( @@ -247,7 +248,6 @@ class SharedAxisTransition extends StatelessWidget { return _EnterTransition( animation: animation, transitionType: transitionType, - fillColor: fillColor ?? Theme.of(context).canvasColor, child: child, ); }, @@ -260,6 +260,7 @@ class SharedAxisTransition extends StatelessWidget { animation: animation, transitionType: transitionType, reverse: true, + fillColor: color, child: child, ); }, @@ -274,7 +275,6 @@ class SharedAxisTransition extends StatelessWidget { animation: animation, transitionType: transitionType, reverse: true, - fillColor: fillColor ?? Theme.of(context).canvasColor, child: child, ); }, @@ -286,6 +286,7 @@ class SharedAxisTransition extends StatelessWidget { return _ExitTransition( animation: animation, transitionType: transitionType, + fillColor: color, child: child, ); }, @@ -300,7 +301,6 @@ class _EnterTransition extends StatelessWidget { this.animation, this.transitionType, this.reverse = false, - this.fillColor, this.child, }); @@ -308,7 +308,6 @@ class _EnterTransition extends StatelessWidget { final SharedAxisTransitionType transitionType; final Widget child; final bool reverse; - final Color fillColor; static final Animatable _fadeInTransition = CurveTween( curve: decelerateEasing, @@ -335,12 +334,9 @@ class _EnterTransition extends StatelessWidget { return FadeTransition( opacity: _fadeInTransition.animate(animation), - child: Container( - color: fillColor, - child: Transform.translate( - offset: slideInTransition.evaluate(animation), - child: child, - ), + child: Transform.translate( + offset: slideInTransition.evaluate(animation), + child: child, ), ); break; @@ -352,25 +348,19 @@ class _EnterTransition extends StatelessWidget { return FadeTransition( opacity: _fadeInTransition.animate(animation), - child: Container( - color: fillColor, - child: Transform.translate( - offset: slideInTransition.evaluate(animation), - child: child, - ), + child: Transform.translate( + offset: slideInTransition.evaluate(animation), + child: child, ), ); break; case SharedAxisTransitionType.scaled: return FadeTransition( opacity: _fadeInTransition.animate(animation), - child: Container( - color: fillColor, - child: ScaleTransition( - scale: (!reverse ? _scaleUpTransition : _scaleDownTransition) - .animate(animation), - child: child, - ), + child: ScaleTransition( + scale: (!reverse ? _scaleUpTransition : _scaleDownTransition) + .animate(animation), + child: child, ), ); break; @@ -384,12 +374,14 @@ class _ExitTransition extends StatelessWidget { this.animation, this.transitionType, this.reverse = false, + @required this.fillColor, this.child, }); final Animation animation; final SharedAxisTransitionType transitionType; final bool reverse; + final Color fillColor; final Widget child; static final Animatable _fadeOutTransition = FlippedCurveTween( @@ -417,9 +409,12 @@ class _ExitTransition extends StatelessWidget { return FadeTransition( opacity: _fadeOutTransition.animate(animation), - child: Transform.translate( - offset: slideOutTransition.evaluate(animation), - child: child, + child: Container( + color: fillColor, + child: Transform.translate( + offset: slideOutTransition.evaluate(animation), + child: child, + ), ), ); break; @@ -431,19 +426,25 @@ class _ExitTransition extends StatelessWidget { return FadeTransition( opacity: _fadeOutTransition.animate(animation), - child: Transform.translate( - offset: slideOutTransition.evaluate(animation), - child: child, + child: Container( + color: fillColor, + child: Transform.translate( + offset: slideOutTransition.evaluate(animation), + child: child, + ), ), ); break; case SharedAxisTransitionType.scaled: return FadeTransition( opacity: _fadeOutTransition.animate(animation), - child: ScaleTransition( - scale: (!reverse ? _scaleUpTransition : _scaleDownTransition) - .animate(animation), - child: child, + child: Container( + color: fillColor, + child: ScaleTransition( + scale: (!reverse ? _scaleUpTransition : _scaleDownTransition) + .animate(animation), + child: child, + ), ), ); break; From f4aa3a50225ea427ff7b0ab080fe980765dda7ef Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Tue, 19 May 2020 17:02:40 -0700 Subject: [PATCH 03/12] ++ --- packages/animations/example/lib/main.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/animations/example/lib/main.dart b/packages/animations/example/lib/main.dart index be9c66a48d67..99935ec52b93 100644 --- a/packages/animations/example/lib/main.dart +++ b/packages/animations/example/lib/main.dart @@ -17,10 +17,11 @@ void main() { theme: ThemeData.from( colorScheme: const ColorScheme.light(), ).copyWith( + platform: TargetPlatform.android, pageTransitionsTheme: const PageTransitionsTheme( builders: { - TargetPlatform.android: FadeThroughPageTransitionsBuilder(), - TargetPlatform.iOS: FadeThroughPageTransitionsBuilder(), + TargetPlatform.android: SharedAxisPageTransitionsBuilder(transitionType: SharedAxisTransitionType.horizontal), + TargetPlatform.iOS: SharedAxisPageTransitionsBuilder(transitionType: SharedAxisTransitionType.horizontal), }, ), ), @@ -137,7 +138,7 @@ class _TransitionsHomePageState extends State<_TransitionsHomePage> { int count = 0; Route _getRoute() { - Color color = count++ % 2 == 0 ? Colors.red : Colors.blue; + final Color color = count++ % 2 == 0 ? Colors.red : Colors.blue; return MaterialPageRoute( builder: (BuildContext context) { return Container( From 6cf9e6a2bde4ffc6f8efc134e743b2320ec3e1d4 Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Tue, 19 May 2020 17:21:44 -0700 Subject: [PATCH 04/12] ++ --- .../ios/Runner.xcodeproj/project.pbxproj | 13 +++--- packages/animations/example/lib/main.dart | 43 +------------------ .../lib/src/fade_through_transition.dart | 23 ++++++++-- 3 files changed, 26 insertions(+), 53 deletions(-) diff --git a/packages/animations/example/ios/Runner.xcodeproj/project.pbxproj b/packages/animations/example/ios/Runner.xcodeproj/project.pbxproj index 87070b88ee4c..0ab5cd85b127 100644 --- a/packages/animations/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/animations/example/ios/Runner.xcodeproj/project.pbxproj @@ -140,7 +140,6 @@ TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; - DevelopmentTeam = 6Z5R667H2S; LastSwiftMigration = 1100; }; }; @@ -297,7 +296,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 6Z5R667H2S; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -309,7 +308,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.packages.animations.examplefd; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.packages.animations.example; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -430,7 +429,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 6Z5R667H2S; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -442,7 +441,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.packages.animations.examplefd; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.packages.animations.example; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -458,7 +457,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 6Z5R667H2S; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -470,7 +469,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.packages.animations.examplefd; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.packages.animations.example; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; diff --git a/packages/animations/example/lib/main.dart b/packages/animations/example/lib/main.dart index 99935ec52b93..e29ecad870ac 100644 --- a/packages/animations/example/lib/main.dart +++ b/packages/animations/example/lib/main.dart @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:animations/animations.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; @@ -17,11 +16,9 @@ void main() { theme: ThemeData.from( colorScheme: const ColorScheme.light(), ).copyWith( - platform: TargetPlatform.android, pageTransitionsTheme: const PageTransitionsTheme( builders: { - TargetPlatform.android: SharedAxisPageTransitionsBuilder(transitionType: SharedAxisTransitionType.horizontal), - TargetPlatform.iOS: SharedAxisPageTransitionsBuilder(transitionType: SharedAxisTransitionType.horizontal), + TargetPlatform.android: ZoomPageTransitionsBuilder(), }, ), ), @@ -99,15 +96,6 @@ class _TransitionsHomePageState extends State<_TransitionsHomePage> { ); }, ), - _TransitionListTile( - title: 'Fade', - subtitle: 'FadeScaleTransition', - onTap: () { - Navigator.of(context).push( - _getRoute(), - ); - }, - ), ], ), ), @@ -135,35 +123,6 @@ class _TransitionsHomePageState extends State<_TransitionsHomePage> { } } -int count = 0; - -Route _getRoute() { - final Color color = count++ % 2 == 0 ? Colors.red : Colors.blue; - return MaterialPageRoute( - builder: (BuildContext context) { - return Container( - color: color, - child: Row( - children: [ - RaisedButton( - child: const Text('pop'), - onPressed: () { - Navigator.pop(context); - }, - ), - RaisedButton( - child: const Text('push'), - onPressed: () { - Navigator.push(context, _getRoute()); - }, - ) - ], - ), - ); - }, - ); -} - class _TransitionListTile extends StatelessWidget { const _TransitionListTile({ this.onTap, diff --git a/packages/animations/lib/src/fade_through_transition.dart b/packages/animations/lib/src/fade_through_transition.dart index 281ae45b24f2..f6978e4bbbdf 100644 --- a/packages/animations/lib/src/fade_through_transition.dart +++ b/packages/animations/lib/src/fade_through_transition.dart @@ -62,7 +62,12 @@ import 'dual_transition_builder.dart'; /// ``` class FadeThroughPageTransitionsBuilder extends PageTransitionsBuilder { /// Creates a [FadeThroughPageTransitionsBuilder]. - const FadeThroughPageTransitionsBuilder(); + const FadeThroughPageTransitionsBuilder({this.fillColor}); + + /// The color to use for the background color during the transition. + /// + /// This defaults to the [Theme]'s [ThemeData.canvasColor]. + final Color fillColor; @override Widget buildTransitions( @@ -75,6 +80,7 @@ class FadeThroughPageTransitionsBuilder extends PageTransitionsBuilder { return FadeThroughTransition( animation: animation, secondaryAnimation: secondaryAnimation, + fillColor: fillColor, child: child, ); } @@ -160,6 +166,7 @@ class FadeThroughTransition extends StatelessWidget { const FadeThroughTransition({ @required this.animation, @required this.secondaryAnimation, + this.fillColor, this.child, }) : assert(animation != null), assert(secondaryAnimation != null); @@ -181,6 +188,11 @@ class FadeThroughTransition extends StatelessWidget { // property when the [FadeThroughTransition] is used as a page transition. final Animation secondaryAnimation; + /// The color to use for the background color during the transition. + /// + /// This defaults to the [Theme]'s [ThemeData.canvasColor]. + final Color fillColor; + /// The widget below this widget in the tree. /// /// This widget will transition in and out as driven by [animation] and @@ -191,9 +203,12 @@ class FadeThroughTransition extends StatelessWidget { Widget build(BuildContext context) { return _ZoomedFadeInFadeOut( animation: animation, - child: _ZoomedFadeInFadeOut( - animation: ReverseAnimation(secondaryAnimation), - child: child, + child: Container( + color: fillColor ?? Theme.of(context).canvasColor, + child: _ZoomedFadeInFadeOut( + animation: ReverseAnimation(secondaryAnimation), + child: child, + ), ), ); } From 485e8c9e20cefcc556cac9d10694ed3a0658f96e Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Tue, 19 May 2020 17:32:31 -0700 Subject: [PATCH 05/12] format --- packages/animations/lib/src/dual_transition_builder.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/animations/lib/src/dual_transition_builder.dart b/packages/animations/lib/src/dual_transition_builder.dart index 8045fa49c644..18b86ee612b1 100644 --- a/packages/animations/lib/src/dual_transition_builder.dart +++ b/packages/animations/lib/src/dual_transition_builder.dart @@ -221,7 +221,6 @@ class _CompositeAnimationState extends State { child, ); }, - child: widget.child, ), ); From 329008610834a275a332db9473ad0c2e10d732df Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Wed, 20 May 2020 10:33:58 -0700 Subject: [PATCH 06/12] review comments --- .../lib/src/dual_transition_builder.dart | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/packages/animations/lib/src/dual_transition_builder.dart b/packages/animations/lib/src/dual_transition_builder.dart index 18b86ee612b1..be7e372146a3 100644 --- a/packages/animations/lib/src/dual_transition_builder.dart +++ b/packages/animations/lib/src/dual_transition_builder.dart @@ -18,15 +18,19 @@ typedef TransitionBuilder = Widget Function( Widget child, ); -/// A animated builder that animates its [child] with different transitions -/// based on the [AnimationStatus] of the provided [animation]. +/// A transition builder that animates its [child] based on the +/// [AnimationStatus] of the provided [animation]. +/// +/// This widget can be used to specify different enter and exit transitions for +/// a [child]. /// /// While the [animation] runs forward, the [child] is animated according to /// [forwardBuilder] and while the [animation] is running in reverse, it is /// animated according to [reverseBuilder]. /// -/// This widget can be used to specify different enter and exit transitions for -/// a [child]. +/// Using this builder allows the widget tree to maintain its shape by nesting +/// the enter and exit transitions. This ensures that no state information of +/// any descendant widget is lost when the transition starts or completes. class DualTransitionBuilder extends StatefulWidget { /// Creates a [DualTransitionBuilder]. /// @@ -50,16 +54,15 @@ class DualTransitionBuilder extends StatefulWidget { /// to [reverseBuilder]. final Animation animation; - /// A builder for a transition that wraps [child]. + /// A builder for the transition that makes [child] appear on screen. + /// + /// The [child] should be fully visible when the provided `animation` reaches + /// 1.0. /// /// The `animation` provided to this builder is running forward from 0.0 to /// 1.0 when [animation] runs _forward_. When [animation] runs in reverse, /// the given animation is set to [kAlwaysCompleteAnimation]. /// - /// This builder should be used to create the animation that makes the [child] - /// appear on screen. The [child] should be fully visible when the `animation` - /// reaches 1.0. - /// /// The builder will be called whenever the [Animation.value] of the given /// `animation` changes. /// @@ -69,16 +72,15 @@ class DualTransitionBuilder extends StatefulWidget { /// disappear from the screen. final TransitionBuilder forwardBuilder; - /// A builder for a transition that wraps [child]. + /// A builder for a transition that makes [child] disappear from the screen.. + /// + /// The [child] should be fully invisible when the provided `animation` + /// reaches 1.0. /// /// The `animation` provided to this builder is running forward from 0.0 to /// 1.0 when [animation] runs in _reverse_. When [animation] runs forward, /// the given animation is set to [kAlwaysDismissedAnimation]. /// - /// This builder should be used to create the animation that makes the [child] - /// disappear from the screen. The [child] should be fully invisible when the - /// `animation` reaches 1.0. - /// /// The builder will be called whenever the [Animation.value] of the given /// `animation` changes. /// @@ -88,9 +90,9 @@ class DualTransitionBuilder extends StatefulWidget { /// appear on screen. final TransitionBuilder reverseBuilder; - /// The widget below this widget in the tree. + /// The widget below this [DualTransitionBuilder] in the tree. /// - /// This widget will be wrapped by the transitions built by + /// This child widget will be wrapped by the transitions built by /// [forwardBuilder] and [reverseBuilder]. final Widget child; From 3e8f48bbc44f6e29f6dd769c0729df7c21391c45 Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Wed, 20 May 2020 12:28:05 -0700 Subject: [PATCH 07/12] add state tests for transitions --- .../test/fade_scale_transition_test.dart | 51 +++++ .../test/fade_through_transition_test.dart | 65 ++++++ .../test/shared_axis_transition_test.dart | 198 ++++++++++++++++++ 3 files changed, 314 insertions(+) diff --git a/packages/animations/test/fade_scale_transition_test.dart b/packages/animations/test/fade_scale_transition_test.dart index dc45396ec3c5..314d2f862262 100644 --- a/packages/animations/test/fade_scale_transition_test.dart +++ b/packages/animations/test/fade_scale_transition_test.dart @@ -395,6 +395,57 @@ void main() { expect(find.byKey(topKey), findsNothing); }, ); + + testWidgets( + 'should preserve state', + (WidgetTester tester) async { + final AnimationController controller = AnimationController( + vsync: const TestVSync(), + duration: const Duration(milliseconds: 300), + ); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Center( + child: FadeScaleTransition( + animation: controller, + child: const _FlutterLogoModal(), + ), + ), + ), + ), + ); + + final State state = + tester.state(find.byType(_FlutterLogoModal),); + expect(state, isNotNull); + + controller.forward(); + await tester.pump(); + expect(state, same(tester.state(find.byType(_FlutterLogoModal)))); + await tester.pump(const Duration(milliseconds: 150)); + expect(state, same(tester.state(find.byType(_FlutterLogoModal)))); + await tester.pumpAndSettle(); + expect(state, same(tester.state(find.byType(_FlutterLogoModal)))); + + controller.reverse(); + await tester.pump(); + expect(state, same(tester.state(find.byType(_FlutterLogoModal)))); + await tester.pump(const Duration(milliseconds: 150)); + expect(state, same(tester.state(find.byType(_FlutterLogoModal)))); + await tester.pumpAndSettle(); + expect(state, same(tester.state(find.byType(_FlutterLogoModal)))); + + controller.forward(); + await tester.pump(); + expect(state, same(tester.state(find.byType(_FlutterLogoModal)))); + await tester.pump(const Duration(milliseconds: 150)); + expect(state, same(tester.state(find.byType(_FlutterLogoModal)))); + await tester.pumpAndSettle(); + expect(state, same(tester.state(find.byType(_FlutterLogoModal)))); + }, + ); } double _getOpacity(GlobalKey key, WidgetTester tester) { diff --git a/packages/animations/test/fade_through_transition_test.dart b/packages/animations/test/fade_through_transition_test.dart index 3406da1cdc92..551d403b586f 100644 --- a/packages/animations/test/fade_through_transition_test.dart +++ b/packages/animations/test/fade_through_transition_test.dart @@ -364,6 +364,71 @@ void main() { ); expect(find.byKey(const ValueKey(topRoute)), findsNothing); }); + + testWidgets('should keep state', (WidgetTester tester) async { + final AnimationController animation = AnimationController( + vsync: const TestVSync(), + duration: const Duration(milliseconds: 300), + ); + final AnimationController secondaryAnimation = AnimationController( + vsync: const TestVSync(), + duration: const Duration(milliseconds: 300), + ); + await tester.pumpWidget(Directionality( + textDirection: TextDirection.ltr, + child: Center( + child: FadeThroughTransition( + child: const _StatefulTestWidget(name: 'Foo'), + animation: animation, + secondaryAnimation: secondaryAnimation, + ), + ), + )); + final State state = tester.state( + find.byType(_StatefulTestWidget), + ); + expect(state, isNotNull); + + animation.forward(); + await tester.pump(); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + await tester.pump(const Duration(milliseconds: 150)); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + await tester.pump(const Duration(milliseconds: 150)); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + await tester.pumpAndSettle(); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + + secondaryAnimation.forward(); + await tester.pump(); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + await tester.pump(const Duration(milliseconds: 150)); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + await tester.pump(const Duration(milliseconds: 150)); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + await tester.pumpAndSettle(); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + + secondaryAnimation.reverse(); + await tester.pump(); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + await tester.pump(const Duration(milliseconds: 150)); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + await tester.pump(const Duration(milliseconds: 150)); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + await tester.pumpAndSettle(); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + + animation.reverse(); + await tester.pump(); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + await tester.pump(const Duration(milliseconds: 150)); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + await tester.pump(const Duration(milliseconds: 150)); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + await tester.pumpAndSettle(); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + }); } double _getOpacity(String key, WidgetTester tester) { diff --git a/packages/animations/test/shared_axis_transition_test.dart b/packages/animations/test/shared_axis_transition_test.dart index 443b7bd0863d..687d94203586 100644 --- a/packages/animations/test/shared_axis_transition_test.dart +++ b/packages/animations/test/shared_axis_transition_test.dart @@ -583,6 +583,72 @@ void main() { expect(fillContainerFinder, findsOneWidget); expect(tester.widget(fillContainerFinder).color, Colors.green); }); + + testWidgets('should keep state', (WidgetTester tester) async { + final AnimationController animation = AnimationController( + vsync: const TestVSync(), + duration: const Duration(milliseconds: 300), + ); + final AnimationController secondaryAnimation = AnimationController( + vsync: const TestVSync(), + duration: const Duration(milliseconds: 300), + ); + await tester.pumpWidget(Directionality( + textDirection: TextDirection.ltr, + child: Center( + child: SharedAxisTransition( + transitionType: SharedAxisTransitionType.horizontal, + child: const _StatefulTestWidget(name: 'Foo'), + animation: animation, + secondaryAnimation: secondaryAnimation, + ), + ), + )); + final State state = tester.state( + find.byType(_StatefulTestWidget), + ); + expect(state, isNotNull); + + animation.forward(); + await tester.pump(); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + await tester.pump(const Duration(milliseconds: 150)); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + await tester.pump(const Duration(milliseconds: 150)); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + await tester.pumpAndSettle(); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + + secondaryAnimation.forward(); + await tester.pump(); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + await tester.pump(const Duration(milliseconds: 150)); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + await tester.pump(const Duration(milliseconds: 150)); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + await tester.pumpAndSettle(); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + + secondaryAnimation.reverse(); + await tester.pump(); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + await tester.pump(const Duration(milliseconds: 150)); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + await tester.pump(const Duration(milliseconds: 150)); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + await tester.pumpAndSettle(); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + + animation.reverse(); + await tester.pump(); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + await tester.pump(const Duration(milliseconds: 150)); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + await tester.pump(const Duration(milliseconds: 150)); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + await tester.pumpAndSettle(); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + }); }); group('SharedAxisTransitionType.vertical', () { @@ -1160,6 +1226,72 @@ void main() { expect(fillContainerFinder, findsOneWidget); expect(tester.widget(fillContainerFinder).color, Colors.green); }); + + testWidgets('should keep state', (WidgetTester tester) async { + final AnimationController animation = AnimationController( + vsync: const TestVSync(), + duration: const Duration(milliseconds: 300), + ); + final AnimationController secondaryAnimation = AnimationController( + vsync: const TestVSync(), + duration: const Duration(milliseconds: 300), + ); + await tester.pumpWidget(Directionality( + textDirection: TextDirection.ltr, + child: Center( + child: SharedAxisTransition( + transitionType: SharedAxisTransitionType.vertical, + child: const _StatefulTestWidget(name: 'Foo'), + animation: animation, + secondaryAnimation: secondaryAnimation, + ), + ), + )); + final State state = tester.state( + find.byType(_StatefulTestWidget), + ); + expect(state, isNotNull); + + animation.forward(); + await tester.pump(); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + await tester.pump(const Duration(milliseconds: 150)); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + await tester.pump(const Duration(milliseconds: 150)); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + await tester.pumpAndSettle(); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + + secondaryAnimation.forward(); + await tester.pump(); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + await tester.pump(const Duration(milliseconds: 150)); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + await tester.pump(const Duration(milliseconds: 150)); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + await tester.pumpAndSettle(); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + + secondaryAnimation.reverse(); + await tester.pump(); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + await tester.pump(const Duration(milliseconds: 150)); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + await tester.pump(const Duration(milliseconds: 150)); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + await tester.pumpAndSettle(); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + + animation.reverse(); + await tester.pump(); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + await tester.pump(const Duration(milliseconds: 150)); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + await tester.pump(const Duration(milliseconds: 150)); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + await tester.pumpAndSettle(); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + }); }); group('SharedAxisTransitionType.scaled', () { @@ -1630,6 +1762,72 @@ void main() { expect(fillContainerFinder, findsOneWidget); expect(tester.widget(fillContainerFinder).color, Colors.green); }); + + testWidgets('should keep state', (WidgetTester tester) async { + final AnimationController animation = AnimationController( + vsync: const TestVSync(), + duration: const Duration(milliseconds: 300), + ); + final AnimationController secondaryAnimation = AnimationController( + vsync: const TestVSync(), + duration: const Duration(milliseconds: 300), + ); + await tester.pumpWidget(Directionality( + textDirection: TextDirection.ltr, + child: Center( + child: SharedAxisTransition( + transitionType: SharedAxisTransitionType.scaled, + child: const _StatefulTestWidget(name: 'Foo'), + animation: animation, + secondaryAnimation: secondaryAnimation, + ), + ), + )); + final State state = tester.state( + find.byType(_StatefulTestWidget), + ); + expect(state, isNotNull); + + animation.forward(); + await tester.pump(); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + await tester.pump(const Duration(milliseconds: 150)); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + await tester.pump(const Duration(milliseconds: 150)); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + await tester.pumpAndSettle(); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + + secondaryAnimation.forward(); + await tester.pump(); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + await tester.pump(const Duration(milliseconds: 150)); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + await tester.pump(const Duration(milliseconds: 150)); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + await tester.pumpAndSettle(); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + + secondaryAnimation.reverse(); + await tester.pump(); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + await tester.pump(const Duration(milliseconds: 150)); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + await tester.pump(const Duration(milliseconds: 150)); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + await tester.pumpAndSettle(); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + + animation.reverse(); + await tester.pump(); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + await tester.pump(const Duration(milliseconds: 150)); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + await tester.pump(const Duration(milliseconds: 150)); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + await tester.pumpAndSettle(); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + }); }); } From b2223d05807343f632336215f4afc600d6989854 Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Wed, 20 May 2020 12:30:06 -0700 Subject: [PATCH 08/12] format --- packages/animations/test/fade_scale_transition_test.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/animations/test/fade_scale_transition_test.dart b/packages/animations/test/fade_scale_transition_test.dart index 314d2f862262..6577be9d490f 100644 --- a/packages/animations/test/fade_scale_transition_test.dart +++ b/packages/animations/test/fade_scale_transition_test.dart @@ -417,8 +417,9 @@ void main() { ), ); - final State state = - tester.state(find.byType(_FlutterLogoModal),); + final State state = tester.state( + find.byType(_FlutterLogoModal), + ); expect(state, isNotNull); controller.forward(); From 3684bea3649411b650754bd71b99bbba69ed0558 Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Wed, 20 May 2020 13:13:33 -0700 Subject: [PATCH 09/12] more tests and an optimization --- .../lib/src/dual_transition_builder.dart | 44 ++++++------------- .../lib/src/shared_axis_transition.dart | 40 +++++++++++++---- 2 files changed, 45 insertions(+), 39 deletions(-) diff --git a/packages/animations/lib/src/dual_transition_builder.dart b/packages/animations/lib/src/dual_transition_builder.dart index be7e372146a3..fddbf2567283 100644 --- a/packages/animations/lib/src/dual_transition_builder.dart +++ b/packages/animations/lib/src/dual_transition_builder.dart @@ -63,9 +63,6 @@ class DualTransitionBuilder extends StatefulWidget { /// 1.0 when [animation] runs _forward_. When [animation] runs in reverse, /// the given animation is set to [kAlwaysCompleteAnimation]. /// - /// The builder will be called whenever the [Animation.value] of the given - /// `animation` changes. - /// /// See also: /// /// * [reverseBuilder], which builds the transition for making the [child] @@ -81,9 +78,6 @@ class DualTransitionBuilder extends StatefulWidget { /// 1.0 when [animation] runs in _reverse_. When [animation] runs forward, /// the given animation is set to [kAlwaysDismissedAnimation]. /// - /// The builder will be called whenever the [Animation.value] of the given - /// `animation` changes. - /// /// See also: /// /// * [forwardBuilder], which builds the transition for making the [child] @@ -102,8 +96,8 @@ class DualTransitionBuilder extends StatefulWidget { class _CompositeAnimationState extends State { AnimationStatus _effectiveAnimationStatus; - Animation _forwardAnimation; - Animation _reverseAnimation; + final ProxyAnimation _forwardAnimation = ProxyAnimation(); + final ProxyAnimation _reverseAnimation = ProxyAnimation(); @override void initState() { @@ -186,13 +180,13 @@ class _CompositeAnimationState extends State { switch (_effectiveAnimationStatus) { case AnimationStatus.dismissed: case AnimationStatus.forward: - _forwardAnimation = widget.animation; - _reverseAnimation = kAlwaysDismissedAnimation; + _forwardAnimation.parent = widget.animation; + _reverseAnimation.parent = kAlwaysDismissedAnimation; break; case AnimationStatus.reverse: case AnimationStatus.completed: - _forwardAnimation = kAlwaysCompleteAnimation; - _reverseAnimation = ReverseAnimation(widget.animation); + _forwardAnimation.parent = kAlwaysCompleteAnimation; + _reverseAnimation.parent = ReverseAnimation(widget.animation); break; } } @@ -205,25 +199,13 @@ class _CompositeAnimationState extends State { @override Widget build(BuildContext context) { - return AnimatedBuilder( - animation: _forwardAnimation, - builder: (BuildContext context, Widget child) { - return widget.forwardBuilder( - context, - _forwardAnimation, - child, - ); - }, - child: AnimatedBuilder( - animation: _reverseAnimation, - builder: (BuildContext context, Widget child) { - return widget.reverseBuilder( - context, - _reverseAnimation, - child, - ); - }, - child: widget.child, + return widget.forwardBuilder( + context, + _forwardAnimation, + widget.reverseBuilder( + context, + _reverseAnimation, + widget.child, ), ); } diff --git a/packages/animations/lib/src/shared_axis_transition.dart b/packages/animations/lib/src/shared_axis_transition.dart index da2b2d755f84..3439bd49a249 100644 --- a/packages/animations/lib/src/shared_axis_transition.dart +++ b/packages/animations/lib/src/shared_axis_transition.dart @@ -334,8 +334,14 @@ class _EnterTransition extends StatelessWidget { return FadeTransition( opacity: _fadeInTransition.animate(animation), - child: Transform.translate( - offset: slideInTransition.evaluate(animation), + child: AnimatedBuilder( + animation: animation, + builder: (BuildContext context, Widget child) { + return Transform.translate( + offset: slideInTransition.evaluate(animation), + child: child, + ); + }, child: child, ), ); @@ -348,8 +354,14 @@ class _EnterTransition extends StatelessWidget { return FadeTransition( opacity: _fadeInTransition.animate(animation), - child: Transform.translate( - offset: slideInTransition.evaluate(animation), + child: AnimatedBuilder( + animation: animation, + builder: (BuildContext context, Widget child) { + return Transform.translate( + offset: slideInTransition.evaluate(animation), + child: child, + ); + }, child: child, ), ); @@ -411,8 +423,14 @@ class _ExitTransition extends StatelessWidget { opacity: _fadeOutTransition.animate(animation), child: Container( color: fillColor, - child: Transform.translate( - offset: slideOutTransition.evaluate(animation), + child: AnimatedBuilder( + animation: animation, + builder: (BuildContext context, Widget child) { + return Transform.translate( + offset: slideOutTransition.evaluate(animation), + child: child, + ); + }, child: child, ), ), @@ -428,8 +446,14 @@ class _ExitTransition extends StatelessWidget { opacity: _fadeOutTransition.animate(animation), child: Container( color: fillColor, - child: Transform.translate( - offset: slideOutTransition.evaluate(animation), + child: AnimatedBuilder( + animation: animation, + builder: (BuildContext context, Widget child) { + return Transform.translate( + offset: slideOutTransition.evaluate(animation), + child: child, + ); + }, child: child, ), ), From 0fc939a3ae142a469c67ce8a19c2b9cdd0b87bea Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Wed, 20 May 2020 13:17:01 -0700 Subject: [PATCH 10/12] added missing file --- .../test/dual_transition_builder_test.dart | 299 ++++++++++++++++++ 1 file changed, 299 insertions(+) create mode 100644 packages/animations/test/dual_transition_builder_test.dart diff --git a/packages/animations/test/dual_transition_builder_test.dart b/packages/animations/test/dual_transition_builder_test.dart new file mode 100644 index 000000000000..4a1d65e4da4a --- /dev/null +++ b/packages/animations/test/dual_transition_builder_test.dart @@ -0,0 +1,299 @@ +// Copyright 2020 The Flutter 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:animations/src/dual_transition_builder.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter/widgets.dart'; + +void main() { + testWidgets('runs animations', (WidgetTester tester) async { + final AnimationController controller = AnimationController( + vsync: const TestVSync(), + duration: const Duration(milliseconds: 300), + ); + + await tester.pumpWidget(Center( + child: DualTransitionBuilder( + animation: controller, + forwardBuilder: ( + BuildContext context, + Animation animation, + Widget child, + ) { + return ScaleTransition( + scale: animation, + child: child, + ); + }, + reverseBuilder: ( + BuildContext context, + Animation animation, + Widget child, + ) { + return FadeTransition( + opacity: Tween(begin: 1.0, end: 0.0).animate(animation), + child: child, + ); + }, + child: Container( + color: Colors.green, + height: 100, + width: 100, + ), + ), + )); + expect(_getScale(tester), 0.0); + expect(_getOpacity(tester), 1.0); + + controller.forward(); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 150)); + expect(_getScale(tester), 0.5); + expect(_getOpacity(tester), 1.0); + + await tester.pump(const Duration(milliseconds: 150)); + expect(_getScale(tester), 1.0); + expect(_getOpacity(tester), 1.0); + + await tester.pumpAndSettle(); + expect(_getScale(tester), 1.0); + expect(_getOpacity(tester), 1.0); + + controller.reverse(); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 150)); + expect(_getScale(tester), 1.0); + expect(_getOpacity(tester), 0.5); + + await tester.pump(const Duration(milliseconds: 150)); + expect(_getScale(tester), 1.0); + expect(_getOpacity(tester), 0.0); + + await tester.pumpAndSettle(); + expect(_getScale(tester), 0.0); + expect(_getOpacity(tester), 1.0); + }); + + testWidgets('keeps state', (WidgetTester tester) async { + final AnimationController controller = AnimationController( + vsync: const TestVSync(), + duration: const Duration(milliseconds: 300), + ); + + await tester.pumpWidget(Directionality( + textDirection: TextDirection.ltr, + child: Center( + child: DualTransitionBuilder( + animation: controller, + forwardBuilder: ( + BuildContext context, + Animation animation, + Widget child, + ) { + return ScaleTransition( + scale: animation, + child: child, + ); + }, + reverseBuilder: ( + BuildContext context, + Animation animation, + Widget child, + ) { + return FadeTransition( + opacity: Tween(begin: 1.0, end: 0.0).animate(animation), + child: child, + ); + }, + child: const _StatefulTestWidget(name: 'Foo'), + ), + ), + )); + final State state = + tester.state(find.byType(_StatefulTestWidget)); + expect(state, isNotNull); + + controller.forward(); + await tester.pump(); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + await tester.pump(const Duration(milliseconds: 150)); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + + await tester.pump(const Duration(milliseconds: 150)); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + + await tester.pumpAndSettle(); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + + controller.reverse(); + await tester.pump(); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + await tester.pump(const Duration(milliseconds: 150)); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + + await tester.pump(const Duration(milliseconds: 150)); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + + await tester.pumpAndSettle(); + expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); + }); + + testWidgets('does not jump when interrupted - forward', + (WidgetTester tester) async { + final AnimationController controller = AnimationController( + vsync: const TestVSync(), + duration: const Duration(milliseconds: 300), + ); + await tester.pumpWidget(Center( + child: DualTransitionBuilder( + animation: controller, + forwardBuilder: ( + BuildContext context, + Animation animation, + Widget child, + ) { + return ScaleTransition( + scale: animation, + child: child, + ); + }, + reverseBuilder: ( + BuildContext context, + Animation animation, + Widget child, + ) { + return FadeTransition( + opacity: Tween(begin: 1.0, end: 0.0).animate(animation), + child: child, + ); + }, + child: Container( + color: Colors.green, + height: 100, + width: 100, + ), + ), + )); + expect(_getScale(tester), 0.0); + expect(_getOpacity(tester), 1.0); + + controller.forward(); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 150)); + expect(_getScale(tester), 0.5); + expect(_getOpacity(tester), 1.0); + + controller.reverse(); + expect(_getScale(tester), 0.5); + expect(_getOpacity(tester), 1.0); + await tester.pump(); + expect(_getScale(tester), 0.5); + expect(_getOpacity(tester), 1.0); + + await tester.pump(const Duration(milliseconds: 75)); + expect(_getScale(tester), 0.25); + expect(_getOpacity(tester), 1.0); + + await tester.pump(const Duration(milliseconds: 75)); + expect(_getScale(tester), 0.0); + expect(_getOpacity(tester), 1.0); + + await tester.pumpAndSettle(); + expect(_getScale(tester), 0.0); + expect(_getOpacity(tester), 1.0); + }); + + testWidgets('does not jump when interrupted - reverse', + (WidgetTester tester) async { + final AnimationController controller = AnimationController( + value: 1.0, + vsync: const TestVSync(), + duration: const Duration(milliseconds: 300), + ); + await tester.pumpWidget(Center( + child: DualTransitionBuilder( + animation: controller, + forwardBuilder: ( + BuildContext context, + Animation animation, + Widget child, + ) { + return ScaleTransition( + scale: animation, + child: child, + ); + }, + reverseBuilder: ( + BuildContext context, + Animation animation, + Widget child, + ) { + return FadeTransition( + opacity: Tween(begin: 1.0, end: 0.0).animate(animation), + child: child, + ); + }, + child: Container( + color: Colors.green, + height: 100, + width: 100, + ), + ), + )); + expect(_getScale(tester), 1.0); + expect(_getOpacity(tester), 1.0); + + controller.reverse(); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 150)); + expect(_getScale(tester), 1.0); + expect(_getOpacity(tester), 0.5); + + controller.forward(); + expect(_getScale(tester), 1.0); + expect(_getOpacity(tester), 0.5); + await tester.pump(); + expect(_getScale(tester), 1.0); + expect(_getOpacity(tester), 0.5); + + await tester.pump(const Duration(milliseconds: 75)); + expect(_getScale(tester), 1.0); + expect(_getOpacity(tester), 0.75); + + await tester.pump(const Duration(milliseconds: 75)); + expect(_getScale(tester), 1.0); + expect(_getOpacity(tester), 1.0); + + await tester.pumpAndSettle(); + expect(_getScale(tester), 1.0); + expect(_getOpacity(tester), 1.0); + }); +} + +double _getScale(WidgetTester tester) { + final ScaleTransition scale = tester.widget(find.byType(ScaleTransition)); + return scale.scale.value; +} + +double _getOpacity(WidgetTester tester) { + final FadeTransition scale = tester.widget(find.byType(FadeTransition)); + return scale.opacity.value; +} + +class _StatefulTestWidget extends StatefulWidget { + const _StatefulTestWidget({Key key, this.name}) : super(key: key); + + final String name; + + @override + State<_StatefulTestWidget> createState() => _StatefulTestWidgetState(); +} + +class _StatefulTestWidgetState extends State<_StatefulTestWidget> { + @override + Widget build(BuildContext context) { + return Text(widget.name); + } +} From 05d5fcd92bb0692556268b36fe05e6e24f306943 Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Wed, 20 May 2020 13:27:13 -0700 Subject: [PATCH 11/12] self review --- .../lib/src/dual_transition_builder.dart | 28 ++++++------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/packages/animations/lib/src/dual_transition_builder.dart b/packages/animations/lib/src/dual_transition_builder.dart index fddbf2567283..b7fa932b5dd0 100644 --- a/packages/animations/lib/src/dual_transition_builder.dart +++ b/packages/animations/lib/src/dual_transition_builder.dart @@ -10,8 +10,6 @@ import 'package:flutter/widgets.dart'; /// `animation` and wrapping the provided `child`. /// /// The `animation` provided to the builder always runs forward from 0.0 to 1.0. -/// The builder will be called whenever the [Animation.value] of `animation` -/// has changed. typedef TransitionBuilder = Widget Function( BuildContext context, Animation animation, @@ -61,7 +59,7 @@ class DualTransitionBuilder extends StatefulWidget { /// /// The `animation` provided to this builder is running forward from 0.0 to /// 1.0 when [animation] runs _forward_. When [animation] runs in reverse, - /// the given animation is set to [kAlwaysCompleteAnimation]. + /// the given `animation` is set to [kAlwaysCompleteAnimation]. /// /// See also: /// @@ -76,7 +74,7 @@ class DualTransitionBuilder extends StatefulWidget { /// /// The `animation` provided to this builder is running forward from 0.0 to /// 1.0 when [animation] runs in _reverse_. When [animation] runs forward, - /// the given animation is set to [kAlwaysDismissedAnimation]. + /// the given `animation` is set to [kAlwaysDismissedAnimation]. /// /// See also: /// @@ -91,10 +89,10 @@ class DualTransitionBuilder extends StatefulWidget { final Widget child; @override - State createState() => _CompositeAnimationState(); + State createState() => _DualTransitionBuilderState(); } -class _CompositeAnimationState extends State { +class _DualTransitionBuilderState extends State { AnimationStatus _effectiveAnimationStatus; final ProxyAnimation _forwardAnimation = ProxyAnimation(); final ProxyAnimation _reverseAnimation = ProxyAnimation(); @@ -121,20 +119,10 @@ class _CompositeAnimationState extends State { @override void didUpdateWidget(DualTransitionBuilder oldWidget) { super.didUpdateWidget(oldWidget); - _updateAnimationListener( - oldWidget.animation, - widget.animation, - ); - } - - void _updateAnimationListener( - Animation oldAnimation, - Animation animation, - ) { - if (oldAnimation != animation) { - oldAnimation.removeStatusListener(_animationListener); - animation.addStatusListener(_animationListener); - _animationListener(animation.status); + if (oldWidget.animation != widget.animation) { + oldWidget.animation.removeStatusListener(_animationListener); + widget.animation.addStatusListener(_animationListener); + _animationListener(widget.animation.status); } } From c2f45132e93efc86f9a713f6dae1fff8f187e778 Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Tue, 26 May 2020 09:58:21 -0700 Subject: [PATCH 12/12] review --- packages/animations/lib/src/dual_transition_builder.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/animations/lib/src/dual_transition_builder.dart b/packages/animations/lib/src/dual_transition_builder.dart index b7fa932b5dd0..5534a6d4162b 100644 --- a/packages/animations/lib/src/dual_transition_builder.dart +++ b/packages/animations/lib/src/dual_transition_builder.dart @@ -67,7 +67,7 @@ class DualTransitionBuilder extends StatefulWidget { /// disappear from the screen. final TransitionBuilder forwardBuilder; - /// A builder for a transition that makes [child] disappear from the screen.. + /// A builder for a transition that makes [child] disappear from the screen. /// /// The [child] should be fully invisible when the provided `animation` /// reaches 1.0.