-
Notifications
You must be signed in to change notification settings - Fork 3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
PageTransitionSwitcher #50
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm holding off on the rest because I feel like moving further without being 100% sure of what "previous", "old" and "new" child may mean would make reviewing the rest of the code difficult.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic and tests look good! Most of my comments are suggestions to clarify the language of the code since it can be confusing to wrap your head around if you're unfamiliar with the primary/secondaryAnimation jargon and expectations.
@required this.secondaryController, | ||
@required this.transition, | ||
@required this.widgetChild, | ||
}) : assert(primaryController != null), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit:
}) : assert(primaryController != null), | |
}) : assert(primaryController != null), | |
assert(secondaryController != null), | |
assert(widgetChild != null), | |
assert(transition != null); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that would be better! However, unfortunately, dartfmt is enabled for this repository giving me no control over the formatting here :(
/// widget data that distinguishes this child from the others). | ||
/// | ||
/// The same key can be used for a new child as was used for an already-outgoing | ||
/// child; the two will not be considered related. (For example, if a progress |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// child; the two will not be considered related. (For example, if a progress | |
/// child; the two will not be considered related. For example, if a progress |
/// indicator with key A is first shown, then an image with key B, then another | ||
/// progress indicator with key A again, all in rapid succession, then the old | ||
/// progress indicator and the image will be fading out while a new progress | ||
/// indicator is fading in.) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// indicator is fading in.) | |
/// indicator is fading in. |
/// durations of transitions already in progress. | ||
final Duration duration; | ||
|
||
/// Indicates the direction of the animation when a new [child] is set. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe "Determines whether or not the new [child] will replace the previous [child] from the top or underneath the previous [child]", or something similar to describe the actual transition visually.
expect(_primaryAnimation[containerTwo], moreOrLessEquals(0.4)); | ||
expect(_secondaryAnimation[containerTwo], moreOrLessEquals(0.0)); | ||
|
||
// Container one is at the bottom. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Container one is at the bottom. | |
// Container one is underneath container two. |
_primaryAnimation = _getPrimaryAnimation( | ||
<Key>[containerOne, containerTwo, containerThree], tester); | ||
_secondaryAnimation = _getSecondaryAnimation( | ||
<Key>[containerOne, containerTwo, containerThree], tester); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe comment the expectation for this particular case since it's pretty unique. Like:
// Container one is expected to continue running its primary animation in reverse since it is exiting.
// Container two's secondary animation switches from running its secondary animation in reverse to
// running forwards since it should now be exiting underneath container three. Container three's primary
// animation should be running forwards since it is entering above container two.
ScaleTransition scale = tester.firstWidget(find.byType(ScaleTransition)); | ||
expect(fade.opacity.value, moreOrLessEquals(1.0)); | ||
expect(scale.scale.value, moreOrLessEquals(0.4)); | ||
await tester.pumpAndSettle(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe add a comment here saying that this pumpAndSettle
call is meant to let the entering widget's transition continue to completion
}); | ||
} | ||
|
||
class StatefulTest extends StatefulWidget { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps rename to make it clearer:
class StatefulTest extends StatefulWidget { | |
class StatefulTestWidget extends StatefulWidget { |
expect(find.text('1'), findsOneWidget); | ||
expect(find.text('2'), findsNothing); | ||
await pumpChild(const Text('2')); | ||
fade = tester.widget(find.byType(FadeTransition).first); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't there only be one widget, since it's not animating and simply updating the value?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, you're right. removed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mostly small stuff. Explaining how buildTransitions works is difficult!
@@ -51,8 +51,8 @@ typedef PageTransitionSwitcherTransitionBuilder = Widget Function( | |||
Animation<double> secondaryAnimation, | |||
); | |||
|
|||
/// A widget that transitions from a previously set child to a new child using | |||
/// an animation specified by [transitionBuilder]. | |||
/// A widget that transitions from a previously set child to a newly set child |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
from the current child to a new child?
/// If the "new" child is the same widget type and key as the "old" child, but | ||
/// with different parameters, then [PageTransitionSwitcher] will *not* do a | ||
/// If the *new* child is the same widget type and key as the *previous* child, | ||
/// but with different parameters, then [PageTransitionSwitcher] will *not* do a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this case by "but with different parameters" I assume that you mean that the new child was configured differently. Developers may find this a little confusing. You might provide an example. Like: changing the child from SizedBox(width: 10)
to SizedBox(width: 100)
would not trigger a transition but changing the child from SizedBox(width: 10)
to SizedBox(key: Key('foo'), width: 100)
would. Similarly, changing the child to Container(width: 10)
would trigger a transition.
The AnimatedSwitcher class API doc makes this same point. Might make sense to sync this paragraph with that one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added.
@@ -128,22 +128,23 @@ class PageTransitionSwitcher extends StatefulWidget { | |||
/// durations of transitions already in progress. | |||
final Duration duration; | |||
|
|||
/// Indicates the direction of the animation. | |||
/// Indicates the direction of the animation when a new [child] is set. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe: when the [child] is changed.
Widgets don't have properties that are set so much as constructor parameters whose values are provided.
/// previous child while its primary animation and the secondary animation | ||
/// of the previous child are running forward. This is similar to the | ||
/// transition associated with pushing a new [PageRoute] on top of another. | ||
/// previously set child while its primary animation and the secondary |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
previously set child => previous child
I'll stop complaining about "child setting" etc.
/// specified, similar to how the enter and exit transitions of a [PageRoute] | ||
/// are defined. | ||
/// | ||
/// The transitions returned by the [transitionBuilder] are driven by two |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the difficult part of explaining route transitions. I'll take a crack at the whole thing:
When a new [child] is specified, the [transitionBuilder] is effectively applied twice, once to the old child and once to the new one. The old child's secondaryAnimation
runs forward, and the value of its primaryAnimation
is fixed at 1.0. The new child's primaryAnimation
runs forward and the value of its secondary animation is fixed at 0.0. The widget returned by the [transitionBuilder] can incorporate both animations. It will use the primary animation to define how its child appears, and the secondary animation to define how its child disappears. This process is the same as the one used by [PageRoute.buildTransitions].
[And then include simple transitionBuilder example. There's a simple example here: https://api.flutter.dev/flutter/material/MaterialPageRoute/buildTransitions.html; prefer one that uses both animations]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Forgot to add the example, working on that now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
final _ChildEntry entry = _ChildEntry( | ||
widgetChild: child, | ||
transition: KeyedSubtree.wrap( | ||
builder(child, primaryController, secondaryController), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe assert that the builder's return value is non-null here and provide a nice error message
/// [PageRoute] below it. | ||
final bool reverse; | ||
|
||
/// A function that wraps a new [child] with a primary and secondary animation |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new PageTransition child could be null, by the transitionBuilder's child is guaranteed to be non-null, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. added documentation.
/// | ||
/// This duration is applied to the given [child] when that property is set to | ||
/// a new child. Changing [duration] will not affect the | ||
/// durations of transitions already in progress. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will a duration change affect in-progress transitions if transitionBuilder is also changed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No.
_secondaryAnimation = | ||
_getSecondaryAnimation(<Key>[containerOne, containerTwo], tester); | ||
// Secondary is running for outgoing widget. | ||
expect(_primaryAnimation[containerOne], moreOrLessEquals(1.0)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe moreOrLessEquals isn't needed for cases were we believe the animation's value will have reached 0 or 1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed where not needed.
import 'package:flutter_test/flutter_test.dart'; | ||
|
||
void main() { | ||
testWidgets('transitions in a new child.', (WidgetTester tester) async { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should verify that transitioning to/from a null child works too
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe there is a subsequent test for this called "handles null children"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, there is one later in this file :)
PTAL and let me know if the descriptions are clearer now. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, it's much clearer now :)
Co-Authored-By: Shi-Hao Hong <shihaohong@google.com>
Co-Authored-By: Shi-Hao Hong <shihaohong@google.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
/// Animation<double> primaryAnimation, | ||
/// Animation<double> secondaryAnimation, | ||
/// ) { | ||
/// return SlideTransition( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NICE
This is a variation of an AnimatedSwitcher [1], but it instead of using the same transition for enter and exit, two separate transitions can be specified, similar to how the enter and exit transitions of a PageRoute are defined.
The goal is that transitions written for transitioning PageRoutes in and out can be re-used to switch between two widgets. This can be useful in combination with a BottomNavigationBar to switch between widgets when another BottomNavigationBarItem is activated using a PageRoute-like transition.
[1] https://master-api.flutter.dev/flutter/widgets/AnimatedSwitcher-class.html