From 6ee6ae03c94e88bec7a57bd25424bdacb4b306e1 Mon Sep 17 00:00:00 2001 From: Adam Barth Date: Sat, 31 Oct 2015 15:36:32 -0700 Subject: [PATCH] Black flash when returning to a PageRoute We were trying to do a hero animation from a page to itself, which doesn't make any sense. Now we only render the "to" page offstage if it is different from the "from" page and if its performance isn't already complete. --- .../lib/src/widgets/hero_controller.dart | 6 +- packages/unit/test/widget/heroes_test.dart | 117 ++++++++++++++++++ 2 files changed, 121 insertions(+), 2 deletions(-) create mode 100644 packages/unit/test/widget/heroes_test.dart diff --git a/packages/flutter/lib/src/widgets/hero_controller.dart b/packages/flutter/lib/src/widgets/hero_controller.dart index 3995caa36f..cf51afabfc 100644 --- a/packages/flutter/lib/src/widgets/hero_controller.dart +++ b/packages/flutter/lib/src/widgets/hero_controller.dart @@ -45,8 +45,10 @@ class HeroController { return; } _to = current; - current.offstage = true; - scheduler.requestPostFrameCallback(_updateQuest); + if (_from != _to) { + current.offstage = current.performance.status != PerformanceStatus.completed; + scheduler.requestPostFrameCallback(_updateQuest); + } } void _handleQuestFinished() { diff --git a/packages/unit/test/widget/heroes_test.dart b/packages/unit/test/widget/heroes_test.dart new file mode 100644 index 0000000000..a8dec7a8ee --- /dev/null +++ b/packages/unit/test/widget/heroes_test.dart @@ -0,0 +1,117 @@ +import 'package:flutter/material.dart'; +import 'package:test/test.dart'; + +import 'widget_tester.dart'; + +class TestOverlayRoute extends OverlayRoute { + List createWidgets() => [ new Text('Overlay') ]; +} + +bool _isOnStage(Element element) { + expect(element, isNotNull); + bool result = true; + element.visitAncestorElements((Element ancestor) { + if (ancestor.widget is OffStage) { + result = false; + return false; + } + return true; + }); + return result; +} + +class _IsOnStage extends Matcher { + const _IsOnStage(); + bool matches(item, Map matchState) => _isOnStage(item); + Description describe(Description description) => description.add('onstage'); +} + +class _IsOffStage extends Matcher { + const _IsOffStage(); + bool matches(item, Map matchState) => !_isOnStage(item); + Description describe(Description description) => description.add('offstage'); +} + +const Matcher isOnStage = const _IsOnStage(); +const Matcher isOffStage = const _IsOffStage(); + +void main() { + test('Can pop ephemeral route without black flash', () { + testWidgets((WidgetTester tester) { + GlobalKey containerKey = new GlobalKey(); + final Map routes = { + '/': (_) => new Container(key: containerKey, child: new Text('Home')), + '/settings': (_) => new Container(child: new Text('Settings')), + }; + + tester.pumpWidget(new MaterialApp(routes: routes)); + + expect(tester.findText('Home'), isOnStage); + expect(tester.findText('Settings'), isNull); + expect(tester.findText('Overlay'), isNull); + + NavigatorState navigator = Navigator.of(containerKey.currentContext); + + navigator.pushNamed('/settings'); + + tester.pump(); + + expect(tester.findText('Home'), isOnStage); + expect(tester.findText('Settings'), isOffStage); + expect(tester.findText('Overlay'), isNull); + + tester.pump(const Duration(milliseconds: 16)); + + expect(tester.findText('Home'), isOnStage); + expect(tester.findText('Settings'), isOnStage); + expect(tester.findText('Overlay'), isNull); + + tester.pump(const Duration(seconds: 1)); + + expect(tester.findText('Home'), isNull); + expect(tester.findText('Settings'), isOnStage); + expect(tester.findText('Overlay'), isNull); + + navigator.push(new TestOverlayRoute()); + + tester.pump(); + + expect(tester.findText('Home'), isNull); + expect(tester.findText('Settings'), isOnStage); + expect(tester.findText('Overlay'), isOnStage); + + tester.pump(const Duration(seconds: 1)); + + expect(tester.findText('Home'), isNull); + expect(tester.findText('Settings'), isOnStage); + expect(tester.findText('Overlay'), isOnStage); + + navigator.pop(); + tester.pump(); + + expect(tester.findText('Home'), isNull); + expect(tester.findText('Settings'), isOnStage); + expect(tester.findText('Overlay'), isNull); + + tester.pump(const Duration(seconds: 1)); + + expect(tester.findText('Home'), isNull); + expect(tester.findText('Settings'), isOnStage); + expect(tester.findText('Overlay'), isNull); + + navigator.pop(); + tester.pump(); + + expect(tester.findText('Home'), isOnStage); + expect(tester.findText('Settings'), isOnStage); + expect(tester.findText('Overlay'), isNull); + + tester.pump(const Duration(seconds: 1)); + + expect(tester.findText('Home'), isOnStage); + expect(tester.findText('Settings'), isNull); + expect(tester.findText('Overlay'), isNull); + + }); + }); +}