Skip to content
25 changes: 21 additions & 4 deletions packages/animations/lib/src/shared_axis_transition.dart
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,17 @@ class SharedAxisPageTransitionsBuilder extends PageTransitionsBuilder {
/// Construct a [SharedAxisPageTransitionsBuilder].
const SharedAxisPageTransitionsBuilder({
this.transitionType,
this.fillColor,
});

/// Determines which [SharedAxisTransitionType] to build.
final SharedAxisTransitionType transitionType;

/// 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<T>(
PageRoute<T> route,
Expand All @@ -97,6 +103,7 @@ class SharedAxisPageTransitionsBuilder extends PageTransitionsBuilder {
animation: animation,
secondaryAnimation: secondaryAnimation,
transitionType: transitionType,
fillColor: fillColor,
child: child,
);
}
Expand Down Expand Up @@ -186,6 +193,7 @@ class SharedAxisTransition extends StatefulWidget {
@required this.animation,
@required this.secondaryAnimation,
@required this.transitionType,
this.fillColor,
this.child,
}) : assert(transitionType != null),
super(key: key);
Expand Down Expand Up @@ -215,6 +223,11 @@ class SharedAxisTransition extends StatefulWidget {
/// axis transition types.
final SharedAxisTransitionType transitionType;

/// 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
Expand Down Expand Up @@ -341,6 +354,7 @@ class _SharedAxisTransitionState extends State<SharedAxisTransition> {
animation: _flip(widget.animation),
transitionType: widget.transitionType,
reverse: true,
fillColor: widget.fillColor,
child: child,
);
}
Expand All @@ -355,6 +369,7 @@ class _SharedAxisTransitionState extends State<SharedAxisTransition> {
return _ExitTransition(
animation: widget.secondaryAnimation,
transitionType: widget.transitionType,
fillColor: widget.fillColor,
child: child,
);
case AnimationStatus.dismissed:
Expand Down Expand Up @@ -453,13 +468,15 @@ class _ExitTransition extends StatelessWidget {
this.animation,
this.transitionType,
this.reverse = false,
this.fillColor,
this.child,
});

final Animation<double> animation;
final SharedAxisTransitionType transitionType;
final Widget child;
final bool reverse;
final Color fillColor;
final Widget child;

