mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Merge pull request #1866 from abarth/route_types
Add support for modal, ephemeral, and contentless routes to Navigator2
This commit is contained in:
commit
9548634bc2
@ -31,7 +31,6 @@ class Scheduler {
|
||||
}
|
||||
|
||||
bool _haveScheduledVisualUpdate = false;
|
||||
int _nextPrivateCallbackId = 0; // negative
|
||||
int _nextCallbackId = 0; // positive
|
||||
|
||||
final List<SchedulerCallback> _persistentCallbacks = new List<SchedulerCallback>();
|
||||
@ -55,7 +54,6 @@ class Scheduler {
|
||||
Duration timeStamp = new Duration(
|
||||
microseconds: (rawTimeStamp.inMicroseconds / timeDilation).round());
|
||||
_haveScheduledVisualUpdate = false;
|
||||
assert(_postFrameCallbacks.length == 0);
|
||||
|
||||
Map<int, SchedulerCallback> callbacks = _transientCallbacks;
|
||||
_transientCallbacks = new Map<int, SchedulerCallback>();
|
||||
@ -68,9 +66,11 @@ class Scheduler {
|
||||
for (SchedulerCallback callback in _persistentCallbacks)
|
||||
invokeCallback(callback, timeStamp);
|
||||
|
||||
for (SchedulerCallback callback in _postFrameCallbacks)
|
||||
invokeCallback(callback, timeStamp);
|
||||
List<SchedulerCallback> localPostFrameCallbacks =
|
||||
new List<SchedulerCallback>.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.
|
||||
|
||||
@ -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/hero_controller.dart' as n2;
|
||||
|
||||
import 'theme.dart';
|
||||
import 'title.dart';
|
||||
@ -86,12 +87,25 @@ class _MaterialAppState extends State<MaterialApp> {
|
||||
|
||||
void _metricHandler(Size size) => setState(() { _size = size; });
|
||||
|
||||
final n2.HeroController _heroController = new n2.HeroController();
|
||||
|
||||
n2.Route _generateRoute(n2.NamedRouteSettings settings) {
|
||||
return new n2.HeroPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
RouteBuilder builder = config.routes[settings.name] ?? config.onGenerateRoute(settings.name);
|
||||
return builder(new RouteArguments(context: context));
|
||||
},
|
||||
settings: settings,
|
||||
heroController: _heroController
|
||||
);
|
||||
}
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
Widget navigator;
|
||||
if (_kUseNavigator2) {
|
||||
navigator = new n2.Navigator(
|
||||
key: _navigator,
|
||||
routes: config.routes
|
||||
onGenerateRoute: _generateRoute
|
||||
);
|
||||
} else {
|
||||
navigator = new Navigator(
|
||||
|
||||
112
packages/flutter/lib/src/widgets/hero_controller.dart
Normal file
112
packages/flutter/lib/src/widgets/hero_controller.dart
Normal file
@ -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<OverlayEntry> _overlayEntries = new List<OverlayEntry>();
|
||||
|
||||
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<Widget> 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<Key> _getMostValuableKeys() {
|
||||
Set<Key> result = new Set<Key>();
|
||||
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<Key> mostValuableKeys = _getMostValuableKeys();
|
||||
|
||||
Map<Object, HeroHandle> heroesFrom = _party.isEmpty ?
|
||||
Hero.of(_from.pageKey.currentContext, mostValuableKeys) : _party.getHeroesToAnimate();
|
||||
|
||||
BuildContext context = _to.pageKey.currentContext;
|
||||
Map<Object, HeroHandle> 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<Widget> heroes = _party.getWidgets(navigator.context, performance);
|
||||
_addHeroesToOverlay(heroes, navigator.overlay);
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -2,55 +2,56 @@
|
||||
// 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<Widget> createWidgets() => const <Widget>[];
|
||||
|
||||
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<OverlayEntry> _entries = new List<OverlayEntry>();
|
||||
|
||||
void didPush(OverlayState overlay, OverlayEntry insertionPoint) {
|
||||
List<Widget> widgets = createWidgets();
|
||||
for (Widget widget in widgets) {
|
||||
_entries.add(new OverlayEntry(child: widget));
|
||||
overlay?.insert(_entries.last, above: insertionPoint);
|
||||
insertionPoint = _entries.last;
|
||||
}
|
||||
}
|
||||
|
||||
void didMakeCurrent() { }
|
||||
|
||||
void didPop(dynamic result) {
|
||||
_entry.remove();
|
||||
for (OverlayEntry entry in _entries)
|
||||
entry.remove();
|
||||
}
|
||||
}
|
||||
|
||||
typedef Widget RouteBuilder(args);
|
||||
typedef RouteBuilder RouteGenerator(String name);
|
||||
class NamedRouteSettings {
|
||||
const NamedRouteSettings({ this.name: '<anonymous>', this.mostValuableKeys });
|
||||
|
||||
const String _kDefaultPageName = '/';
|
||||
final String name;
|
||||
final Set<Key> mostValuableKeys;
|
||||
}
|
||||
|
||||
typedef Route RouteFactory(NamedRouteSettings settings);
|
||||
|
||||
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<String, RouteBuilder> 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 +69,74 @@ class Navigator extends StatefulComponent {
|
||||
}
|
||||
|
||||
class NavigatorState extends State<Navigator> {
|
||||
GlobalKey<OverlayState> _overlay = new GlobalKey<OverlayState>();
|
||||
List<Route> _history = new List<Route>();
|
||||
final GlobalKey<OverlayState> _overlayKey = new GlobalKey<OverlayState>();
|
||||
final List<Route> _ephemeral = new List<Route>();
|
||||
final List<Route> _modal = new List<Route>();
|
||||
|
||||
void initState() {
|
||||
super.initState();
|
||||
_addRouteToHistory(new PageRoute(
|
||||
builder: config.routes[_kDefaultPageName],
|
||||
name: _kDefaultPageName
|
||||
));
|
||||
push(config.onGenerateRoute(new NamedRouteSettings(name: Navigator.defaultRouteName)));
|
||||
}
|
||||
|
||||
RouteBuilder _generatePage(String name) {
|
||||
assert(config.onGeneratePage != null);
|
||||
return config.onGeneratePage(name);
|
||||
bool get hasPreviousRoute => _modal.length > 1;
|
||||
OverlayState get overlay => _overlayKey.currentState;
|
||||
|
||||
OverlayEntry get _currentOverlay {
|
||||
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;
|
||||
}
|
||||
|
||||
bool get hasPreviousRoute => _history.length > 1;
|
||||
Route get _currentRoute => _ephemeral.isNotEmpty ? _ephemeral.last : _modal.last;
|
||||
|
||||
Route _removeCurrentRoute() {
|
||||
return _ephemeral.isNotEmpty ? _ephemeral.removeLast() : _modal.removeLast();
|
||||
}
|
||||
|
||||
void pushNamed(String name, { Set<Key> mostValuableKeys }) {
|
||||
final RouteBuilder builder = config.routes[name] ?? _generatePage(name) ?? config.onUnknownPage;
|
||||
assert(builder != null); // 404 getting your 404!
|
||||
push(new PageRoute(
|
||||
builder: builder,
|
||||
NamedRouteSettings settings = new NamedRouteSettings(
|
||||
name: name,
|
||||
mostValuableKeys: mostValuableKeys
|
||||
));
|
||||
}
|
||||
|
||||
void _addRouteToHistory(Route route) {
|
||||
route.willPush();
|
||||
route._entry = new OverlayEntry(child: route._child);
|
||||
_history.add(route);
|
||||
);
|
||||
push(config.onGenerateRoute(settings) ?? config.onUnknownRoute(settings));
|
||||
}
|
||||
|
||||
void push(Route route) {
|
||||
OverlayEntry reference = _history.last._entry;
|
||||
_addRouteToHistory(route);
|
||||
_overlay.currentState.insert(route._entry, above: reference);
|
||||
_popAllEphemeralRoutes();
|
||||
route.didPush(overlay, _currentOverlay);
|
||||
_modal.add(route);
|
||||
route.didMakeCurrent();
|
||||
}
|
||||
|
||||
void pushEphemeral(Route route) {
|
||||
route.didPush(overlay, _currentOverlay);
|
||||
_ephemeral.add(route);
|
||||
route.didMakeCurrent();
|
||||
}
|
||||
|
||||
void _popAllEphemeralRoutes() {
|
||||
List<Route> localEphemeral = new List<Route>.from(_ephemeral);
|
||||
_ephemeral.clear();
|
||||
for (Route route in localEphemeral)
|
||||
route.didPop(null);
|
||||
assert(_ephemeral.isEmpty);
|
||||
}
|
||||
|
||||
void pop([dynamic result]) {
|
||||
_history.removeLast().didPop(result);
|
||||
_removeCurrentRoute().didPop(result);
|
||||
_currentRoute.didMakeCurrent();
|
||||
}
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
return new Overlay(
|
||||
key: _overlay,
|
||||
initialEntries: <OverlayEntry>[ _history.first._entry ]
|
||||
key: _overlayKey,
|
||||
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<Point> _position =
|
||||
new AnimatedValue<Point>(const Point(0.0, 75.0), end: Point.origin, curve: Curves.easeOut);
|
||||
|
||||
final AnimatedValue<double> _opacity =
|
||||
new AnimatedValue<double>(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: '<anonymous>',
|
||||
this.mostValuableKeys
|
||||
}) {
|
||||
assert(builder != null);
|
||||
}
|
||||
|
||||
final RouteBuilder builder;
|
||||
final String name;
|
||||
final Set<Key> mostValuableKeys;
|
||||
|
||||
Duration get transitionDuration => const Duration(milliseconds: 150);
|
||||
Widget createWidget() => new _Page(route: this);
|
||||
|
||||
String get debugLabel => '${super.debugLabel}($name)';
|
||||
}
|
||||
|
||||
@ -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<Overlay> {
|
||||
|
||||
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(() {
|
||||
|
||||
145
packages/flutter/lib/src/widgets/page.dart
Normal file
145
packages/flutter/lib/src/widgets/page.dart
Normal file
@ -0,0 +1,145 @@
|
||||
// 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';
|
||||
|
||||
// TODO(abarth): Should we add a type for the result?
|
||||
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) {
|
||||
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 didPush(OverlayState overlay, OverlayEntry insertionPoint) {
|
||||
_performance = createPerformance()
|
||||
..addStatusListener(_handleStatusChanged)
|
||||
..forward();
|
||||
super.didPush(overlay, insertionPoint);
|
||||
}
|
||||
|
||||
void didPop(dynamic result) {
|
||||
_result = result;
|
||||
_performance.reverse();
|
||||
}
|
||||
|
||||
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<Point> _position =
|
||||
new AnimatedValue<Point>(const Point(0.0, 75.0), end: Point.origin, curve: Curves.easeOut);
|
||||
|
||||
final AnimatedValue<double> _opacity =
|
||||
new AnimatedValue<double>(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: new KeyedSubtree(
|
||||
key: _subtreeKey,
|
||||
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.settings: const NamedRouteSettings()
|
||||
}) {
|
||||
assert(builder != null);
|
||||
assert(opaque);
|
||||
}
|
||||
|
||||
final WidgetBuilder builder;
|
||||
final NamedRouteSettings settings;
|
||||
|
||||
final GlobalKey<_PageState> pageKey = new GlobalKey<_PageState>();
|
||||
|
||||
String get name => settings.name;
|
||||
|
||||
Duration get transitionDuration => const Duration(milliseconds: 150);
|
||||
List<Widget> 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)';
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user