mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Merge pull request #385 from Hixie/MaterialPageRoute
Move Material page animations to Material layer.
This commit is contained in:
commit
d5e71f768b
@ -33,9 +33,11 @@ export 'src/material/material.dart';
|
||||
export 'src/material/material_app.dart';
|
||||
export 'src/material/material_button.dart';
|
||||
export 'src/material/material_list.dart';
|
||||
export 'src/material/popup_menu_item.dart';
|
||||
export 'src/material/page.dart';
|
||||
export 'src/material/popup_menu.dart';
|
||||
export 'src/material/popup_menu_item.dart';
|
||||
export 'src/material/progress_indicator.dart';
|
||||
export 'src/material/radial_reaction.dart';
|
||||
export 'src/material/radio.dart';
|
||||
export 'src/material/raised_button.dart';
|
||||
export 'src/material/scaffold.dart';
|
||||
@ -44,11 +46,10 @@ export 'src/material/shadows.dart';
|
||||
export 'src/material/snack_bar.dart';
|
||||
export 'src/material/switch.dart';
|
||||
export 'src/material/tabs.dart';
|
||||
export 'src/material/theme_data.dart';
|
||||
export 'src/material/theme.dart';
|
||||
export 'src/material/theme_data.dart';
|
||||
export 'src/material/title.dart';
|
||||
export 'src/material/tool_bar.dart';
|
||||
export 'src/material/typography.dart';
|
||||
export 'src/material/radial_reaction.dart';
|
||||
|
||||
export 'widgets.dart';
|
||||
|
||||
@ -168,7 +168,7 @@ class Performance extends PerformanceView {
|
||||
|
||||
/// Returns a [PerformanceView] for this performance,
|
||||
/// so that a pointer to this object can be passed around without
|
||||
/// allowing users of that pointer to mutate the AnimationPerformance state.
|
||||
/// allowing users of that pointer to mutate the Performance state.
|
||||
PerformanceView get view => this;
|
||||
|
||||
/// The length of time this performance should last
|
||||
|
||||
@ -124,7 +124,9 @@ class _DialogRoute extends ModalRoute {
|
||||
Duration get transitionDuration => const Duration(milliseconds: 150);
|
||||
Color get barrierColor => Colors.black54;
|
||||
|
||||
Widget buildModalWidget(BuildContext context) {
|
||||
Widget buildPage(BuildContext context) => child;
|
||||
|
||||
Widget buildTransition(BuildContext context, PerformanceView performance, Widget child) {
|
||||
return new FadeTransition(
|
||||
performance: performance,
|
||||
opacity: new AnimatedValue<double>(0.0, end: 1.0, curve: Curves.easeOut),
|
||||
|
||||
@ -8,6 +8,7 @@ import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'page.dart';
|
||||
import 'theme.dart';
|
||||
import 'title.dart';
|
||||
|
||||
@ -95,13 +96,12 @@ class _MaterialAppState extends State<MaterialApp> {
|
||||
final HeroController _heroController = new HeroController();
|
||||
|
||||
Route _generateRoute(NamedRouteSettings settings) {
|
||||
return new HeroPageRoute(
|
||||
return new MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
RouteBuilder builder = config.routes[settings.name] ?? config.onGenerateRoute(settings.name);
|
||||
return builder(new RouteArguments(context: context));
|
||||
},
|
||||
settings: settings,
|
||||
heroController: _heroController
|
||||
settings: settings
|
||||
);
|
||||
}
|
||||
|
||||
@ -118,7 +118,8 @@ class _MaterialAppState extends State<MaterialApp> {
|
||||
title: config.title,
|
||||
child: new Navigator(
|
||||
key: _navigator,
|
||||
onGenerateRoute: _generateRoute
|
||||
onGenerateRoute: _generateRoute,
|
||||
observer: _heroController
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
73
packages/flutter/lib/src/material/page.dart
Normal file
73
packages/flutter/lib/src/material/page.dart
Normal file
@ -0,0 +1,73 @@
|
||||
// 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/widgets.dart';
|
||||
|
||||
class _MaterialPageTransition extends TransitionWithChild {
|
||||
_MaterialPageTransition({
|
||||
Key key,
|
||||
PerformanceView performance,
|
||||
Widget child
|
||||
}) : super(key: key,
|
||||
performance: performance,
|
||||
child: child);
|
||||
|
||||
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 buildWithChild(BuildContext context, Widget child) {
|
||||
performance.updateVariable(_position);
|
||||
performance.updateVariable(_opacity);
|
||||
Matrix4 transform = new Matrix4.identity()
|
||||
..translate(_position.value.x, _position.value.y);
|
||||
return new Transform(
|
||||
transform: transform,
|
||||
// TODO(ianh): tell the transform to be un-transformed for hit testing
|
||||
child: new Opacity(
|
||||
opacity: _opacity.value,
|
||||
child: child
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MaterialPageRoute extends ModalRoute {
|
||||
MaterialPageRoute({
|
||||
this.builder,
|
||||
NamedRouteSettings settings: const NamedRouteSettings()
|
||||
}) : super(settings: settings) {
|
||||
assert(builder != null);
|
||||
assert(opaque);
|
||||
}
|
||||
|
||||
final WidgetBuilder builder;
|
||||
|
||||
Duration get transitionDuration => const Duration(milliseconds: 150);
|
||||
|
||||
bool get opaque => true;
|
||||
|
||||
Widget buildPage(BuildContext context) {
|
||||
Widget result = builder(context);
|
||||
assert(() {
|
||||
if (result == null)
|
||||
debugPrint('The builder for route \'${settings.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;
|
||||
}
|
||||
|
||||
Widget buildTransition(BuildContext context, PerformanceView performance, Widget child) {
|
||||
return new _MaterialPageTransition(
|
||||
performance: performance,
|
||||
child: child
|
||||
);
|
||||
}
|
||||
|
||||
String get debugLabel => '${super.debugLabel}(${settings.name})';
|
||||
}
|
||||
@ -110,7 +110,7 @@ class _MenuRoute extends ModalRoute {
|
||||
bool get opaque => false;
|
||||
Duration get transitionDuration => _kMenuDuration;
|
||||
|
||||
Widget buildModalWidget(BuildContext context) => new _PopupMenu(route: this);
|
||||
Widget buildPage(BuildContext context) => new _PopupMenu(route: this);
|
||||
}
|
||||
|
||||
Future showMenu({ BuildContext context, ModalPosition position, List<PopupMenuItem> items, int level: 4 }) {
|
||||
|
||||
@ -10,71 +10,49 @@ import 'framework.dart';
|
||||
import 'heroes.dart';
|
||||
import 'navigator.dart';
|
||||
import 'overlay.dart';
|
||||
import 'page.dart';
|
||||
import 'routes.dart';
|
||||
|
||||
class HeroPageRoute extends PageRoute {
|
||||
HeroPageRoute({
|
||||
WidgetBuilder builder,
|
||||
NamedRouteSettings settings: const NamedRouteSettings(),
|
||||
this.heroController
|
||||
}) : super(builder: builder, settings: settings);
|
||||
|
||||
final HeroController heroController;
|
||||
NavigatorState _navigator;
|
||||
|
||||
void didPush(OverlayState overlay, OverlayEntry insertionPoint) {
|
||||
super.didPush(overlay, insertionPoint);
|
||||
// TODO(abarth): Pass the NavigatorState explicitly.
|
||||
if (overlay != null) {
|
||||
_navigator = Navigator.of(overlay.context);
|
||||
heroController?.didPush(_navigator, this);
|
||||
}
|
||||
}
|
||||
|
||||
void didPop(dynamic result) {
|
||||
super.didPop(result);
|
||||
if (_navigator != null) {
|
||||
heroController?.didPop(_navigator, this);
|
||||
_navigator = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class HeroController {
|
||||
class HeroController extends NavigatorObserver {
|
||||
HeroController() {
|
||||
_party = new HeroParty(onQuestFinished: _handleQuestFinished);
|
||||
}
|
||||
|
||||
HeroParty _party;
|
||||
PerformanceView _performance;
|
||||
HeroPageRoute _from;
|
||||
HeroPageRoute _to;
|
||||
ModalRoute _from;
|
||||
ModalRoute _to;
|
||||
|
||||
final List<OverlayEntry> _overlayEntries = new List<OverlayEntry>();
|
||||
|
||||
void didPush(NavigatorState navigator, HeroPageRoute route) {
|
||||
void didPushModal(Route route) {
|
||||
assert(navigator != null);
|
||||
assert(route != null);
|
||||
assert(route.performance != null);
|
||||
Route from = navigator.currentRoute;
|
||||
if (from is HeroPageRoute)
|
||||
_from = from;
|
||||
_to = route;
|
||||
_performance = route.performance;
|
||||
_checkForHeroQuest();
|
||||
}
|
||||
|
||||
void didPop(NavigatorState navigator, HeroPageRoute route) {
|
||||
assert(route != null);
|
||||
assert(route.performance != null);
|
||||
Route to = navigator.currentRoute;
|
||||
if (to is HeroPageRoute) {
|
||||
_to = to;
|
||||
_from = route;
|
||||
if (route is ModalRoute) { // as opposed to StateRoute, say
|
||||
assert(route.performance != null);
|
||||
Route from = navigator.currentRoute;
|
||||
if (from is ModalRoute) // as opposed to the many other types of routes, or null
|
||||
_from = from;
|
||||
_to = route;
|
||||
_performance = route.performance;
|
||||
_checkForHeroQuest();
|
||||
}
|
||||
}
|
||||
|
||||
void didPopModal(Route route) {
|
||||
assert(navigator != null);
|
||||
assert(route != null);
|
||||
if (route is ModalRoute) { // as opposed to StateRoute, say
|
||||
assert(route.performance != null);
|
||||
Route to = navigator.currentRoute;
|
||||
if (to is ModalRoute) { // as opposed to the many other types of routes
|
||||
_to = to;
|
||||
_from = route;
|
||||
_performance = route.performance;
|
||||
_checkForHeroQuest();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _checkForHeroQuest() {
|
||||
if (_from != null && _to != null && _from != _to) {
|
||||
_to.offstage = _to.performance.status != PerformanceStatus.completed;
|
||||
@ -123,10 +101,9 @@ class HeroController {
|
||||
Set<Key> mostValuableKeys = _getMostValuableKeys();
|
||||
|
||||
Map<Object, HeroHandle> heroesFrom = _party.isEmpty ?
|
||||
Hero.of(_from.pageKey.currentContext, mostValuableKeys) : _party.getHeroesToAnimate();
|
||||
Hero.of(_from.subtreeContext, mostValuableKeys) : _party.getHeroesToAnimate();
|
||||
|
||||
BuildContext context = _to.pageKey.currentContext;
|
||||
Map<Object, HeroHandle> heroesTo = Hero.of(context, mostValuableKeys);
|
||||
Map<Object, HeroHandle> heroesTo = Hero.of(_to.subtreeContext, mostValuableKeys);
|
||||
_to.offstage = false;
|
||||
|
||||
PerformanceView performance = _performance;
|
||||
@ -136,7 +113,6 @@ class HeroController {
|
||||
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);
|
||||
|
||||
@ -2,32 +2,30 @@
|
||||
// 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 'focus.dart';
|
||||
import 'framework.dart';
|
||||
import 'navigator.dart';
|
||||
import 'routes.dart';
|
||||
import 'status_transitions.dart';
|
||||
import 'transitions.dart';
|
||||
|
||||
const Color _kTransparent = const Color(0x00000000);
|
||||
const Color kTransparent = const Color(0x00000000);
|
||||
|
||||
class ModalBarrier extends StatelessComponent {
|
||||
ModalBarrier({
|
||||
Key key,
|
||||
this.color: _kTransparent
|
||||
this.color: kTransparent,
|
||||
this.dismissable: true
|
||||
}) : super(key: key);
|
||||
|
||||
final Color color;
|
||||
final bool dismissable;
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
return new Listener(
|
||||
onPointerDown: (_) {
|
||||
Navigator.of(context).pop();
|
||||
if (dismissable)
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: new ConstrainedBox(
|
||||
constraints: const BoxConstraints.expand(),
|
||||
@ -45,11 +43,13 @@ class AnimatedModalBarrier extends StatelessComponent {
|
||||
AnimatedModalBarrier({
|
||||
Key key,
|
||||
this.color,
|
||||
this.performance
|
||||
this.performance,
|
||||
this.dismissable: true
|
||||
}) : super(key: key);
|
||||
|
||||
final AnimatedColorValue color;
|
||||
final PerformanceView performance;
|
||||
final bool dismissable;
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
return new BuilderTransition(
|
||||
@ -58,69 +58,12 @@ class AnimatedModalBarrier extends StatelessComponent {
|
||||
builder: (BuildContext context) {
|
||||
return new IgnorePointer(
|
||||
ignoring: performance.status == PerformanceStatus.reverse,
|
||||
child: new ModalBarrier(color: color.value)
|
||||
child: new ModalBarrier(
|
||||
color: color.value,
|
||||
dismissable: dismissable
|
||||
)
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ModalScope extends StatusTransitionComponent {
|
||||
_ModalScope({
|
||||
Key key,
|
||||
ModalRoute route,
|
||||
this.child
|
||||
}) : route = route, super(key: key, performance: route.performance);
|
||||
|
||||
final ModalRoute route;
|
||||
final Widget child;
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
Widget focus = new Focus(
|
||||
key: new GlobalObjectKey(route),
|
||||
child: new IgnorePointer(
|
||||
ignoring: route.performance.status == PerformanceStatus.reverse,
|
||||
child: child
|
||||
)
|
||||
);
|
||||
ModalPosition position = route.position;
|
||||
if (position == null)
|
||||
return focus;
|
||||
return new Positioned(
|
||||
top: position.top,
|
||||
right: position.right,
|
||||
bottom: position.bottom,
|
||||
left: position.left,
|
||||
child: focus
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ModalPosition {
|
||||
const ModalPosition({ this.top, this.right, this.bottom, this.left });
|
||||
final double top;
|
||||
final double right;
|
||||
final double bottom;
|
||||
final double left;
|
||||
}
|
||||
|
||||
abstract class ModalRoute extends TransitionRoute {
|
||||
ModalRoute({ Completer completer }) : super(completer: completer);
|
||||
|
||||
ModalPosition get position => null;
|
||||
Color get barrierColor => _kTransparent;
|
||||
Widget buildModalWidget(BuildContext context);
|
||||
|
||||
Widget _buildModalBarrier(BuildContext context) {
|
||||
return new AnimatedModalBarrier(
|
||||
color: new AnimatedColorValue(_kTransparent, end: barrierColor, curve: Curves.ease),
|
||||
performance: performance
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildModalScope(BuildContext context) {
|
||||
return new _ModalScope(route: this, child: buildModalWidget(context));
|
||||
}
|
||||
|
||||
List<WidgetBuilder> get builders => <WidgetBuilder>[ _buildModalBarrier, _buildModalScope ];
|
||||
}
|
||||
|
||||
@ -21,17 +21,26 @@ class NamedRouteSettings {
|
||||
|
||||
typedef Route RouteFactory(NamedRouteSettings settings);
|
||||
|
||||
class NavigatorObserver {
|
||||
NavigatorState _navigator;
|
||||
NavigatorState get navigator => _navigator;
|
||||
void didPopModal(Route route) { }
|
||||
void didPushModal(Route route) { }
|
||||
}
|
||||
|
||||
class Navigator extends StatefulComponent {
|
||||
Navigator({
|
||||
Key key,
|
||||
this.onGenerateRoute,
|
||||
this.onUnknownRoute
|
||||
this.onUnknownRoute,
|
||||
this.observer
|
||||
}) : super(key: key) {
|
||||
assert(onGenerateRoute != null);
|
||||
}
|
||||
|
||||
final RouteFactory onGenerateRoute;
|
||||
final RouteFactory onUnknownRoute;
|
||||
final NavigatorObserver observer;
|
||||
|
||||
static const String defaultRouteName = '/';
|
||||
|
||||
@ -57,9 +66,24 @@ class NavigatorState extends State<Navigator> {
|
||||
|
||||
void initState() {
|
||||
super.initState();
|
||||
assert(config.observer == null || config.observer.navigator == null);
|
||||
config.observer?._navigator = this;
|
||||
push(config.onGenerateRoute(new NamedRouteSettings(name: Navigator.defaultRouteName)));
|
||||
}
|
||||
|
||||
void didUpdateConfig(Navigator oldConfig) {
|
||||
if (oldConfig.observer != config.observer) {
|
||||
oldConfig.observer?._navigator = null;
|
||||
assert(config.observer == null || config.observer.navigator == null);
|
||||
config.observer?._navigator = this;
|
||||
}
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
config.observer?._navigator = null;
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
bool get hasPreviousRoute => _modal.length > 1;
|
||||
OverlayState get overlay => _overlayKey.currentState;
|
||||
|
||||
@ -75,11 +99,7 @@ class NavigatorState extends State<Navigator> {
|
||||
return null;
|
||||
}
|
||||
|
||||
Route get currentRoute => _ephemeral.isNotEmpty ? _ephemeral.last : _modal.last;
|
||||
|
||||
Route _removeCurrentRoute() {
|
||||
return _ephemeral.isNotEmpty ? _ephemeral.removeLast() : _modal.removeLast();
|
||||
}
|
||||
Route get currentRoute => _ephemeral.isNotEmpty ? _ephemeral.last : _modal.isNotEmpty ? _modal.last : null;
|
||||
|
||||
void pushNamed(String name, { Set<Key> mostValuableKeys }) {
|
||||
assert(name != null);
|
||||
@ -90,9 +110,10 @@ class NavigatorState extends State<Navigator> {
|
||||
push(config.onGenerateRoute(settings) ?? config.onUnknownRoute(settings));
|
||||
}
|
||||
|
||||
void push(Route route) {
|
||||
void push(Route route, { Set<Key> mostValuableKeys }) {
|
||||
_popAllEphemeralRoutes();
|
||||
route.didPush(overlay, _currentOverlay);
|
||||
config.observer?.didPushModal(route);
|
||||
_modal.add(route);
|
||||
}
|
||||
|
||||
@ -110,7 +131,13 @@ class NavigatorState extends State<Navigator> {
|
||||
}
|
||||
|
||||
void pop([dynamic result]) {
|
||||
_removeCurrentRoute().didPop(result);
|
||||
if (_ephemeral.isNotEmpty) {
|
||||
_ephemeral.removeLast().didPop(result);
|
||||
} else {
|
||||
Route route = _modal.removeLast();
|
||||
route.didPop(result);
|
||||
config.observer?.didPopModal(route);
|
||||
}
|
||||
}
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
@ -1,122 +0,0 @@
|
||||
// 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 'modal_barrier.dart';
|
||||
import 'navigator.dart';
|
||||
import 'page_storage.dart';
|
||||
import 'transitions.dart';
|
||||
|
||||
class _PageTransition extends TransitionWithChild {
|
||||
_PageTransition({
|
||||
Key key,
|
||||
PerformanceView performance,
|
||||
Widget child
|
||||
}) : super(key: key,
|
||||
performance: performance,
|
||||
child: child);
|
||||
|
||||
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 buildWithChild(BuildContext context, Widget child) {
|
||||
performance.updateVariable(_position);
|
||||
performance.updateVariable(_opacity);
|
||||
Matrix4 transform = new Matrix4.identity()
|
||||
..translate(_position.value.x, _position.value.y);
|
||||
return new Transform(
|
||||
transform: transform,
|
||||
child: new Opacity(
|
||||
opacity: _opacity.value,
|
||||
child: child
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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 GlobalKey _subtreeKey = new GlobalKey();
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
if (config.route._offstage) {
|
||||
return new OffStage(
|
||||
child: new PageStorage(
|
||||
key: _subtreeKey,
|
||||
bucket: config.route._storageBucket,
|
||||
child: _invokeBuilder()
|
||||
)
|
||||
);
|
||||
}
|
||||
return new _PageTransition(
|
||||
performance: config.route.performance,
|
||||
child: new PageStorage(
|
||||
key: _subtreeKey,
|
||||
bucket: config.route._storageBucket,
|
||||
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 ModalRoute {
|
||||
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>();
|
||||
|
||||
bool get opaque => true;
|
||||
|
||||
String get name => settings.name;
|
||||
Duration get transitionDuration => const Duration(milliseconds: 150);
|
||||
Widget buildModalWidget(BuildContext context) => new _Page(key: pageKey, route: this);
|
||||
|
||||
final PageStorageBucket _storageBucket = new PageStorageBucket();
|
||||
|
||||
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)';
|
||||
}
|
||||
@ -7,9 +7,13 @@ import 'dart:async';
|
||||
import 'package:flutter/animation.dart';
|
||||
|
||||
import 'basic.dart';
|
||||
import 'focus.dart';
|
||||
import 'framework.dart';
|
||||
import 'modal_barrier.dart';
|
||||
import 'navigator.dart';
|
||||
import 'overlay.dart';
|
||||
import 'page_storage.dart';
|
||||
import 'status_transitions.dart';
|
||||
|
||||
class StateRoute extends Route {
|
||||
StateRoute({ this.onPop });
|
||||
@ -29,7 +33,7 @@ class OverlayRoute extends Route {
|
||||
List<WidgetBuilder> get builders => const <WidgetBuilder>[];
|
||||
|
||||
List<OverlayEntry> get overlayEntries => _overlayEntries;
|
||||
final List<OverlayEntry> _overlayEntries = new List<OverlayEntry>();
|
||||
final List<OverlayEntry> _overlayEntries = <OverlayEntry>[];
|
||||
|
||||
void didPush(OverlayState overlay, OverlayEntry insertionPoint) {
|
||||
for (WidgetBuilder builder in builders) {
|
||||
@ -99,3 +103,128 @@ abstract class TransitionRoute extends OverlayRoute {
|
||||
String get debugLabel => '$runtimeType';
|
||||
String toString() => '$runtimeType(performance: $_performance)';
|
||||
}
|
||||
|
||||
class _ModalScope extends StatusTransitionComponent {
|
||||
_ModalScope({
|
||||
Key key,
|
||||
this.subtreeKey,
|
||||
this.storageBucket,
|
||||
PerformanceView performance,
|
||||
this.route
|
||||
}) : super(key: key, performance: performance);
|
||||
|
||||
final GlobalKey subtreeKey;
|
||||
final PageStorageBucket storageBucket;
|
||||
final ModalRoute route;
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
Widget contents = new PageStorage(
|
||||
key: subtreeKey,
|
||||
bucket: storageBucket,
|
||||
child: route.buildPage(context)
|
||||
);
|
||||
if (route.offstage) {
|
||||
contents = new OffStage(child: contents);
|
||||
} else {
|
||||
contents = new Focus(
|
||||
key: new GlobalObjectKey(route),
|
||||
child: new IgnorePointer(
|
||||
ignoring: performance.status == PerformanceStatus.reverse,
|
||||
child: route.buildTransition(context, performance, contents)
|
||||
)
|
||||
);
|
||||
}
|
||||
ModalPosition position = route.position;
|
||||
if (position == null)
|
||||
return contents;
|
||||
return new Positioned(
|
||||
top: position.top,
|
||||
right: position.right,
|
||||
bottom: position.bottom,
|
||||
left: position.left,
|
||||
child: contents
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ModalPosition {
|
||||
const ModalPosition({ this.top, this.right, this.bottom, this.left });
|
||||
final double top;
|
||||
final double right;
|
||||
final double bottom;
|
||||
final double left;
|
||||
}
|
||||
|
||||
abstract class ModalRoute extends TransitionRoute {
|
||||
ModalRoute({
|
||||
Completer completer,
|
||||
this.settings: const NamedRouteSettings()
|
||||
}) : super(completer: completer);
|
||||
|
||||
final NamedRouteSettings settings;
|
||||
|
||||
|
||||
// The API for subclasses to override - used by _ModalScope
|
||||
|
||||
ModalPosition get position => null;
|
||||
Widget buildPage(BuildContext context);
|
||||
Widget buildTransition(BuildContext context, PerformanceView performance, Widget child) {
|
||||
return child;
|
||||
}
|
||||
|
||||
// The API for subclasses to override - used by this class
|
||||
|
||||
Color get barrierColor => kTransparent;
|
||||
|
||||
|
||||
// The API for _ModalScope and HeroController
|
||||
|
||||
bool get offstage => _offstage;
|
||||
bool _offstage = false;
|
||||
void set offstage (bool value) {
|
||||
if (_offstage == value)
|
||||
return;
|
||||
_offstage = value;
|
||||
_scopeKey.currentState?.setState(() {
|
||||
// _offstage is the value we're setting, but since there might not be a
|
||||
// state, we set it outside of this callback (which will only be called if
|
||||
// there's a state currently built).
|
||||
// _scopeKey is the key for the _ModalScope built in _buildModalScope().
|
||||
// When we mark that state dirty, it'll rebuild itself, and use our
|
||||
// offstage (via their config.route.offstage) when building.
|
||||
});
|
||||
}
|
||||
|
||||
BuildContext get subtreeContext => _subtreeKey.currentContext;
|
||||
|
||||
|
||||
// Internals
|
||||
|
||||
final GlobalKey<StatusTransitionState> _scopeKey = new GlobalKey<StatusTransitionState>();
|
||||
final GlobalKey _subtreeKey = new GlobalKey();
|
||||
final PageStorageBucket _storageBucket = new PageStorageBucket();
|
||||
|
||||
Widget _buildModalBarrier(BuildContext context) {
|
||||
return new AnimatedModalBarrier(
|
||||
color: new AnimatedColorValue(kTransparent, end: barrierColor, curve: Curves.ease),
|
||||
performance: performance,
|
||||
dismissable: false
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildModalScope(BuildContext context) {
|
||||
return new _ModalScope(
|
||||
key: _scopeKey,
|
||||
subtreeKey: _subtreeKey,
|
||||
storageBucket: _storageBucket,
|
||||
performance: performance,
|
||||
route: this
|
||||
);
|
||||
}
|
||||
|
||||
List<WidgetBuilder> get builders => <WidgetBuilder>[
|
||||
_buildModalBarrier,
|
||||
_buildModalScope
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
@ -18,10 +18,10 @@ abstract class StatusTransitionComponent extends StatefulComponent {
|
||||
|
||||
Widget build(BuildContext context);
|
||||
|
||||
_StatusTransitionState createState() => new _StatusTransitionState();
|
||||
StatusTransitionState createState() => new StatusTransitionState();
|
||||
}
|
||||
|
||||
class _StatusTransitionState extends State<StatusTransitionComponent> {
|
||||
class StatusTransitionState extends State<StatusTransitionComponent> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
config.performance.addStatusListener(_performanceStatusChanged);
|
||||
|
||||
@ -27,7 +27,6 @@ export 'src/widgets/modal_barrier.dart';
|
||||
export 'src/widgets/navigator.dart';
|
||||
export 'src/widgets/overlay.dart';
|
||||
export 'src/widgets/page_storage.dart';
|
||||
export 'src/widgets/page.dart';
|
||||
export 'src/widgets/placeholder.dart';
|
||||
export 'src/widgets/routes.dart';
|
||||
export 'src/widgets/scrollable.dart';
|
||||
|
||||
@ -37,8 +37,8 @@ void main() {
|
||||
// Tap on the the bottom sheet itself to dismiss it
|
||||
tester.tap(tester.findText('BottomSheet'));
|
||||
tester.pump(); // bottom sheet dismiss animation starts
|
||||
tester.pump(new Duration(seconds: 1)); // animation done
|
||||
tester.pump(new Duration(seconds: 1)); // rebuild frame
|
||||
tester.pump(new Duration(seconds: 1)); // last frame of animation (sheet is entirely off-screen, but still present)
|
||||
tester.pump(new Duration(seconds: 1)); // frame after the animation (sheet has been removed)
|
||||
expect(showBottomSheetThenCalled, isTrue);
|
||||
expect(tester.findText('BottomSheet'), isNull);
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/animation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'widget_tester.dart';
|
||||
@ -36,9 +36,9 @@ void main() {
|
||||
key: navigatorKey,
|
||||
onGenerateRoute: (NamedRouteSettings settings) {
|
||||
if (settings.name == '/')
|
||||
return new PageRoute(builder: (_) => new Container(child: new ThePositiveNumbers()));
|
||||
return new MaterialPageRoute(builder: (_) => new Container(child: new ThePositiveNumbers()));
|
||||
else if (settings.name == '/second')
|
||||
return new PageRoute(builder: (_) => new Container(child: new ThePositiveNumbers()));
|
||||
return new MaterialPageRoute(builder: (_) => new Container(child: new ThePositiveNumbers()));
|
||||
return null;
|
||||
}
|
||||
));
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user