static final Animatable<double> _fadeOutTransition = FlippedCurveTween(
curve: accelerateEasing,
Expand Down Expand Up @@ -487,7 +504,7 @@ class _ExitTransition extends StatelessWidget {
return FadeTransition(
opacity: _fadeOutTransition.animate(animation),
child: Container(
color: Theme.of(context).canvasColor,
color: fillColor ?? Theme.of(context).canvasColor,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did we have to give this a background color at all? Couldn't it just be unopinionated and let whatever color is behind it shine through?

@shihaohong shihaohong Apr 9, 2020

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this was an oversight. Would it be better to just remove the color parameter from the Container altogether?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think so. It seems the transition shouldn't really have an opinion about the background color.

@shihaohong shihaohong Apr 10, 2020

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually remember why now. This becomes a problem if you use SharedAxisPageTransitionsBuilder without a Container with some sort of fill color. What ends up happening is while the transition is occurring, you'll get a black background if there's nothing behind those routes already, since both become transparent. This isn't a problem when using the PageTransitionSwitcher because it does have a background color behind the coming and going widgets.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, that makes sense.

child: Transform.translate(
offset: slideOutTransition.evaluate(animation),
child: child,
Expand All @@ -504,7 +521,7 @@ class _ExitTransition extends StatelessWidget {
return FadeTransition(
opacity: _fadeOutTransition.animate(animation),
child: Container(
color: Theme.of(context).canvasColor,
color: fillColor ?? Theme.of(context).canvasColor,
child: Transform.translate(
offset: slideOutTransition.evaluate(animation),
child: child,
Expand All @@ -516,7 +533,7 @@ class _ExitTransition extends StatelessWidget {
return FadeTransition(
opacity: _fadeOutTransition.animate(animation),
child: Container(
color: Theme.of(context).canvasColor,
color: fillColor ?? Theme.of(context).canvasColor,
child: ScaleTransition(
scale: (!reverse ? _scaleUpTransition : _scaleDownTransition)
.animate(animation),
Expand Down
227 changes: 226 additions & 1 deletion packages/animations/test/shared_axis_transition_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import 'package:flutter/animation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/widgets.dart';
import 'package:vector_math/vector_math_64.dart';
import 'package:vector_math/vector_math_64.dart' hide Colors;

void main() {
group('SharedAxisTransitionType.horizontal', () {
Expand Down Expand Up @@ -505,6 +505,80 @@ void main() {
expect(find.byKey(const ValueKey<String>(topRoute)), findsNothing);
},
);

testWidgets(
'default fill color',
(WidgetTester tester) async {
final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>();
const String bottomRoute = '/';
const String topRoute = '/a';

// The default fill color should be derived from ThemeData.canvasColor.
final Color defaultFillColor = ThemeData().canvasColor;

await tester.pumpWidget(
_TestWidget(
navigatorKey: navigator,
transitionType: SharedAxisTransitionType.horizontal,
),
);

expect(find.text(bottomRoute), findsOneWidget);
Finder fillContainerFinder = find.ancestor(
matching: find.byType(Container),
of: find.byKey(const ValueKey<String>('/')),
).last;
expect(fillContainerFinder, findsOneWidget);
expect(tester.widget<Container>(fillContainerFinder).color, defaultFillColor);

navigator.currentState.pushNamed(topRoute);
await tester.pump();
await tester.pumpAndSettle();

fillContainerFinder = find.ancestor(
matching: find.byType(Container),
of: find.byKey(const ValueKey<String>('/a')),
).last;
expect(fillContainerFinder, findsOneWidget);
expect(tester.widget<Container>(fillContainerFinder).color, defaultFillColor);
}
);

testWidgets(
'custom fill color',
(WidgetTester tester) async {
final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>();
const String bottomRoute = '/';
const String topRoute = '/a';

await tester.pumpWidget(
_TestWidget(
navigatorKey: navigator,
fillColor: Colors.green,
transitionType: SharedAxisTransitionType.horizontal,
),
);

expect(find.text(bottomRoute), findsOneWidget);
Finder fillContainerFinder = find.ancestor(
matching: find.byType(Container),
of: find.byKey(const ValueKey<String>('/')),
).last;
expect(fillContainerFinder, findsOneWidget);
expect(tester.widget<Container>(fillContainerFinder).color, Colors.green);

navigator.currentState.pushNamed(topRoute);
await tester.pump();
await tester.pumpAndSettle();

fillContainerFinder = find.ancestor(
matching: find.byType(Container),
of: find.byKey(const ValueKey<String>('/a')),
).last;
expect(fillContainerFinder, findsOneWidget);
expect(tester.widget<Container>(fillContainerFinder).color, Colors.green);
}
);
});

group('SharedAxisTransitionType.vertical', () {
Expand Down Expand Up @@ -1004,6 +1078,80 @@ void main() {
expect(find.byKey(const ValueKey<String>(topRoute)), findsNothing);
},
);

testWidgets(
'default fill color',
(WidgetTester tester) async {
final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>();
const String bottomRoute = '/';
const String topRoute = '/a';

// The default fill color should be derived from ThemeData.canvasColor.
final Color defaultFillColor = ThemeData().canvasColor;

await tester.pumpWidget(
_TestWidget(
navigatorKey: navigator,
transitionType: SharedAxisTransitionType.vertical,
),
);

expect(find.text(bottomRoute), findsOneWidget);
Finder fillContainerFinder = find.ancestor(
matching: find.byType(Container),
of: find.byKey(const ValueKey<String>('/')),
).last;
expect(fillContainerFinder, findsOneWidget);
expect(tester.widget<Container>(fillContainerFinder).color, defaultFillColor);

navigator.currentState.pushNamed(topRoute);
await tester.pump();
await tester.pumpAndSettle();

fillContainerFinder = find.ancestor(
matching: find.byType(Container),
of: find.byKey(const ValueKey<String>('/a')),
).last;
expect(fillContainerFinder, findsOneWidget);
expect(tester.widget<Container>(fillContainerFinder).color, defaultFillColor);
}
);

testWidgets(
'custom fill color',
(WidgetTester tester) async {
final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>();
const String bottomRoute = '/';
const String topRoute = '/a';

await tester.pumpWidget(
_TestWidget(
navigatorKey: navigator,
fillColor: Colors.green,
transitionType: SharedAxisTransitionType.vertical,
),
);

expect(find.text(bottomRoute), findsOneWidget);
Finder fillContainerFinder = find.ancestor(
matching: find.byType(Container),
of: find.byKey(const ValueKey<String>('/')),
).last;
expect(fillContainerFinder, findsOneWidget);
expect(tester.widget<Container>(fillContainerFinder).color, Colors.green);

navigator.currentState.pushNamed(topRoute);
await tester.pump();
await tester.pumpAndSettle();

fillContainerFinder = find.ancestor(
matching: find.byType(Container),
of: find.byKey(const ValueKey<String>('/a')),
).last;
expect(fillContainerFinder, findsOneWidget);
expect(tester.widget<Container>(fillContainerFinder).color, Colors.green);
}
);
});

group('SharedAxisTransitionType.scaled', () {
Expand Down Expand Up @@ -1396,6 +1544,80 @@ void main() {
expect(find.byKey(const ValueKey<String>(topRoute)), findsNothing);
},
);

testWidgets(
'default fill color',
(WidgetTester tester) async {
final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>();
const String bottomRoute = '/';
const String topRoute = '/a';

// The default fill color should be derived from ThemeData.canvasColor.
final Color defaultFillColor = ThemeData().canvasColor;

await tester.pumpWidget(
_TestWidget(
navigatorKey: navigator,
transitionType: SharedAxisTransitionType.scaled,
),
);

expect(find.text(bottomRoute), findsOneWidget);
Finder fillContainerFinder = find.ancestor(
matching: find.byType(Container),
of: find.byKey(const ValueKey<String>('/')),
).last;
expect(fillContainerFinder, findsOneWidget);
expect(tester.widget<Container>(fillContainerFinder).color, defaultFillColor);

navigator.currentState.pushNamed(topRoute);
await tester.pump();
await tester.pumpAndSettle();

fillContainerFinder = find.ancestor(
matching: find.byType(Container),
of: find.byKey(const ValueKey<String>('/a')),
).last;
expect(fillContainerFinder, findsOneWidget);
expect(tester.widget<Container>(fillContainerFinder).color, defaultFillColor);
}
);

testWidgets(
'custom fill color',
(WidgetTester tester) async {
final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>();
const String bottomRoute = '/';
const String topRoute = '/a';

await tester.pumpWidget(
_TestWidget(
navigatorKey: navigator,
fillColor: Colors.green,
transitionType: SharedAxisTransitionType.scaled,
),
);

expect(find.text(bottomRoute), findsOneWidget);
Finder fillContainerFinder = find.ancestor(
matching: find.byType(Container),
of: find.byKey(const ValueKey<String>('/')),
).last;
expect(fillContainerFinder, findsOneWidget);
expect(tester.widget<Container>(fillContainerFinder).color, Colors.green);

navigator.currentState.pushNamed(topRoute);
await tester.pump();
await tester.pumpAndSettle();

fillContainerFinder = find.ancestor(
matching: find.byType(Container),
of: find.byKey(const ValueKey<String>('/a')),
).last;
expect(fillContainerFinder, findsOneWidget);
expect(tester.widget<Container>(fillContainerFinder).color, Colors.green);
}
);
});
}

Expand Down Expand Up @@ -1462,11 +1684,13 @@ class _TestWidget extends StatelessWidget {
this.navigatorKey,
this.contentBuilder,
this.transitionType,
this.fillColor,
});

final Key navigatorKey;
final _ContentBuilder contentBuilder;
final SharedAxisTransitionType transitionType;
final Color fillColor;

@override
Widget build(BuildContext context) {
Expand All @@ -1477,6 +1701,7 @@ class _TestWidget extends StatelessWidget {
pageTransitionsTheme: PageTransitionsTheme(
builders: <TargetPlatform, PageTransitionsBuilder>{
TargetPlatform.android: SharedAxisPageTransitionsBuilder(
fillColor: fillColor,
transitionType: transitionType,
),
},
Expand Down