Unverified Commit 8f3805f5 authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

Build routes even less. (#62588)

parent 90aad51d
...@@ -175,9 +175,21 @@ class TransitionBuilderPage<T> extends Page<T> { ...@@ -175,9 +175,21 @@ class TransitionBuilderPage<T> extends Page<T> {
final bool barrierDismissible; final bool barrierDismissible;
/// {@macro flutter.widgets.modalRoute.barrierColor} /// {@macro flutter.widgets.modalRoute.barrierColor}
///
/// See also:
///
/// * [barrierDismissible], which controls the behavior of the barrier when
/// tapped.
/// * [ModalBarrier], the widget that implements this feature.
final Color barrierColor; final Color barrierColor;
/// {@macro flutter.widgets.modalRoute.barrierLabel} /// {@macro flutter.widgets.modalRoute.barrierLabel}
///
/// See also:
///
/// * [barrierDismissible], which controls the behavior of the barrier when
/// tapped.
/// * [ModalBarrier], the widget that implements this feature.
final String barrierLabel; final String barrierLabel;
/// {@macro flutter.widgets.modalRoute.maintainState} /// {@macro flutter.widgets.modalRoute.maintainState}
......
...@@ -24,6 +24,7 @@ import 'transitions.dart'; ...@@ -24,6 +24,7 @@ import 'transitions.dart';
// Examples can assume: // Examples can assume:
// dynamic routeObserver; // dynamic routeObserver;
// NavigatorState navigator;
const Color _kTransparent = Color(0x00000000); const Color _kTransparent = Color(0x00000000);
...@@ -193,7 +194,6 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> { ...@@ -193,7 +194,6 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
} }
break; break;
} }
changedInternalState();
} }
@override @override
...@@ -201,16 +201,19 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> { ...@@ -201,16 +201,19 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
assert(!_transitionCompleter.isCompleted, 'Cannot install a $runtimeType after disposing it.'); assert(!_transitionCompleter.isCompleted, 'Cannot install a $runtimeType after disposing it.');
_controller = createAnimationController(); _controller = createAnimationController();
assert(_controller != null, '$runtimeType.createAnimationController() returned null.'); assert(_controller != null, '$runtimeType.createAnimationController() returned null.');
_animation = createAnimation(); _animation = createAnimation()
..addStatusListener(_handleStatusChanged);
assert(_animation != null, '$runtimeType.createAnimation() returned null.'); assert(_animation != null, '$runtimeType.createAnimation() returned null.');
super.install(); super.install();
if (_animation.isCompleted && overlayEntries.isNotEmpty) {
overlayEntries.first.opaque = opaque;
}
} }
@override @override
TickerFuture didPush() { TickerFuture didPush() {
assert(_controller != null, '$runtimeType.didPush called before calling install() or after calling dispose().'); assert(_controller != null, '$runtimeType.didPush called before calling install() or after calling dispose().');
assert(!_transitionCompleter.isCompleted, 'Cannot reuse a $runtimeType after disposing it.'); assert(!_transitionCompleter.isCompleted, 'Cannot reuse a $runtimeType after disposing it.');
_didPushOrReplace();
super.didPush(); super.didPush();
return _controller.forward(); return _controller.forward();
} }
...@@ -219,7 +222,6 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> { ...@@ -219,7 +222,6 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
void didAdd() { void didAdd() {
assert(_controller != null, '$runtimeType.didPush called before calling install() or after calling dispose().'); assert(_controller != null, '$runtimeType.didPush called before calling install() or after calling dispose().');
assert(!_transitionCompleter.isCompleted, 'Cannot reuse a $runtimeType after disposing it.'); assert(!_transitionCompleter.isCompleted, 'Cannot reuse a $runtimeType after disposing it.');
_didPushOrReplace();
super.didAdd(); super.didAdd();
_controller.value = _controller.upperBound; _controller.value = _controller.upperBound;
} }
...@@ -230,19 +232,9 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> { ...@@ -230,19 +232,9 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
assert(!_transitionCompleter.isCompleted, 'Cannot reuse a $runtimeType after disposing it.'); assert(!_transitionCompleter.isCompleted, 'Cannot reuse a $runtimeType after disposing it.');
if (oldRoute is TransitionRoute) if (oldRoute is TransitionRoute)
_controller.value = oldRoute._controller.value; _controller.value = oldRoute._controller.value;
_didPushOrReplace();
super.didReplace(oldRoute); super.didReplace(oldRoute);
} }
void _didPushOrReplace() {
_animation.addStatusListener(_handleStatusChanged);
// If the animation is already completed, _handleStatusChanged will not get
// a chance to set opaqueness of OverlayEntry.
if (_animation.isCompleted && overlayEntries.isNotEmpty) {
overlayEntries.first.opaque = opaque;
}
}
@override @override
bool didPop(T result) { bool didPop(T result) {
assert(_controller != null, '$runtimeType.didPop called before calling install() or after calling dispose().'); assert(_controller != null, '$runtimeType.didPop called before calling install() or after calling dispose().');
...@@ -878,7 +870,7 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T ...@@ -878,7 +870,7 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
/// ``` /// ```
/// ///
/// The given [BuildContext] will be rebuilt if the state of the route changes /// The given [BuildContext] will be rebuilt if the state of the route changes
/// (specifically, if [isCurrent] or [canPop] change value). /// while it is visible (specifically, if [isCurrent] or [canPop] change value).
@optionalTypeArgs @optionalTypeArgs
static ModalRoute<T> of<T extends Object>(BuildContext context) { static ModalRoute<T> of<T extends Object>(BuildContext context) {
final _ModalScopeStatus widget = context.dependOnInheritedWidgetOfExactType<_ModalScopeStatus>(); final _ModalScopeStatus widget = context.dependOnInheritedWidgetOfExactType<_ModalScopeStatus>();
...@@ -958,7 +950,8 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T ...@@ -958,7 +950,8 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
/// is not wrapped in any transition widgets. /// is not wrapped in any transition widgets.
/// ///
/// The [buildTransitions] method, in contrast to [buildPage], is called each /// The [buildTransitions] method, in contrast to [buildPage], is called each
/// time the [Route]'s state changes (e.g. the value of [canPop]). /// time the [Route]'s state changes while it is visible (e.g. if the value of
/// [canPop] changes on the active route).
/// ///
/// The [buildTransitions] method is typically used to define transitions /// The [buildTransitions] method is typically used to define transitions
/// that animate the new topmost route's comings and goings. When the /// that animate the new topmost route's comings and goings. When the
...@@ -1155,17 +1148,31 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T ...@@ -1155,17 +1148,31 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
/// ///
/// While the route is animating into position, the color is animated from /// While the route is animating into position, the color is animated from
/// transparent to the specified color. /// transparent to the specified color.
/// {@endtemplate}
/// ///
/// If this getter would ever start returning a different color, the /// If this getter would ever start returning a different color, the
/// [Route.changedInternalState] should be invoked so that the change can take /// [Route.changedInternalState] should be invoked so that the change can take
/// effect. /// effect.
/// ///
/// {@tool snippet}
///
/// It is safe to use `navigator.context` here. For example, to make
/// the barrier color use the theme's background color, one could say:
///
/// ```dart
/// Color get barrierColor => Theme.of(navigator.context).backgroundColor;
/// ```
///
/// The [Navigator] causes the [ModalRoute]'s modal barrier overlay entry
/// to rebuild any time its dependencies change.
///
/// {@end-tool}
///
/// See also: /// See also:
/// ///
/// * [barrierDismissible], which controls the behavior of the barrier when /// * [barrierDismissible], which controls the behavior of the barrier when
/// tapped. /// tapped.
/// * [ModalBarrier], the widget that implements this feature. /// * [ModalBarrier], the widget that implements this feature.
/// {@endtemplate}
Color get barrierColor; Color get barrierColor;
/// {@template flutter.widgets.modalRoute.barrierLabel} /// {@template flutter.widgets.modalRoute.barrierLabel}
...@@ -1180,6 +1187,7 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T ...@@ -1180,6 +1187,7 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
/// ///
/// For example, when a dialog is on the screen, the page below the dialog is /// For example, when a dialog is on the screen, the page below the dialog is
/// usually darkened by the modal barrier. /// usually darkened by the modal barrier.
/// {@endtemplate}
/// ///
/// If this getter would ever start returning a different label, the /// If this getter would ever start returning a different label, the
/// [Route.changedInternalState] should be invoked so that the change can take /// [Route.changedInternalState] should be invoked so that the change can take
...@@ -1190,7 +1198,6 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T ...@@ -1190,7 +1198,6 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
/// * [barrierDismissible], which controls the behavior of the barrier when /// * [barrierDismissible], which controls the behavior of the barrier when
/// tapped. /// tapped.
/// * [ModalBarrier], the widget that implements this feature. /// * [ModalBarrier], the widget that implements this feature.
/// {@endtemplate}
String get barrierLabel; String get barrierLabel;
/// The curve that is used for animating the modal barrier in and out. /// The curve that is used for animating the modal barrier in and out.
...@@ -1247,6 +1254,8 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T ...@@ -1247,6 +1254,8 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
/// ///
/// The modal barrier, if any, is not rendered if [offstage] is true (see /// The modal barrier, if any, is not rendered if [offstage] is true (see
/// [barrierColor]). /// [barrierColor]).
///
/// Whenever this changes value, [changedInternalState] is called.
bool get offstage => _offstage; bool get offstage => _offstage;
bool _offstage = false; bool _offstage = false;
set offstage(bool value) { set offstage(bool value) {
...@@ -1257,6 +1266,7 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T ...@@ -1257,6 +1266,7 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
}); });
_animationProxy.parent = _offstage ? kAlwaysCompleteAnimation : super.animation; _animationProxy.parent = _offstage ? kAlwaysCompleteAnimation : super.animation;
_secondaryAnimationProxy.parent = _offstage ? kAlwaysDismissedAnimation : super.secondaryAnimation; _secondaryAnimationProxy.parent = _offstage ? kAlwaysDismissedAnimation : super.secondaryAnimation;
changedInternalState();
} }
/// The build context for the subtree containing the primary content of this route. /// The build context for the subtree containing the primary content of this route.
...@@ -1423,8 +1433,9 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T ...@@ -1423,8 +1433,9 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
/// Whether this route can be popped. /// Whether this route can be popped.
/// ///
/// When this changes, the route will rebuild, and any widgets that used /// When this changes, if the route is visible, the route will
/// [ModalRoute.of] will be notified. /// rebuild, and any widgets that used [ModalRoute.of] will be
/// notified.
bool get canPop => !isFirst || willHandlePopInternally; bool get canPop => !isFirst || willHandlePopInternally;
// Internals // Internals
......
...@@ -1208,7 +1208,50 @@ void main() { ...@@ -1208,7 +1208,50 @@ void main() {
expect(log, <String>['building B', 'building C', 'found C', 'building D']); expect(log, <String>['building B', 'building C', 'found C', 'building D']);
key.currentState.pop<void>(); key.currentState.pop<void>();
await tester.pumpAndSettle(const Duration(milliseconds: 10)); await tester.pumpAndSettle(const Duration(milliseconds: 10));
expect(log, <String>['building B', 'building C', 'found C', 'building D', 'building C', 'found C']); expect(log, <String>['building B', 'building C', 'found C', 'building D']);
});
testWidgets('Routes don\'t rebuild just because their animations ended', (WidgetTester tester) async {
final GlobalKey<NavigatorState> key = GlobalKey<NavigatorState>();
final List<String> log = <String>[];
Route<dynamic> nextRoute = PageRouteBuilder<int>(
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
log.add('building page 1 - ${ModalRoute.of(context).canPop}');
return const Placeholder();
},
);
await tester.pumpWidget(MaterialApp(
navigatorKey: key,
onGenerateRoute: (RouteSettings settings) {
assert(nextRoute != null);
final Route<dynamic> result = nextRoute;
nextRoute = null;
return result;
},
));
expect(log, <String>['building page 1 - false']);
key.currentState.pushReplacement(PageRouteBuilder<int>(
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
log.add('building page 2 - ${ModalRoute.of(context).canPop}');
return const Placeholder();
},
));
expect(log, <String>['building page 1 - false']);
await tester.pump();
expect(log, <String>['building page 1 - false', 'building page 2 - false']);
await tester.pump(const Duration(milliseconds: 150));
expect(log, <String>['building page 1 - false', 'building page 2 - false']);
key.currentState.pushReplacement(PageRouteBuilder<int>(
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
log.add('building page 3 - ${ModalRoute.of(context).canPop}');
return const Placeholder();
},
));
expect(log, <String>['building page 1 - false', 'building page 2 - false']);
await tester.pump();
expect(log, <String>['building page 1 - false', 'building page 2 - false', 'building page 3 - false']);
await tester.pump(const Duration(milliseconds: 200));
expect(log, <String>['building page 1 - false', 'building page 2 - false', 'building page 3 - false']);
}); });
testWidgets('route semantics', (WidgetTester tester) async { testWidgets('route semantics', (WidgetTester tester) async {
......
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.8
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
class TestPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Test',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
@override
State<StatefulWidget> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
void _presentModalPage() {
Navigator.of(context).push(PageRouteBuilder<void>(
transitionDuration: const Duration(milliseconds: 300),
barrierColor: Colors.black54,
opaque: false,
pageBuilder: (BuildContext context, _, __) {
return ModalPage();
},
));
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: const Center(
child: Text('Test Home'),
),
floatingActionButton: FloatingActionButton(
onPressed: _presentModalPage,
child: const Icon(Icons.add),
),
);
}
}
class ModalPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Material(
type: MaterialType.transparency,
child: SafeArea(
child: Stack(
children: <Widget>[
InkWell(
highlightColor: Colors.transparent,
splashColor: Colors.transparent,
onTap: () {
Navigator.of(context).pop();
},
child: const SizedBox.expand(),
),
Align(
alignment: Alignment.bottomCenter,
child: Container(
height: 150,
color: Colors.teal,
),
),
],
),
),
);
}
}
void main() {
testWidgets('Barriers show when using PageRouteBuilder', (WidgetTester tester) async {
await tester.pumpWidget(TestPage());
await tester.tap(find.byType(FloatingActionButton));
await tester.pumpAndSettle();
await expectLater(
find.byType(TestPage),
matchesGoldenFile('page_route_builder.barrier.png'),
);
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment