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>> {
child: new CustomSingleChildLayout(
delegate: new _ModalBottomSheetLayout(config.route.animation.value),
child: new BottomSheet(
animationController: config.route.animation,
animationController: config.route._animationController,
onClosing: () => Navigator.pop(context),
builder: config.route.builder
)
......@@ -215,9 +215,13 @@ class _ModalBottomSheetRoute<T> extends PopupRoute<T> {
@override
Color get barrierColor => Colors.black54;
AnimationController _animationController;
@override
AnimationController createAnimationController() {
return BottomSheet.createAnimationController();
assert(_animationController == null);
_animationController = BottomSheet.createAnimationController();
return _animationController;
}
@override
......
......@@ -207,10 +207,10 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
TrainHoppingAnimation newAnimation;
newAnimation = new TrainHoppingAnimation(
current.currentTrain,
nextRoute.animation,
nextRoute._animation,
onSwitchedTrain: () {
assert(_forwardAnimation.parent == newAnimation);
assert(newAnimation.currentTrain == nextRoute.animation);
assert(newAnimation.currentTrain == nextRoute._animation);
_forwardAnimation.parent = newAnimation.currentTrain;
newAnimation.dispose();
}
......@@ -218,10 +218,10 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
_forwardAnimation.parent = newAnimation;
current.dispose();
} else {
_forwardAnimation.parent = new TrainHoppingAnimation(current, nextRoute.animation);
_forwardAnimation.parent = new TrainHoppingAnimation(current, nextRoute._animation);
}
} else {
_forwardAnimation.parent = nextRoute.animation;
_forwardAnimation.parent = nextRoute._animation;
}
} else {
_forwardAnimation.parent = kAlwaysDismissedAnimation;
......@@ -367,10 +367,12 @@ class _ModalScopeStatus extends InheritedWidget {
class _ModalScope extends StatefulWidget {
_ModalScope({
Key key,
this.route
this.route,
this.page
}) : super(key: key);
final ModalRoute<dynamic> route;
final Widget page;
@override
_ModalScopeState createState() => new _ModalScopeState();
......@@ -408,31 +410,30 @@ class _ModalScopeState extends State<_ModalScope> {
@override
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(
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
@override
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
void didPush() {
if (!settings.isInitialRoute) {
......@@ -573,11 +581,21 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
setState(() {
_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.
BuildContext get subtreeContext => _subtreeKey.currentContext;
@override
Animation<double> get animation => _animationProxy;
ProxyAnimation _animationProxy;
@override
Animation<double> get forwardAnimation => _forwardAnimationProxy;
ProxyAnimation _forwardAnimationProxy;
// Internals
......@@ -615,8 +633,9 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
Widget _buildModalScope(BuildContext context) {
return new _ModalScope(
key: _scopeKey,
route: this
// calls buildTransitions() and buildPage(), defined above
route: this,
page: buildPage(context, animation, forwardAnimation)
// _ModalScope calls buildTransitions(), defined above
);
}
......
......@@ -72,4 +72,49 @@ void main() {
expect(state1, equals(state2));
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