mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Add an initial implementation of Navigator2
This navigator can handle simple page navigation. I'll add more features in subsequent CLs.
This commit is contained in:
parent
cbf9eab8fe
commit
b991b7d664
@ -4,51 +4,71 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter/src/widgets/navigator2.dart' as n2;
|
||||
|
||||
class Home extends StatelessComponent {
|
||||
Widget build(BuildContext context) {
|
||||
return new Container(
|
||||
padding: const EdgeDims.all(30.0),
|
||||
decoration: new BoxDecoration(backgroundColor: const Color(0xFFCCCCCC)),
|
||||
child: new Column(<Widget>[
|
||||
new Text("You are at home"),
|
||||
new RaisedButton(
|
||||
child: new Text('GO SHOPPING'),
|
||||
onPressed: () => n2.Navigator.of(context).pushNamed('/shopping')
|
||||
),
|
||||
new RaisedButton(
|
||||
child: new Text('START ADVENTURE'),
|
||||
onPressed: () => n2.Navigator.of(context).pushNamed('/adventure')
|
||||
)],
|
||||
justifyContent: FlexJustifyContent.center
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Shopping extends StatelessComponent {
|
||||
Widget build(BuildContext context) {
|
||||
return new Container(
|
||||
padding: const EdgeDims.all(20.0),
|
||||
decoration: new BoxDecoration(backgroundColor: const Color(0xFFBF5FFF)),
|
||||
child: new Column(<Widget>[
|
||||
new Text("Village Shop"),
|
||||
new RaisedButton(
|
||||
child: new Text('RETURN HOME'),
|
||||
onPressed: () => n2.Navigator.of(context).pop()
|
||||
),
|
||||
new RaisedButton(
|
||||
child: new Text('GO TO DUNGEON'),
|
||||
onPressed: () => n2.Navigator.of(context).pushNamed('/adventure')
|
||||
)],
|
||||
justifyContent: FlexJustifyContent.center
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Adventure extends StatelessComponent {
|
||||
Widget build(BuildContext context) {
|
||||
return new Container(
|
||||
padding: const EdgeDims.all(20.0),
|
||||
decoration: new BoxDecoration(backgroundColor: const Color(0xFFDC143C)),
|
||||
child: new Column(<Widget>[
|
||||
new Text("Monster's Lair"),
|
||||
new RaisedButton(
|
||||
child: new Text('RUN!!!'),
|
||||
onPressed: () => n2.Navigator.of(context).pop()
|
||||
)],
|
||||
justifyContent: FlexJustifyContent.center
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final Map<String, RouteBuilder> routes = <String, RouteBuilder>{
|
||||
'/': (RouteArguments args) => new Container(
|
||||
padding: const EdgeDims.all(30.0),
|
||||
decoration: new BoxDecoration(backgroundColor: const Color(0xFFCCCCCC)),
|
||||
child: new Column(<Widget>[
|
||||
new Text("You are at home"),
|
||||
new RaisedButton(
|
||||
child: new Text('GO SHOPPING'),
|
||||
onPressed: () => Navigator.of(args.context).pushNamed('/shopping')
|
||||
),
|
||||
new RaisedButton(
|
||||
child: new Text('START ADVENTURE'),
|
||||
onPressed: () => Navigator.of(args.context).pushNamed('/adventure')
|
||||
)],
|
||||
justifyContent: FlexJustifyContent.center
|
||||
)
|
||||
),
|
||||
'/shopping': (RouteArguments args) => new Container(
|
||||
padding: const EdgeDims.all(20.0),
|
||||
decoration: new BoxDecoration(backgroundColor: const Color(0xFFBF5FFF)),
|
||||
child: new Column(<Widget>[
|
||||
new Text("Village Shop"),
|
||||
new RaisedButton(
|
||||
child: new Text('RETURN HOME'),
|
||||
onPressed: () => Navigator.of(args.context).pop()
|
||||
),
|
||||
new RaisedButton(
|
||||
child: new Text('GO TO DUNGEON'),
|
||||
onPressed: () => Navigator.of(args.context).pushNamed('/adventure')
|
||||
)],
|
||||
justifyContent: FlexJustifyContent.center
|
||||
)
|
||||
),
|
||||
'/adventure': (RouteArguments args) => new Container(
|
||||
padding: const EdgeDims.all(20.0),
|
||||
decoration: new BoxDecoration(backgroundColor: const Color(0xFFDC143C)),
|
||||
child: new Column(<Widget>[
|
||||
new Text("Monster's Lair"),
|
||||
new RaisedButton(
|
||||
child: new Text('RUN!!!'),
|
||||
onPressed: () => Navigator.of(args.context).pop()
|
||||
)],
|
||||
justifyContent: FlexJustifyContent.center
|
||||
)
|
||||
)
|
||||
'/': (_) => new Home(),
|
||||
'/shopping': (_) => new Shopping(),
|
||||
'/adventure': (_) => new Adventure(),
|
||||
};
|
||||
|
||||
final ThemeData theme = new ThemeData(
|
||||
|
@ -8,6 +8,8 @@ import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'package:flutter/src/widgets/navigator2.dart' as n2;
|
||||
|
||||
import 'theme.dart';
|
||||
import 'title.dart';
|
||||
|
||||
@ -31,6 +33,8 @@ AssetBundle _initDefaultBundle() {
|
||||
|
||||
final AssetBundle _defaultBundle = _initDefaultBundle();
|
||||
|
||||
const bool _kUseNavigator2 = false;
|
||||
|
||||
class MaterialApp extends StatefulComponent {
|
||||
MaterialApp({
|
||||
Key key,
|
||||
@ -83,6 +87,19 @@ class _MaterialAppState extends State<MaterialApp> {
|
||||
void _metricHandler(Size size) => setState(() { _size = size; });
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
Widget navigator;
|
||||
if (_kUseNavigator2) {
|
||||
navigator = new n2.Navigator(
|
||||
key: _navigator,
|
||||
routes: config.routes
|
||||
);
|
||||
} else {
|
||||
navigator = new Navigator(
|
||||
key: _navigator,
|
||||
routes: config.routes,
|
||||
onGenerateRoute: config.onGenerateRoute
|
||||
);
|
||||
}
|
||||
return new MediaQuery(
|
||||
data: new MediaQueryData(size: _size),
|
||||
child: new Theme(
|
||||
@ -93,11 +110,7 @@ class _MaterialAppState extends State<MaterialApp> {
|
||||
bundle: _defaultBundle,
|
||||
child: new Title(
|
||||
title: config.title,
|
||||
child: new Navigator(
|
||||
key: _navigator,
|
||||
routes: config.routes,
|
||||
onGenerateRoute: config.onGenerateRoute
|
||||
)
|
||||
child: navigator
|
||||
)
|
||||
)
|
||||
)
|
||||
|
206
packages/flutter/lib/src/widgets/navigator2.dart
Normal file
206
packages/flutter/lib/src/widgets/navigator2.dart
Normal file
@ -0,0 +1,206 @@
|
||||
// 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 '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();
|
||||
|
||||
Widget _child;
|
||||
OverlayEntry _entry;
|
||||
|
||||
void willPush() {
|
||||
_child = createWidget();
|
||||
}
|
||||
|
||||
void didPop(dynamic result) {
|
||||
_entry.remove();
|
||||
}
|
||||
}
|
||||
|
||||
typedef Widget RouteBuilder(args);
|
||||
typedef RouteBuilder RouteGenerator(String name);
|
||||
|
||||
const String _kDefaultPageName = '/';
|
||||
|
||||
class Navigator extends StatefulComponent {
|
||||
Navigator({
|
||||
Key key,
|
||||
this.routes,
|
||||
this.onGeneratePage,
|
||||
this.onUnknownPage
|
||||
}) : 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));
|
||||
}
|
||||
|
||||
final Map<String, RouteBuilder> routes;
|
||||
|
||||
/// 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 NavigatorState of(BuildContext context) {
|
||||
NavigatorState result;
|
||||
context.visitAncestorElements((Element element) {
|
||||
if (element is StatefulComponentElement && element.state is NavigatorState) {
|
||||
result = element.state;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
NavigatorState createState() => new NavigatorState();
|
||||
}
|
||||
|
||||
class NavigatorState extends State<Navigator> {
|
||||
GlobalKey<OverlayState> _overlay = new GlobalKey<OverlayState>();
|
||||
List<Route> _history = new List<Route>();
|
||||
|
||||
void initState() {
|
||||
super.initState();
|
||||
_addRouteToHistory(new PageRoute(
|
||||
builder: config.routes[_kDefaultPageName],
|
||||
name: _kDefaultPageName
|
||||
));
|
||||
}
|
||||
|
||||
RouteBuilder _generatePage(String name) {
|
||||
assert(config.onGeneratePage != null);
|
||||
return config.onGeneratePage(name);
|
||||
}
|
||||
|
||||
bool get hasPreviousRoute => _history.length > 1;
|
||||
|
||||
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,
|
||||
name: name,
|
||||
mostValuableKeys: mostValuableKeys
|
||||
));
|
||||
}
|
||||
|
||||
void _addRouteToHistory(Route route) {
|
||||
route.willPush();
|
||||
route._entry = new OverlayEntry(child: route._child);
|
||||
_history.add(route);
|
||||
}
|
||||
|
||||
void push(Route route) {
|
||||
OverlayEntry reference = _history.last._entry;
|
||||
_addRouteToHistory(route);
|
||||
_overlay.currentState.insert(route._entry, above: reference);
|
||||
}
|
||||
|
||||
void pop([dynamic result]) {
|
||||
_history.removeLast().didPop(result);
|
||||
}
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
return new Overlay(
|
||||
key: _overlay,
|
||||
initialEntries: <OverlayEntry>[ _history.first._entry ]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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)';
|
||||
}
|
87
packages/flutter/lib/src/widgets/overlay.dart
Normal file
87
packages/flutter/lib/src/widgets/overlay.dart
Normal file
@ -0,0 +1,87 @@
|
||||
// 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 'basic.dart';
|
||||
import 'framework.dart';
|
||||
|
||||
class OverlayEntry {
|
||||
OverlayEntry({
|
||||
this.child,
|
||||
bool opaque: false
|
||||
}) : _opaque = opaque;
|
||||
|
||||
final Widget child;
|
||||
|
||||
bool get opaque => _opaque;
|
||||
bool _opaque;
|
||||
void set opaque(bool value) {
|
||||
_opaque = value;
|
||||
_state?.setState(() {});
|
||||
}
|
||||
|
||||
OverlayState _state;
|
||||
|
||||
/// Remove the entry from the overlay.
|
||||
void remove() {
|
||||
_state?._remove(this);
|
||||
_state = null;
|
||||
}
|
||||
}
|
||||
|
||||
class Overlay extends StatefulComponent {
|
||||
Overlay({
|
||||
Key key,
|
||||
this.initialEntries
|
||||
}) : super(key: key);
|
||||
|
||||
final List<OverlayEntry> initialEntries;
|
||||
|
||||
OverlayState createState() => new OverlayState();
|
||||
}
|
||||
|
||||
class OverlayState extends State<Overlay> {
|
||||
final List<OverlayEntry> _entries = new List<OverlayEntry>();
|
||||
|
||||
void initState() {
|
||||
super.initState();
|
||||
for (OverlayEntry entry in config.initialEntries)
|
||||
insert(entry);
|
||||
}
|
||||
|
||||
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(() {
|
||||
int index = above == null ? _entries.length : _entries.indexOf(above) + 1;
|
||||
_entries.insert(index, entry);
|
||||
});
|
||||
}
|
||||
|
||||
void _remove(OverlayEntry entry) {
|
||||
setState(() {
|
||||
_entries.remove(entry);
|
||||
});
|
||||
}
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
List<Widget> backwardsChildren = <Widget>[];
|
||||
|
||||
for (int i = _entries.length - 1; i >= 0; --i) {
|
||||
OverlayEntry entry = _entries[i];
|
||||
backwardsChildren.add(new KeyedSubtree(
|
||||
key: new ObjectKey(entry),
|
||||
child: entry.child
|
||||
));
|
||||
if (entry.opaque)
|
||||
break;
|
||||
}
|
||||
|
||||
return new Stack(backwardsChildren.reversed.toList(growable: false));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user