Commit 8219ab61 authored by Adam Barth's avatar Adam Barth Committed by GitHub

Don't rebuild routes on the second animation frame (#5097)

Previously we would rebuild every route on the second animation frame to
rejigger the offstage bit and the animations. Now we build the page once and
update the offstage bit in place and update the animations using
ProxyAnimations.
parent ac14648b
...@@ -186,7 +186,7 @@ class _ModalBottomSheetState<T> extends State<_ModalBottomSheet<T>> { ...@@ -186,7 +186,7 @@ class _ModalBottomSheetState<T> extends State<_ModalBottomSheet<T>> {
child: new CustomSingleChildLayout( child: new CustomSingleChildLayout(
delegate: new _ModalBottomSheetLayout(config.route.animation.value), delegate: new _ModalBottomSheetLayout(config.route.animation.value),
child: new BottomSheet( child: new BottomSheet(
animationController: config.route.animation, animationController: config.route._animationController,
onClosing: () => Navigator.pop(context), onClosing: () => Navigator.pop(context),
builder: config.route.builder builder: config.route.builder
) )
...@@ -215,9 +215,13 @@ class _ModalBottomSheetRoute<T> extends PopupRoute<T> { ...@@ -215,9 +215,13 @@ class _ModalBottomSheetRoute<T> extends PopupRoute<T> {
@override @override
Color get barrierColor => Colors.black54; Color get barrierColor => Colors.black54;
AnimationController _animationController;
@override @override
AnimationController createAnimationController() { AnimationController createAnimationController() {
return BottomSheet.createAnimationController(); assert(_animationController == null);
_animationController = BottomSheet.createAnimationController();
return _animationController;
} }
@override @override
......
...@@ -207,10 +207,10 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> { ...@@ -207,10 +207,10 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
TrainHoppingAnimation newAnimation; TrainHoppingAnimation newAnimation;
newAnimation = new TrainHoppingAnimation( newAnimation = new TrainHoppingAnimation(
current.currentTrain, current.currentTrain,
nextRoute.animation, nextRoute._animation,
onSwitchedTrain: () { onSwitchedTrain: () {
assert(_forwardAnimation.parent == newAnimation); assert(_forwardAnimation.parent == newAnimation);
assert(newAnimation.currentTrain == nextRoute.animation); assert(newAnimation.currentTrain == nextRoute._animation);
_forwardAnimation.parent = newAnimation.currentTrain; _forwardAnimation.parent = newAnimation.currentTrain;
newAnimation.dispose(); newAnimation.dispose();
} }
...@@ -218,10 +218,10 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> { ...@@ -218,10 +218,10 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
_forwardAnimation.parent = newAnimation; _forwardAnimation.parent = newAnimation;
current.dispose(); current.dispose();
} else { } else {
_forwardAnimation.parent = new TrainHoppingAnimation(current, nextRoute.animation); _forwardAnimation.parent = new TrainHoppingAnimation(current, nextRoute._animation);
} }
} else { } else {
_forwardAnimation.parent = nextRoute.animation; _forwardAnimation.parent = nextRoute._animation;
} }
} else { } else {
_forwardAnimation.parent = kAlwaysDismissedAnimation; _forwardAnimation.parent = kAlwaysDismissedAnimation;
...@@ -367,10 +367,12 @@ class _ModalScopeStatus extends InheritedWidget { ...@@ -367,10 +367,12 @@ class _ModalScopeStatus extends InheritedWidget {
class _ModalScope extends StatefulWidget { class _ModalScope extends StatefulWidget {
_ModalScope({ _ModalScope({
Key key, Key key,
this.route this.route,
this.page
}) : super(key: key); }) : super(key: key);
final ModalRoute<dynamic> route; final ModalRoute<dynamic> route;
final Widget page;
@override @override
_ModalScopeState createState() => new _ModalScopeState(); _ModalScopeState createState() => new _ModalScopeState();
...@@ -408,31 +410,30 @@ class _ModalScopeState extends State<_ModalScope> { ...@@ -408,31 +410,30 @@ class _ModalScopeState extends State<_ModalScope> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Widget contents = new PageStorage(
key: config.route._subtreeKey,
bucket: config.route._storageBucket,
child: new _ModalScopeStatus(
route: config.route,
isCurrent: config.route.isCurrent,
child: config.route.buildPage(context, config.route.animation, config.route.forwardAnimation)
)
);
if (config.route.offstage) {
contents = new OffStage(child: contents);
} else {
contents = new IgnorePointer(
ignoring: config.route.animation?.status == AnimationStatus.reverse,
child: config.route.buildTransitions(
context,
config.route.animation,
config.route.forwardAnimation,
new RepaintBoundary(child: contents)
)
);
}
return new Focus( return new Focus(
key: config.route.focusKey, key: config.route.focusKey,
child: contents child: new OffStage(
offstage: config.route.offstage,
child: new IgnorePointer(
ignoring: config.route.animation?.status == AnimationStatus.reverse,
child: config.route.buildTransitions(
context,
config.route.animation,
config.route.forwardAnimation,
new RepaintBoundary(
child: new PageStorage(
key: config.route._subtreeKey,
bucket: config.route._storageBucket,
child: new _ModalScopeStatus(
route: config.route,
isCurrent: config.route.isCurrent,
child: config.page
)
)
)
)
)
)
); );
} }
} }
...@@ -521,6 +522,13 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T ...@@ -521,6 +522,13 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
@override @override
GlobalKey get focusKey => new GlobalObjectKey(this); GlobalKey get focusKey => new GlobalObjectKey(this);
@override
void install(OverlayEntry insertionPoint) {
super.install(insertionPoint);
_animationProxy = new ProxyAnimation(super.animation);
_forwardAnimationProxy = new ProxyAnimation(super.forwardAnimation);
}
@override @override
void didPush() { void didPush() {
if (!settings.isInitialRoute) { if (!settings.isInitialRoute) {
...@@ -573,11 +581,21 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T ...@@ -573,11 +581,21 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
setState(() { setState(() {
_offstage = value; _offstage = value;
}); });
_animationProxy.parent = _offstage ? kAlwaysCompleteAnimation : super.animation;
_forwardAnimationProxy.parent = _offstage ? kAlwaysDismissedAnimation : super.forwardAnimation;
} }
/// The build context for the subtree containing the primary content of this route. /// The build context for the subtree containing the primary content of this route.
BuildContext get subtreeContext => _subtreeKey.currentContext; BuildContext get subtreeContext => _subtreeKey.currentContext;
@override
Animation<double> get animation => _animationProxy;
ProxyAnimation _animationProxy;
@override
Animation<double> get forwardAnimation => _forwardAnimationProxy;
ProxyAnimation _forwardAnimationProxy;
// Internals // Internals
...@@ -615,8 +633,9 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T ...@@ -615,8 +633,9 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
Widget _buildModalScope(BuildContext context) { Widget _buildModalScope(BuildContext context) {
return new _ModalScope( return new _ModalScope(
key: _scopeKey, key: _scopeKey,
route: this route: this,
// calls buildTransitions() and buildPage(), defined above page: buildPage(context, animation, forwardAnimation)
// _ModalScope calls buildTransitions(), defined above
); );
} }
......
...@@ -72,4 +72,49 @@ void main() { ...@@ -72,4 +72,49 @@ void main() {
expect(state1, equals(state2)); expect(state1, equals(state2));
expect(state2.marker, equals('original')); expect(state2.marker, equals('original'));
}); });
testWidgets('Do not rebuild page on the second frame of the route transition', (WidgetTester tester) async {
int buildCounter = 0;
await tester.pumpWidget(
new MaterialApp(
home: new Builder(
builder: (BuildContext context) {
return new Material(
child: new RaisedButton(
child: new Text('X'),
onPressed: () { Navigator.of(context).pushNamed('/next'); }
)
);
}
),
routes: <String, WidgetBuilder>{
'/next': (BuildContext context) {
return new Builder(
builder: (BuildContext context) {
++buildCounter;
return new Container();
}
);
}
}
)
);
expect(buildCounter, 0);
await tester.tap(find.text('X'));
expect(buildCounter, 0);
await tester.pump();
expect(buildCounter, 1);
await tester.pump(const Duration(milliseconds: 10));
expect(buildCounter, 1);
await tester.pump(const Duration(milliseconds: 10));
expect(buildCounter, 1);
await tester.pump(const Duration(milliseconds: 10));
expect(buildCounter, 1);
await tester.pump(const Duration(milliseconds: 10));
expect(buildCounter, 1);
await tester.pump(const Duration(seconds: 1));
expect(buildCounter, 2);
});
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment