From 2e301d4d70af8672d7d95e1f22ad17b54d22e41c Mon Sep 17 00:00:00 2001 From: Adam Barth Date: Wed, 28 Oct 2015 23:18:14 -0700 Subject: [PATCH 1/2] Add support for modal, ephemeral, and contentless routes to Navigator2 Routes can now create a list of widgets, which can be empty (in the case of contentless routes) or have multiple entries (in the case of modal routes). Ephemeral routes are handled by keeping a separate list of modal and ephemeral routes. --- .../lib/src/material/material_app.dart | 13 +- .../flutter/lib/src/widgets/navigator2.dart | 211 ++++++------------ packages/flutter/lib/src/widgets/page.dart | 112 ++++++++++ 3 files changed, 192 insertions(+), 144 deletions(-) create mode 100644 packages/flutter/lib/src/widgets/page.dart diff --git a/packages/flutter/lib/src/material/material_app.dart b/packages/flutter/lib/src/material/material_app.dart index dc7bfb3f3a6..81df08aa24a 100644 --- a/packages/flutter/lib/src/material/material_app.dart +++ b/packages/flutter/lib/src/material/material_app.dart @@ -9,6 +9,7 @@ import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter/src/widgets/navigator2.dart' as n2; +import 'package:flutter/src/widgets/page.dart' as n2; import 'theme.dart'; import 'title.dart'; @@ -86,12 +87,22 @@ class _MaterialAppState extends State { void _metricHandler(Size size) => setState(() { _size = size; }); + n2.Route _generateRoute(n2.RouteArguments args) { + return new n2.PageRoute( + builder: (BuildContext context) { + RouteArguments routeArgs = new RouteArguments(context: context); + return config.routes[args.name](routeArgs); + }, + args: args + ); + } + Widget build(BuildContext context) { Widget navigator; if (_kUseNavigator2) { navigator = new n2.Navigator( key: _navigator, - routes: config.routes + onGenerateRoute: _generateRoute ); } else { navigator = new Navigator( diff --git a/packages/flutter/lib/src/widgets/navigator2.dart b/packages/flutter/lib/src/widgets/navigator2.dart index 6370862d496..debd883bf79 100644 --- a/packages/flutter/lib/src/widgets/navigator2.dart +++ b/packages/flutter/lib/src/widgets/navigator2.dart @@ -2,55 +2,54 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:async'; - -import 'package:flutter/animation.dart'; - -import 'basic.dart'; import 'framework.dart'; import 'overlay.dart'; -import 'transitions.dart'; abstract class Route { - /// Override this function to return the widget that this route should display. - Widget createWidget(); + List createWidgets() => const []; - Widget _child; - OverlayEntry _entry; + OverlayEntry get topEntry => _entries.isNotEmpty ? _entries.last : null; + OverlayEntry get bottomEntry => _entries.isNotEmpty ? _entries.first : null; - void willPush() { - _child = createWidget(); + final List _entries = new List(); + + void add(OverlayState overlay, OverlayEntry insertionPoint) { + List widgets = createWidgets(); + for (Widget widget in widgets) { + _entries.add(new OverlayEntry(child: widget)); + overlay?.insert(_entries.last, above: insertionPoint); + insertionPoint = _entries.last; + } } - void didPop(dynamic result) { - _entry.remove(); + void remove(dynamic result) { + for (OverlayEntry entry in _entries) + entry.remove(); } } -typedef Widget RouteBuilder(args); -typedef RouteBuilder RouteGenerator(String name); +class RouteArguments { + const RouteArguments({ this.name: '', this.mostValuableKeys }); -const String _kDefaultPageName = '/'; + final String name; + final Set mostValuableKeys; +} + +typedef Route RouteFactory(RouteArguments args); class Navigator extends StatefulComponent { Navigator({ Key key, - this.routes, - this.onGeneratePage, - this.onUnknownPage + this.onGenerateRoute, + this.onUnknownRoute }) : super(key: key) { - // To use a navigator, you must at a minimum define the route with the name '/'. - assert(routes != null); - assert(routes.containsKey(_kDefaultPageName)); + assert(onGenerateRoute != null); } - final Map routes; + final RouteFactory onGenerateRoute; + final RouteFactory onUnknownRoute; - /// you need to implement this if you pushNamed() to names that might not be in routes. - final RouteGenerator onGeneratePage; - - /// 404 generator. You only need to implement this if you have a way to navigate to arbitrary names. - final RouteBuilder onUnknownPage; + static const String defaultRouteName = '/'; static NavigatorState of(BuildContext context) { NavigatorState result; @@ -68,139 +67,65 @@ class Navigator extends StatefulComponent { } class NavigatorState extends State { - GlobalKey _overlay = new GlobalKey(); - List _history = new List(); + final GlobalKey _overlay = new GlobalKey(); + final List _ephemeral = new List(); + final List _modal = new List(); void initState() { super.initState(); - _addRouteToHistory(new PageRoute( - builder: config.routes[_kDefaultPageName], - name: _kDefaultPageName - )); + push(config.onGenerateRoute(new RouteArguments(name: Navigator.defaultRouteName))); } - RouteBuilder _generatePage(String name) { - assert(config.onGeneratePage != null); - return config.onGeneratePage(name); - } + bool get hasPreviousRoute => _modal.length > 1; - bool get hasPreviousRoute => _history.length > 1; + OverlayEntry get _topRouteOverlay { + for (Route route in _ephemeral.reversed) { + if (route.topEntry != null) + return route.topEntry; + } + for (Route route in _modal.reversed) { + if (route.topEntry != null) + return route.topEntry; + } + return null; + } void pushNamed(String name, { Set mostValuableKeys }) { - final RouteBuilder builder = config.routes[name] ?? _generatePage(name) ?? config.onUnknownPage; - assert(builder != null); // 404 getting your 404! - push(new PageRoute( - builder: builder, - name: name, - mostValuableKeys: mostValuableKeys - )); - } - - void _addRouteToHistory(Route route) { - route.willPush(); - route._entry = new OverlayEntry(child: route._child); - _history.add(route); + RouteArguments args = new RouteArguments(name: name, mostValuableKeys: mostValuableKeys); + push(config.onGenerateRoute(args) ?? config.onUnknownRoute(args)); } void push(Route route) { - OverlayEntry reference = _history.last._entry; - _addRouteToHistory(route); - _overlay.currentState.insert(route._entry, above: reference); + _popAllEphemeralRoutes(); + route.add(_overlay.currentState, _topRouteOverlay); + _modal.add(route); + } + + void pushEphemeral(Route route) { + route.add(_overlay.currentState, _topRouteOverlay); + _ephemeral.add(route); + } + + void _popAllEphemeralRoutes() { + List localEphemeral = new List.from(_ephemeral); + _ephemeral.clear(); + for (Route route in localEphemeral) + route.remove(null); + assert(_ephemeral.isEmpty); } void pop([dynamic result]) { - _history.removeLast().didPop(result); + if (_ephemeral.isNotEmpty) { + _ephemeral.removeLast().remove(result); + return; + } + _modal.removeLast().remove(result); } Widget build(BuildContext context) { return new Overlay( key: _overlay, - initialEntries: [ _history.first._entry ] + initialEntries: _modal.first._entries ); } } - -abstract class TransitionRoute extends Route { - PerformanceView get performance => _performance?.view; - Performance _performance; - - Duration get transitionDuration; - - Performance createPerformance() { - Duration duration = transitionDuration; - assert(duration != null && duration >= Duration.ZERO); - return new Performance(duration: duration, debugLabel: debugLabel); - } - - void willPush() { - _performance = createPerformance(); - _performance.forward(); - super.willPush(); - } - - Future didPop(dynamic result) async { - await _performance.reverse(); - super.didPop(result); - } - - String get debugLabel => '$runtimeType'; - String toString() => '$runtimeType(performance: $_performance)'; -} - -class _Page extends StatefulComponent { - _Page({ Key key, this.route }) : super(key: key); - - final PageRoute route; - - _PageState createState() => new _PageState(); -} - -class _PageState extends State<_Page> { - final AnimatedValue _position = - new AnimatedValue(const Point(0.0, 75.0), end: Point.origin, curve: Curves.easeOut); - - final AnimatedValue _opacity = - new AnimatedValue(0.0, end: 1.0, curve: Curves.easeOut); - - Widget build(BuildContext context) { - return new SlideTransition( - performance: config.route.performance, - position: _position, - child: new FadeTransition( - performance: config.route.performance, - opacity: _opacity, - child: _invokeBuilder() - ) - ); - } - - Widget _invokeBuilder() { - Widget result = config.route.builder(null); - assert(() { - if (result == null) - debugPrint('The builder for route \'${config.route.name}\' returned null. Route builders must never return null.'); - assert(result != null && 'A route builder returned null. See the previous log message for details.' is String); - return true; - }); - return result; - } -} - -class PageRoute extends TransitionRoute { - PageRoute({ - this.builder, - this.name: '', - this.mostValuableKeys - }) { - assert(builder != null); - } - - final RouteBuilder builder; - final String name; - final Set mostValuableKeys; - - Duration get transitionDuration => const Duration(milliseconds: 150); - Widget createWidget() => new _Page(route: this); - - String get debugLabel => '${super.debugLabel}($name)'; -} diff --git a/packages/flutter/lib/src/widgets/page.dart b/packages/flutter/lib/src/widgets/page.dart new file mode 100644 index 00000000000..64ca2fdda5e --- /dev/null +++ b/packages/flutter/lib/src/widgets/page.dart @@ -0,0 +1,112 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/animation.dart'; + +import 'basic.dart'; +import 'framework.dart'; +import 'navigator2.dart'; +import 'overlay.dart'; +import 'transitions.dart'; + +abstract class TransitionRoute extends Route { + bool get opaque => true; + + PerformanceView get performance => _performance?.view; + Performance _performance; + + Duration get transitionDuration; + + Performance createPerformance() { + Duration duration = transitionDuration; + assert(duration != null && duration >= Duration.ZERO); + return new Performance(duration: duration, debugLabel: debugLabel); + } + + dynamic _result; + + void _handleStatusChanged(PerformanceStatus status) { + if (status == PerformanceStatus.completed && opaque) { + bottomEntry.opaque = true; + } else if (status == PerformanceStatus.dismissed) { + super.remove(_result); + } + } + + void add(OverlayState overlayer, OverlayEntry insertionPoint) { + _performance = createPerformance() + ..addStatusListener(_handleStatusChanged) + ..forward(); + super.add(overlayer, insertionPoint); + } + + void remove(dynamic result) { + _result = result; + _performance.reverse(); + } + + String get debugLabel => '$runtimeType'; + String toString() => '$runtimeType(performance: $_performance)'; +} + +class _Page extends StatefulComponent { + _Page({ + PageRoute route + }) : route = route, super(key: new GlobalObjectKey(route)); + + final PageRoute route; + + _PageState createState() => new _PageState(); +} + +class _PageState extends State<_Page> { + final AnimatedValue _position = + new AnimatedValue(const Point(0.0, 75.0), end: Point.origin, curve: Curves.easeOut); + + final AnimatedValue _opacity = + new AnimatedValue(0.0, end: 1.0, curve: Curves.easeOut); + + Widget build(BuildContext context) { + return new SlideTransition( + performance: config.route.performance, + position: _position, + child: new FadeTransition( + performance: config.route.performance, + opacity: _opacity, + child: _invokeBuilder() + ) + ); + } + + Widget _invokeBuilder() { + Widget result = config.route.builder(context); + assert(() { + if (result == null) + debugPrint('The builder for route \'${config.route.name}\' returned null. Route builders must never return null.'); + assert(result != null && 'A route builder returned null. See the previous log message for details.' is String); + return true; + }); + return result; + } +} + +class PageRoute extends TransitionRoute { + PageRoute({ + this.builder, + this.args: const RouteArguments() + }) { + assert(builder != null); + assert(opaque); + } + + final WidgetBuilder builder; + final RouteArguments args; + + String get name => args.name; + + Duration get transitionDuration => const Duration(milliseconds: 150); + List createWidgets() => [ new _Page(route: this) ]; + + String get debugLabel => '${super.debugLabel}($name)'; +} From 395184f77f241fac249a2f91a21b5a03de40a3a6 Mon Sep 17 00:00:00 2001 From: Adam Barth Date: Thu, 29 Oct 2015 01:27:48 -0700 Subject: [PATCH 2/2] Add hero transition support to Navigator2 In this approach, the hero support is layered on top of the basic navigator functionality. --- .../flutter/lib/src/animation/scheduler.dart | 16 +-- .../lib/src/material/material_app.dart | 15 ++- .../lib/src/widgets/hero_controller.dart | 112 ++++++++++++++++++ packages/flutter/lib/src/widgets/heroes.dart | 4 +- .../flutter/lib/src/widgets/navigator2.dart | 49 +++++--- packages/flutter/lib/src/widgets/overlay.dart | 6 +- packages/flutter/lib/src/widgets/page.dart | 61 +++++++--- 7 files changed, 207 insertions(+), 56 deletions(-) create mode 100644 packages/flutter/lib/src/widgets/hero_controller.dart diff --git a/packages/flutter/lib/src/animation/scheduler.dart b/packages/flutter/lib/src/animation/scheduler.dart index 2d374f83575..8b1d891b465 100644 --- a/packages/flutter/lib/src/animation/scheduler.dart +++ b/packages/flutter/lib/src/animation/scheduler.dart @@ -31,7 +31,6 @@ class Scheduler { } bool _haveScheduledVisualUpdate = false; - int _nextPrivateCallbackId = 0; // negative int _nextCallbackId = 0; // positive final List _persistentCallbacks = new List(); @@ -55,7 +54,6 @@ class Scheduler { Duration timeStamp = new Duration( microseconds: (rawTimeStamp.inMicroseconds / timeDilation).round()); _haveScheduledVisualUpdate = false; - assert(_postFrameCallbacks.length == 0); Map callbacks = _transientCallbacks; _transientCallbacks = new Map(); @@ -68,9 +66,11 @@ class Scheduler { for (SchedulerCallback callback in _persistentCallbacks) invokeCallback(callback, timeStamp); - for (SchedulerCallback callback in _postFrameCallbacks) - invokeCallback(callback, timeStamp); + List localPostFrameCallbacks = + new List.from(_postFrameCallbacks); _postFrameCallbacks.clear(); + for (SchedulerCallback callback in localPostFrameCallbacks) + invokeCallback(callback, timeStamp); _inFrame = false; } @@ -133,13 +133,7 @@ class Scheduler { /// frame. In this case, the registration order is not preserved. Callbacks /// are called in an arbitrary order. void requestPostFrameCallback(SchedulerCallback callback) { - if (_inFrame) { - _postFrameCallbacks.add(callback); - } else { - _nextPrivateCallbackId -= 1; - _transientCallbacks[_nextPrivateCallbackId] = callback; - ensureVisualUpdate(); - } + _postFrameCallbacks.add(callback); } /// Ensure that a frame will be produced after this function is called. diff --git a/packages/flutter/lib/src/material/material_app.dart b/packages/flutter/lib/src/material/material_app.dart index 81df08aa24a..117c6d5c038 100644 --- a/packages/flutter/lib/src/material/material_app.dart +++ b/packages/flutter/lib/src/material/material_app.dart @@ -9,7 +9,7 @@ import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter/src/widgets/navigator2.dart' as n2; -import 'package:flutter/src/widgets/page.dart' as n2; +import 'package:flutter/src/widgets/hero_controller.dart' as n2; import 'theme.dart'; import 'title.dart'; @@ -87,13 +87,16 @@ class _MaterialAppState extends State { void _metricHandler(Size size) => setState(() { _size = size; }); - n2.Route _generateRoute(n2.RouteArguments args) { - return new n2.PageRoute( + final n2.HeroController _heroController = new n2.HeroController(); + + n2.Route _generateRoute(n2.NamedRouteSettings settings) { + return new n2.HeroPageRoute( builder: (BuildContext context) { - RouteArguments routeArgs = new RouteArguments(context: context); - return config.routes[args.name](routeArgs); + RouteBuilder builder = config.routes[settings.name] ?? config.onGenerateRoute(settings.name); + return builder(new RouteArguments(context: context)); }, - args: args + settings: settings, + heroController: _heroController ); } diff --git a/packages/flutter/lib/src/widgets/hero_controller.dart b/packages/flutter/lib/src/widgets/hero_controller.dart new file mode 100644 index 00000000000..3e0852bbce1 --- /dev/null +++ b/packages/flutter/lib/src/widgets/hero_controller.dart @@ -0,0 +1,112 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/animation.dart'; +import 'package:flutter/rendering.dart'; + +import 'basic.dart'; +import 'framework.dart'; +import 'heroes.dart'; +import 'navigator2.dart'; +import 'overlay.dart'; +import 'page.dart'; + +class HeroPageRoute extends PageRoute { + HeroPageRoute({ + WidgetBuilder builder, + NamedRouteSettings settings: const NamedRouteSettings(), + this.heroController + }) : super(builder: builder, settings: settings); + + final HeroController heroController; + + void didMakeCurrent() { + heroController?.didMakeCurrent(this); + } +} + +class HeroController { + HeroController() { + _party = new HeroParty(onQuestFinished: _handleQuestFinished); + } + + HeroParty _party; + HeroPageRoute _from; + HeroPageRoute _to; + + final List _overlayEntries = new List(); + + void didMakeCurrent(PageRoute current) { + assert(current != null); + assert(current.performance != null); + if (_from == null) { + _from = current; + return; + } + _to = current; + current.offstage = true; + scheduler.requestPostFrameCallback(_updateQuest); + } + + void _handleQuestFinished() { + _removeHeroesFromOverlay(); + _from = _to; + _to = null; + } + + Rect _getAnimationArea(BuildContext context) { + RenderBox box = context.findRenderObject(); + Point topLeft = box.localToGlobal(Point.origin); + Point bottomRight = box.localToGlobal(box.size.bottomRight(Point.origin)); + return new Rect.fromLTRB(topLeft.x, topLeft.y, bottomRight.x, bottomRight.y); + } + + void _removeHeroesFromOverlay() { + for (OverlayEntry entry in _overlayEntries) + entry.remove(); + _overlayEntries.clear(); + } + + void _addHeroesToOverlay(Iterable heroes, OverlayState overlay) { + OverlayEntry insertionPoint = _to.topEntry; + for (Widget hero in heroes) { + OverlayEntry entry = new OverlayEntry(child: hero); + overlay.insert(entry, above: insertionPoint); + _overlayEntries.add(entry); + } + } + + Set _getMostValuableKeys() { + Set result = new Set(); + if (_from.settings.mostValuableKeys != null) + result.addAll(_from.settings.mostValuableKeys); + if (_to.settings.mostValuableKeys != null) + result.addAll(_to.settings.mostValuableKeys); + return result; + } + + void _updateQuest(Duration timeStamp) { + Set mostValuableKeys = _getMostValuableKeys(); + + Map heroesFrom = _party.isEmpty ? + Hero.of(_from.pageKey.currentContext, mostValuableKeys) : _party.getHeroesToAnimate(); + + BuildContext context = _to.pageKey.currentContext; + Map heroesTo = Hero.of(context, mostValuableKeys); + _to.offstage = false; + + PerformanceView performance = _to.performance; + Curve curve = Curves.ease; + if (performance.status == PerformanceStatus.reverse) { + performance = new ReversePerformance(performance); + curve = new Interval(performance.progress, 1.0, curve: curve); + } + + NavigatorState navigator = Navigator.of(context); + _party.animate(heroesFrom, heroesTo, _getAnimationArea(navigator.context), curve); + _removeHeroesFromOverlay(); + Iterable heroes = _party.getWidgets(navigator.context, performance); + _addHeroesToOverlay(heroes, navigator.overlay); + } +} diff --git a/packages/flutter/lib/src/widgets/heroes.dart b/packages/flutter/lib/src/widgets/heroes.dart index 83f794d3b27..0dee9abd08a 100644 --- a/packages/flutter/lib/src/widgets/heroes.dart +++ b/packages/flutter/lib/src/widgets/heroes.dart @@ -387,11 +387,11 @@ class HeroParty { hero.targetState._setChild(hero.key); for (HeroState source in hero.sourceStates) source._resetChild(); - if (onQuestFinished != null) - onQuestFinished(); } _heroes.clear(); _currentPerformance = null; + if (onQuestFinished != null) + onQuestFinished(); } } diff --git a/packages/flutter/lib/src/widgets/navigator2.dart b/packages/flutter/lib/src/widgets/navigator2.dart index debd883bf79..354bb95cb5b 100644 --- a/packages/flutter/lib/src/widgets/navigator2.dart +++ b/packages/flutter/lib/src/widgets/navigator2.dart @@ -13,7 +13,7 @@ abstract class Route { final List _entries = new List(); - void add(OverlayState overlay, OverlayEntry insertionPoint) { + void didPush(OverlayState overlay, OverlayEntry insertionPoint) { List widgets = createWidgets(); for (Widget widget in widgets) { _entries.add(new OverlayEntry(child: widget)); @@ -22,20 +22,22 @@ abstract class Route { } } - void remove(dynamic result) { + void didMakeCurrent() { } + + void didPop(dynamic result) { for (OverlayEntry entry in _entries) entry.remove(); } } -class RouteArguments { - const RouteArguments({ this.name: '', this.mostValuableKeys }); +class NamedRouteSettings { + const NamedRouteSettings({ this.name: '', this.mostValuableKeys }); final String name; final Set mostValuableKeys; } -typedef Route RouteFactory(RouteArguments args); +typedef Route RouteFactory(NamedRouteSettings settings); class Navigator extends StatefulComponent { Navigator({ @@ -67,18 +69,19 @@ class Navigator extends StatefulComponent { } class NavigatorState extends State { - final GlobalKey _overlay = new GlobalKey(); + final GlobalKey _overlayKey = new GlobalKey(); final List _ephemeral = new List(); final List _modal = new List(); void initState() { super.initState(); - push(config.onGenerateRoute(new RouteArguments(name: Navigator.defaultRouteName))); + push(config.onGenerateRoute(new NamedRouteSettings(name: Navigator.defaultRouteName))); } bool get hasPreviousRoute => _modal.length > 1; + OverlayState get overlay => _overlayKey.currentState; - OverlayEntry get _topRouteOverlay { + OverlayEntry get _currentOverlay { for (Route route in _ephemeral.reversed) { if (route.topEntry != null) return route.topEntry; @@ -90,41 +93,49 @@ class NavigatorState extends State { return null; } + Route get _currentRoute => _ephemeral.isNotEmpty ? _ephemeral.last : _modal.last; + + Route _removeCurrentRoute() { + return _ephemeral.isNotEmpty ? _ephemeral.removeLast() : _modal.removeLast(); + } + void pushNamed(String name, { Set mostValuableKeys }) { - RouteArguments args = new RouteArguments(name: name, mostValuableKeys: mostValuableKeys); - push(config.onGenerateRoute(args) ?? config.onUnknownRoute(args)); + NamedRouteSettings settings = new NamedRouteSettings( + name: name, + mostValuableKeys: mostValuableKeys + ); + push(config.onGenerateRoute(settings) ?? config.onUnknownRoute(settings)); } void push(Route route) { _popAllEphemeralRoutes(); - route.add(_overlay.currentState, _topRouteOverlay); + route.didPush(overlay, _currentOverlay); _modal.add(route); + route.didMakeCurrent(); } void pushEphemeral(Route route) { - route.add(_overlay.currentState, _topRouteOverlay); + route.didPush(overlay, _currentOverlay); _ephemeral.add(route); + route.didMakeCurrent(); } void _popAllEphemeralRoutes() { List localEphemeral = new List.from(_ephemeral); _ephemeral.clear(); for (Route route in localEphemeral) - route.remove(null); + route.didPop(null); assert(_ephemeral.isEmpty); } void pop([dynamic result]) { - if (_ephemeral.isNotEmpty) { - _ephemeral.removeLast().remove(result); - return; - } - _modal.removeLast().remove(result); + _removeCurrentRoute().didPop(result); + _currentRoute.didMakeCurrent(); } Widget build(BuildContext context) { return new Overlay( - key: _overlay, + key: _overlayKey, initialEntries: _modal.first._entries ); } diff --git a/packages/flutter/lib/src/widgets/overlay.dart b/packages/flutter/lib/src/widgets/overlay.dart index 8e8ec6f570f..fc289fbb397 100644 --- a/packages/flutter/lib/src/widgets/overlay.dart +++ b/packages/flutter/lib/src/widgets/overlay.dart @@ -16,6 +16,8 @@ class OverlayEntry { bool get opaque => _opaque; bool _opaque; void set opaque(bool value) { + if (_opaque = value) + return; _opaque = value; _state?.setState(() {}); } @@ -51,10 +53,6 @@ class OverlayState extends State { void insert(OverlayEntry entry, { OverlayEntry above }) { assert(entry._state == null); - if (above != null) { - print('above._state ${above._state} --- ${above._state == this}'); - print('_entries.contains ${_entries.contains(above)}'); - } assert(above == null || (above._state == this && _entries.contains(above))); entry._state = this; setState(() { diff --git a/packages/flutter/lib/src/widgets/page.dart b/packages/flutter/lib/src/widgets/page.dart index 64ca2fdda5e..4e1341a886a 100644 --- a/packages/flutter/lib/src/widgets/page.dart +++ b/packages/flutter/lib/src/widgets/page.dart @@ -10,6 +10,7 @@ import 'navigator2.dart'; import 'overlay.dart'; import 'transitions.dart'; +// TODO(abarth): Should we add a type for the result? abstract class TransitionRoute extends Route { bool get opaque => true; @@ -27,21 +28,28 @@ abstract class TransitionRoute extends Route { dynamic _result; void _handleStatusChanged(PerformanceStatus status) { - if (status == PerformanceStatus.completed && opaque) { - bottomEntry.opaque = true; - } else if (status == PerformanceStatus.dismissed) { - super.remove(_result); + switch (status) { + case PerformanceStatus.completed: + bottomEntry.opaque = opaque; + break; + case PerformanceStatus.forward: + case PerformanceStatus.reverse: + bottomEntry.opaque = false; + break; + case PerformanceStatus.dismissed: + super.didPop(_result); + break; } } - void add(OverlayState overlayer, OverlayEntry insertionPoint) { + void didPush(OverlayState overlay, OverlayEntry insertionPoint) { _performance = createPerformance() ..addStatusListener(_handleStatusChanged) ..forward(); - super.add(overlayer, insertionPoint); + super.didPush(overlay, insertionPoint); } - void remove(dynamic result) { + void didPop(dynamic result) { _result = result; _performance.reverse(); } @@ -52,8 +60,9 @@ abstract class TransitionRoute extends Route { class _Page extends StatefulComponent { _Page({ - PageRoute route - }) : route = route, super(key: new GlobalObjectKey(route)); + Key key, + this.route + }) : super(key: key); final PageRoute route; @@ -67,14 +76,27 @@ class _PageState extends State<_Page> { final AnimatedValue _opacity = new AnimatedValue(0.0, end: 1.0, curve: Curves.easeOut); + final GlobalKey _subtreeKey = new GlobalKey(); + Widget build(BuildContext context) { + if (config.route._offstage) { + return new OffStage( + child: new KeyedSubtree( + key: _subtreeKey, + child: _invokeBuilder() + ) + ); + } return new SlideTransition( performance: config.route.performance, position: _position, child: new FadeTransition( performance: config.route.performance, opacity: _opacity, - child: _invokeBuilder() + child: new KeyedSubtree( + key: _subtreeKey, + child: _invokeBuilder() + ) ) ); } @@ -94,19 +116,30 @@ class _PageState extends State<_Page> { class PageRoute extends TransitionRoute { PageRoute({ this.builder, - this.args: const RouteArguments() + this.settings: const NamedRouteSettings() }) { assert(builder != null); assert(opaque); } final WidgetBuilder builder; - final RouteArguments args; + final NamedRouteSettings settings; - String get name => args.name; + final GlobalKey<_PageState> pageKey = new GlobalKey<_PageState>(); + + String get name => settings.name; Duration get transitionDuration => const Duration(milliseconds: 150); - List createWidgets() => [ new _Page(route: this) ]; + List createWidgets() => [ new _Page(key: pageKey, route: this) ]; + + bool get offstage => _offstage; + bool _offstage = false; + void set offstage (bool value) { + if (_offstage == value) + return; + _offstage = value; + pageKey.currentState?.setState(() { }); + } String get debugLabel => '${super.debugLabel}($name)'; }