mirror of
https://github.com/flutter/flutter
synced 2024-10-14 04:02:56 +00:00
buildForwardTransition()
For those times when you want to do something as you move away from a route into the next one, as well as when you move into it from the previous one.
This commit is contained in:
parent
9d86ac5e35
commit
bc5307f5af
|
@ -8,6 +8,7 @@ import 'dart:async';
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
import 'dart:ui' as ui;
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
|
import 'package:flutter/animation.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/painting.dart';
|
import 'package:flutter/painting.dart';
|
||||||
|
|
|
@ -146,13 +146,22 @@ class StockHomeState extends State<StockHome> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildToolBar() {
|
Widget buildToolBar() {
|
||||||
|
PageRoute page = ModalRoute.of(context);
|
||||||
return new ToolBar(
|
return new ToolBar(
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
left: new IconButton(
|
left: new IconButton(
|
||||||
icon: "navigation/menu",
|
icon: "navigation/menu",
|
||||||
onPressed: _showDrawer
|
onPressed: _showDrawer
|
||||||
),
|
),
|
||||||
center: new Text('Stocks'),
|
center: new FadeTransition(
|
||||||
|
opacity: new AnimatedValue<double>(
|
||||||
|
1.0,
|
||||||
|
end: 0.0,
|
||||||
|
curve: const Interval(0.0, 0.5)
|
||||||
|
),
|
||||||
|
performance: page.forwardPerformance,
|
||||||
|
child: new Text('Stocks')
|
||||||
|
),
|
||||||
right: <Widget>[
|
right: <Widget>[
|
||||||
new IconButton(
|
new IconButton(
|
||||||
icon: "action/search",
|
icon: "action/search",
|
||||||
|
|
|
@ -53,6 +53,7 @@ class StockSymbolPage extends StatelessComponent {
|
||||||
final Stock stock;
|
final Stock stock;
|
||||||
|
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
PageRoute page = ModalRoute.of(context);
|
||||||
return new Scaffold(
|
return new Scaffold(
|
||||||
toolBar: new ToolBar(
|
toolBar: new ToolBar(
|
||||||
left: new IconButton(
|
left: new IconButton(
|
||||||
|
@ -61,7 +62,15 @@ class StockSymbolPage extends StatelessComponent {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
center: new Text(stock.name)
|
center: new FadeTransition(
|
||||||
|
opacity: new AnimatedValue<double>(
|
||||||
|
0.0,
|
||||||
|
end: 1.0,
|
||||||
|
curve: const Interval(0.5, 1.0)
|
||||||
|
),
|
||||||
|
performance: page.performance,
|
||||||
|
child: new Text(stock.name)
|
||||||
|
)
|
||||||
),
|
),
|
||||||
body: new Block(<Widget>[
|
body: new Block(<Widget>[
|
||||||
new Container(
|
new Container(
|
||||||
|
|
|
@ -45,6 +45,7 @@ class Interval implements Curve {
|
||||||
assert(start <= 1.0);
|
assert(start <= 1.0);
|
||||||
assert(end >= 0.0);
|
assert(end >= 0.0);
|
||||||
assert(end <= 1.0);
|
assert(end <= 1.0);
|
||||||
|
assert(end >= start);
|
||||||
t = ((t - start) / (end - start)).clamp(0.0, 1.0);
|
t = ((t - start) / (end - start)).clamp(0.0, 1.0);
|
||||||
if (t == 0.0 || t == 1.0)
|
if (t == 0.0 || t == 1.0)
|
||||||
return t;
|
return t;
|
||||||
|
|
|
@ -109,12 +109,10 @@ class ReversePerformance extends PerformanceView {
|
||||||
|
|
||||||
final List<PerformanceStatusListener> _statusListeners = new List<PerformanceStatusListener>();
|
final List<PerformanceStatusListener> _statusListeners = new List<PerformanceStatusListener>();
|
||||||
|
|
||||||
/// Calls listener every time the status of this performance changes
|
|
||||||
void addStatusListener(PerformanceStatusListener listener) {
|
void addStatusListener(PerformanceStatusListener listener) {
|
||||||
_statusListeners.add(listener);
|
_statusListeners.add(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stops calling the listener every time the status of this performance changes
|
|
||||||
void removeStatusListener(PerformanceStatusListener listener) {
|
void removeStatusListener(PerformanceStatusListener listener) {
|
||||||
_statusListeners.remove(listener);
|
_statusListeners.remove(listener);
|
||||||
}
|
}
|
||||||
|
@ -148,6 +146,217 @@ class ReversePerformance extends PerformanceView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum _TrainHoppingMode { minimize, maximize }
|
||||||
|
|
||||||
|
/// This performance starts by proxying one performance, but can be given a
|
||||||
|
/// second performance. When their times cross (either because the second is
|
||||||
|
/// going in the opposite direction, or because the one overtakes the other),
|
||||||
|
/// the performance hops over to proxying the second performance, and the second
|
||||||
|
/// performance becomes the new "first" performance.
|
||||||
|
class TrainHoppingPerformance extends PerformanceView {
|
||||||
|
TrainHoppingPerformance(this._currentTrain, this._nextTrain, { this.onSwitchedTrain }) {
|
||||||
|
assert(_currentTrain != null);
|
||||||
|
if (_nextTrain != null) {
|
||||||
|
|
||||||
|
if (_currentTrain.progress > _nextTrain.progress) {
|
||||||
|
_mode = _TrainHoppingMode.maximize;
|
||||||
|
} else {
|
||||||
|
_mode = _TrainHoppingMode.minimize;
|
||||||
|
if (_currentTrain.progress == _nextTrain.progress) {
|
||||||
|
_currentTrain = _nextTrain;
|
||||||
|
_nextTrain = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_currentTrain.addStatusListener(_statusChangeHandler);
|
||||||
|
_currentTrain.addListener(_valueChangeHandler);
|
||||||
|
if (_nextTrain != null)
|
||||||
|
_nextTrain.addListener(_valueChangeHandler);
|
||||||
|
assert(_mode != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
PerformanceView get currentTrain => _currentTrain;
|
||||||
|
PerformanceView _currentTrain;
|
||||||
|
PerformanceView _nextTrain;
|
||||||
|
_TrainHoppingMode _mode;
|
||||||
|
|
||||||
|
VoidCallback onSwitchedTrain;
|
||||||
|
|
||||||
|
void updateVariable(Animatable variable) {
|
||||||
|
assert(_currentTrain != null);
|
||||||
|
variable.setProgress(progress, curveDirection);
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<VoidCallback> _listeners = new List<VoidCallback>();
|
||||||
|
|
||||||
|
void addListener(VoidCallback listener) {
|
||||||
|
assert(_currentTrain != null);
|
||||||
|
_listeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeListener(VoidCallback listener) {
|
||||||
|
assert(_currentTrain != null);
|
||||||
|
_listeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<PerformanceStatusListener> _statusListeners = new List<PerformanceStatusListener>();
|
||||||
|
|
||||||
|
void addStatusListener(PerformanceStatusListener listener) {
|
||||||
|
assert(_currentTrain != null);
|
||||||
|
_statusListeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeStatusListener(PerformanceStatusListener listener) {
|
||||||
|
assert(_currentTrain != null);
|
||||||
|
_statusListeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
PerformanceStatus _lastStatus;
|
||||||
|
void _statusChangeHandler(PerformanceStatus status) {
|
||||||
|
assert(_currentTrain != null);
|
||||||
|
if (status != _lastStatus) {
|
||||||
|
List<PerformanceStatusListener> localListeners = new List<PerformanceStatusListener>.from(_statusListeners);
|
||||||
|
for (PerformanceStatusListener listener in localListeners)
|
||||||
|
listener(status);
|
||||||
|
_lastStatus = status;
|
||||||
|
}
|
||||||
|
assert(_lastStatus != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
PerformanceStatus get status => _currentTrain.status;
|
||||||
|
AnimationDirection get direction => _currentTrain.direction;
|
||||||
|
AnimationDirection get curveDirection => _currentTrain.curveDirection;
|
||||||
|
|
||||||
|
double _lastProgress;
|
||||||
|
void _valueChangeHandler() {
|
||||||
|
assert(_currentTrain != null);
|
||||||
|
bool hop = false;
|
||||||
|
if (_nextTrain != null) {
|
||||||
|
switch (_mode) {
|
||||||
|
case _TrainHoppingMode.minimize:
|
||||||
|
hop = _nextTrain.progress <= _currentTrain.progress;
|
||||||
|
break;
|
||||||
|
case _TrainHoppingMode.maximize:
|
||||||
|
hop = _nextTrain.progress >= _currentTrain.progress;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (hop) {
|
||||||
|
_currentTrain.removeStatusListener(_statusChangeHandler);
|
||||||
|
_currentTrain.removeListener(_valueChangeHandler);
|
||||||
|
_currentTrain = _nextTrain;
|
||||||
|
_nextTrain.addListener(_valueChangeHandler);
|
||||||
|
_statusChangeHandler(_nextTrain.status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
double newProgress = progress;
|
||||||
|
if (newProgress != _lastProgress) {
|
||||||
|
List<VoidCallback> localListeners = new List<VoidCallback>.from(_listeners);
|
||||||
|
for (VoidCallback listener in localListeners)
|
||||||
|
listener();
|
||||||
|
_lastProgress = newProgress;
|
||||||
|
}
|
||||||
|
assert(_lastProgress != null);
|
||||||
|
if (hop && onSwitchedTrain != null)
|
||||||
|
onSwitchedTrain();
|
||||||
|
}
|
||||||
|
|
||||||
|
double get progress => _currentTrain.progress;
|
||||||
|
|
||||||
|
/// Frees all the resources used by this performance.
|
||||||
|
/// After this is called, this object is no longer usable.
|
||||||
|
void dispose() {
|
||||||
|
assert(_currentTrain != null);
|
||||||
|
_currentTrain.removeStatusListener(_statusChangeHandler);
|
||||||
|
_currentTrain.removeListener(_valueChangeHandler);
|
||||||
|
_currentTrain = null;
|
||||||
|
if (_nextTrain != null) {
|
||||||
|
_nextTrain.removeListener(_valueChangeHandler);
|
||||||
|
_nextTrain = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ProxyPerformance extends PerformanceView {
|
||||||
|
ProxyPerformance([PerformanceView performance]) {
|
||||||
|
masterPerformance = performance;
|
||||||
|
}
|
||||||
|
|
||||||
|
PerformanceView get masterPerformance => _masterPerformance;
|
||||||
|
PerformanceView _masterPerformance;
|
||||||
|
void set masterPerformance(PerformanceView value) {
|
||||||
|
if (value == _masterPerformance)
|
||||||
|
return;
|
||||||
|
if (_masterPerformance != null) {
|
||||||
|
_masterPerformance.removeStatusListener(_statusChangeHandler);
|
||||||
|
_masterPerformance.removeListener(_valueChangeHandler);
|
||||||
|
}
|
||||||
|
_masterPerformance = value;
|
||||||
|
if (_masterPerformance != null) {
|
||||||
|
_masterPerformance.addListener(_valueChangeHandler);
|
||||||
|
_masterPerformance.addStatusListener(_statusChangeHandler);
|
||||||
|
_valueChangeHandler();
|
||||||
|
_statusChangeHandler(_masterPerformance.status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateVariable(Animatable variable) {
|
||||||
|
variable.setProgress(progress, curveDirection);
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<VoidCallback> _listeners = new List<VoidCallback>();
|
||||||
|
|
||||||
|
void addListener(VoidCallback listener) {
|
||||||
|
_listeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeListener(VoidCallback listener) {
|
||||||
|
_listeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<PerformanceStatusListener> _statusListeners = new List<PerformanceStatusListener>();
|
||||||
|
|
||||||
|
void addStatusListener(PerformanceStatusListener listener) {
|
||||||
|
_statusListeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeStatusListener(PerformanceStatusListener listener) {
|
||||||
|
_statusListeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
PerformanceStatus _status = PerformanceStatus.dismissed;
|
||||||
|
AnimationDirection _direction = AnimationDirection.forward;
|
||||||
|
AnimationDirection _curveDirection = AnimationDirection.forward;
|
||||||
|
void _statusChangeHandler(PerformanceStatus status) {
|
||||||
|
assert(_masterPerformance != null);
|
||||||
|
if (status != _status) {
|
||||||
|
_status = status;
|
||||||
|
_direction = _masterPerformance.direction;
|
||||||
|
List<PerformanceStatusListener> localListeners = new List<PerformanceStatusListener>.from(_statusListeners);
|
||||||
|
for (PerformanceStatusListener listener in localListeners)
|
||||||
|
listener(status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PerformanceStatus get status => _status;
|
||||||
|
AnimationDirection get direction => _direction;
|
||||||
|
AnimationDirection get curveDirection => _curveDirection;
|
||||||
|
|
||||||
|
double _progress = 0.0;
|
||||||
|
void _valueChangeHandler() {
|
||||||
|
assert(_masterPerformance != null);
|
||||||
|
double newProgress = _masterPerformance.progress;
|
||||||
|
if (newProgress != _progress) {
|
||||||
|
_progress = newProgress;
|
||||||
|
_curveDirection = _masterPerformance.curveDirection;
|
||||||
|
List<VoidCallback> localListeners = new List<VoidCallback>.from(_listeners);
|
||||||
|
for (VoidCallback listener in localListeners)
|
||||||
|
listener();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double get progress => _progress;
|
||||||
|
}
|
||||||
|
|
||||||
class _RepeatingSimulation extends Simulation {
|
class _RepeatingSimulation extends Simulation {
|
||||||
_RepeatingSimulation(this.min, this.max, Duration period)
|
_RepeatingSimulation(this.min, this.max, Duration period)
|
||||||
: _periodInSeconds = period.inMicroseconds.toDouble() / Duration.MICROSECONDS_PER_SECOND {
|
: _periodInSeconds = period.inMicroseconds.toDouble() / Duration.MICROSECONDS_PER_SECOND {
|
||||||
|
@ -280,12 +489,10 @@ class Performance extends PerformanceView {
|
||||||
|
|
||||||
final List<VoidCallback> _listeners = new List<VoidCallback>();
|
final List<VoidCallback> _listeners = new List<VoidCallback>();
|
||||||
|
|
||||||
/// Calls the listener every time the progress of this performance changes
|
|
||||||
void addListener(VoidCallback listener) {
|
void addListener(VoidCallback listener) {
|
||||||
_listeners.add(listener);
|
_listeners.add(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stop calling the listener every time the progress of this performance changes
|
|
||||||
void removeListener(VoidCallback listener) {
|
void removeListener(VoidCallback listener) {
|
||||||
_listeners.remove(listener);
|
_listeners.remove(listener);
|
||||||
}
|
}
|
||||||
|
@ -298,12 +505,10 @@ class Performance extends PerformanceView {
|
||||||
|
|
||||||
final List<PerformanceStatusListener> _statusListeners = new List<PerformanceStatusListener>();
|
final List<PerformanceStatusListener> _statusListeners = new List<PerformanceStatusListener>();
|
||||||
|
|
||||||
/// Calls listener every time the status of this performance changes
|
|
||||||
void addStatusListener(PerformanceStatusListener listener) {
|
void addStatusListener(PerformanceStatusListener listener) {
|
||||||
_statusListeners.add(listener);
|
_statusListeners.add(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stops calling the listener every time the status of this performance changes
|
|
||||||
void removeStatusListener(PerformanceStatusListener listener) {
|
void removeStatusListener(PerformanceStatusListener listener) {
|
||||||
_statusListeners.remove(listener);
|
_statusListeners.remove(listener);
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,8 @@ class _MaterialPageTransition extends TransitionWithChild {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Duration kMaterialPageRouteTransitionDuration = const Duration(milliseconds: 150);
|
||||||
|
|
||||||
class MaterialPageRoute<T> extends PageRoute<T> {
|
class MaterialPageRoute<T> extends PageRoute<T> {
|
||||||
MaterialPageRoute({
|
MaterialPageRoute({
|
||||||
this.builder,
|
this.builder,
|
||||||
|
@ -49,7 +51,7 @@ class MaterialPageRoute<T> extends PageRoute<T> {
|
||||||
|
|
||||||
final WidgetBuilder builder;
|
final WidgetBuilder builder;
|
||||||
|
|
||||||
Duration get transitionDuration => const Duration(milliseconds: 150);
|
Duration get transitionDuration => kMaterialPageRouteTransitionDuration;
|
||||||
bool get barrierDismissable => false;
|
bool get barrierDismissable => false;
|
||||||
Color get barrierColor => Colors.black54;
|
Color get barrierColor => Colors.black54;
|
||||||
|
|
||||||
|
|
|
@ -72,6 +72,9 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
|
||||||
/// popped.
|
/// popped.
|
||||||
Future<T> get popped => _popCompleter?.future;
|
Future<T> get popped => _popCompleter?.future;
|
||||||
final Completer<T> _popCompleter;
|
final Completer<T> _popCompleter;
|
||||||
|
|
||||||
|
/// This future completes only once the transition itself has finished, after
|
||||||
|
/// the overlay entries have been removed from the navigator's overlay.
|
||||||
Future<T> get completed => _transitionCompleter?.future;
|
Future<T> get completed => _transitionCompleter?.future;
|
||||||
final Completer<T> _transitionCompleter;
|
final Completer<T> _transitionCompleter;
|
||||||
|
|
||||||
|
@ -143,6 +146,52 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
final ProxyPerformance forwardPerformance = new ProxyPerformance();
|
||||||
|
|
||||||
|
void didPushNext(Route nextRoute) {
|
||||||
|
if (nextRoute is TransitionRoute) {
|
||||||
|
PerformanceView current = forwardPerformance.masterPerformance;
|
||||||
|
if (current != null) {
|
||||||
|
if (current is TrainHoppingPerformance) {
|
||||||
|
TrainHoppingPerformance newPerformance;
|
||||||
|
newPerformance = new TrainHoppingPerformance(
|
||||||
|
current.currentTrain,
|
||||||
|
nextRoute.performance,
|
||||||
|
onSwitchedTrain: () {
|
||||||
|
assert(forwardPerformance.masterPerformance == newPerformance);
|
||||||
|
assert(newPerformance.currentTrain == nextRoute.performance);
|
||||||
|
forwardPerformance.masterPerformance = newPerformance.currentTrain;
|
||||||
|
newPerformance.dispose();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
forwardPerformance.masterPerformance = newPerformance;
|
||||||
|
current.dispose();
|
||||||
|
} else {
|
||||||
|
forwardPerformance.masterPerformance = new TrainHoppingPerformance(current, nextRoute.performance);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
forwardPerformance.masterPerformance = nextRoute.performance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.didPushNext(nextRoute);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget wrapTransition(BuildContext context, Widget child) {
|
||||||
|
return buildForwardTransition(
|
||||||
|
context,
|
||||||
|
forwardPerformance,
|
||||||
|
buildTransition(
|
||||||
|
context,
|
||||||
|
performance,
|
||||||
|
child
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildTransition(BuildContext context, PerformanceView performance, Widget child) => child;
|
||||||
|
Widget buildForwardTransition(BuildContext context, PerformanceView performance, Widget child) => child;
|
||||||
|
|
||||||
String get debugLabel => '$runtimeType';
|
String get debugLabel => '$runtimeType';
|
||||||
String toString() => '$runtimeType(performance: $_performance)';
|
String toString() => '$runtimeType(performance: $_performance)';
|
||||||
}
|
}
|
||||||
|
@ -246,7 +295,7 @@ class _ModalScope extends StatusTransitionComponent {
|
||||||
key: new GlobalObjectKey(route),
|
key: new GlobalObjectKey(route),
|
||||||
child: new IgnorePointer(
|
child: new IgnorePointer(
|
||||||
ignoring: performance.status == PerformanceStatus.reverse,
|
ignoring: performance.status == PerformanceStatus.reverse,
|
||||||
child: route.buildTransition(context, performance, contents)
|
child: route.wrapTransition(context, contents)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -354,7 +403,7 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
|
||||||
performance: performance,
|
performance: performance,
|
||||||
current: isCurrent,
|
current: isCurrent,
|
||||||
route: this
|
route: this
|
||||||
// calls buildTransition() and buildPage(), defined above
|
// calls buildTransition()/buildForwardTransition() and buildPage(), defined above
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,11 @@ abstract class TransitionComponent extends StatefulComponent {
|
||||||
Widget build(BuildContext context);
|
Widget build(BuildContext context);
|
||||||
|
|
||||||
_TransitionState createState() => new _TransitionState();
|
_TransitionState createState() => new _TransitionState();
|
||||||
|
|
||||||
|
void debugFillDescription(List<String> description) {
|
||||||
|
super.debugFillDescription(description);
|
||||||
|
description.add('performance: $performance');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _TransitionState extends State<TransitionComponent> {
|
class _TransitionState extends State<TransitionComponent> {
|
||||||
|
|
165
packages/unit/test/widget/page_forward_transitions_test.dart
Normal file
165
packages/unit/test/widget/page_forward_transitions_test.dart
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
// 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_test/flutter_test.dart';
|
||||||
|
import 'package:flutter/animation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
import 'test_widgets.dart';
|
||||||
|
|
||||||
|
class TestTransition extends TransitionComponent {
|
||||||
|
TestTransition({
|
||||||
|
Key key,
|
||||||
|
this.childFirstHalf,
|
||||||
|
this.childSecondHalf,
|
||||||
|
PerformanceView performance
|
||||||
|
}) : super(key: key, performance: performance);
|
||||||
|
|
||||||
|
final Widget childFirstHalf;
|
||||||
|
final Widget childSecondHalf;
|
||||||
|
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (performance.progress >= 0.5)
|
||||||
|
return childSecondHalf;
|
||||||
|
return childFirstHalf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
final Duration kTwoTenthsOfTheTransitionDuration = kMaterialPageRouteTransitionDuration * 0.2;
|
||||||
|
final Duration kFourTenthsOfTheTransitionDuration = kMaterialPageRouteTransitionDuration * 0.4;
|
||||||
|
|
||||||
|
test('Check onstage/offstage handling around transitions', () {
|
||||||
|
testWidgets((WidgetTester tester) {
|
||||||
|
|
||||||
|
GlobalKey insideKey = new GlobalKey();
|
||||||
|
|
||||||
|
String state() {
|
||||||
|
String result = '';
|
||||||
|
if (tester.findText('A') != null)
|
||||||
|
result += 'A';
|
||||||
|
if (tester.findText('B') != null)
|
||||||
|
result += 'B';
|
||||||
|
if (tester.findText('C') != null)
|
||||||
|
result += 'C';
|
||||||
|
if (tester.findText('D') != null)
|
||||||
|
result += 'D';
|
||||||
|
if (tester.findText('E') != null)
|
||||||
|
result += 'E';
|
||||||
|
if (tester.findText('F') != null)
|
||||||
|
result += 'F';
|
||||||
|
if (tester.findText('G') != null)
|
||||||
|
result += 'G';
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
tester.pumpWidget(
|
||||||
|
new MaterialApp(
|
||||||
|
routes: <String, RouteBuilder>{
|
||||||
|
'/': (RouteArguments args) {
|
||||||
|
return new Builder(
|
||||||
|
key: insideKey,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
PageRoute route = ModalRoute.of(context);
|
||||||
|
return new Column([
|
||||||
|
new TestTransition(
|
||||||
|
childFirstHalf: new Text('A'),
|
||||||
|
childSecondHalf: new Text('B'),
|
||||||
|
performance: route.performance
|
||||||
|
),
|
||||||
|
new TestTransition(
|
||||||
|
childFirstHalf: new Text('C'),
|
||||||
|
childSecondHalf: new Text('D'),
|
||||||
|
performance: route.forwardPerformance
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
'/2': (RouteArguments args) => new Text('E'),
|
||||||
|
'/3': (RouteArguments args) => new Text('F'),
|
||||||
|
'/4': (RouteArguments args) => new Text('G'),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO(ianh): Remove the first part of this test once the first page doesn't animate in
|
||||||
|
|
||||||
|
NavigatorState navigator = insideKey.currentContext.ancestorStateOfType(NavigatorState);
|
||||||
|
|
||||||
|
expect(state(), equals('AC')); // transition ->1 is at 0.0
|
||||||
|
|
||||||
|
tester.pump(kFourTenthsOfTheTransitionDuration);
|
||||||
|
expect(state(), equals('AC')); // transition ->1 is at 0.4
|
||||||
|
|
||||||
|
tester.pump(kFourTenthsOfTheTransitionDuration);
|
||||||
|
expect(state(), equals('BC')); // transition ->1 is at 0.8
|
||||||
|
|
||||||
|
tester.pump(kFourTenthsOfTheTransitionDuration);
|
||||||
|
expect(state(), equals('BC')); // transition ->1 is at 1.0
|
||||||
|
|
||||||
|
|
||||||
|
navigator.openTransaction((NavigatorTransaction transaction) => transaction.pushNamed('/2'));
|
||||||
|
expect(state(), equals('BC')); // transition 1->2 is not yet built
|
||||||
|
tester.pump();
|
||||||
|
expect(state(), equals('BCE')); // transition 1->2 is at 0.0
|
||||||
|
|
||||||
|
tester.pump(kFourTenthsOfTheTransitionDuration);
|
||||||
|
expect(state(), equals('BCE')); // transition 1->2 is at 0.4
|
||||||
|
|
||||||
|
tester.pump(kFourTenthsOfTheTransitionDuration);
|
||||||
|
expect(state(), equals('BDE')); // transition 1->2 is at 0.8
|
||||||
|
|
||||||
|
tester.pump(kFourTenthsOfTheTransitionDuration);
|
||||||
|
expect(state(), equals('E')); // transition 1->2 is at 1.0
|
||||||
|
|
||||||
|
|
||||||
|
navigator.openTransaction((NavigatorTransaction transaction) => transaction.pop());
|
||||||
|
expect(state(), equals('E')); // transition 1<-2 is at 1.0, just reversed
|
||||||
|
tester.pump();
|
||||||
|
expect(state(), equals('BDE')); // transition 1<-2 is at 1.0
|
||||||
|
|
||||||
|
tester.pump(kFourTenthsOfTheTransitionDuration);
|
||||||
|
expect(state(), equals('BDE')); // transition 1<-2 is at 0.6
|
||||||
|
|
||||||
|
navigator.openTransaction((NavigatorTransaction transaction) => transaction.pushNamed('/3'));
|
||||||
|
expect(state(), equals('BDE')); // transition 1<-2 is at 0.6
|
||||||
|
tester.pump();
|
||||||
|
expect(state(), equals('BDEF')); // transition 1<-2 is at 0.6, 1->3 is at 0.0
|
||||||
|
|
||||||
|
tester.pump(kFourTenthsOfTheTransitionDuration);
|
||||||
|
expect(state(), equals('BCEF')); // transition 1<-2 is at 0.2, 1->3 is at 0.4
|
||||||
|
|
||||||
|
tester.pump(kFourTenthsOfTheTransitionDuration);
|
||||||
|
expect(state(), equals('BDF')); // transition 1<-2 is done, 1->3 is at 0.8
|
||||||
|
|
||||||
|
navigator.openTransaction((NavigatorTransaction transaction) => transaction.pop());
|
||||||
|
expect(state(), equals('BDF')); // transition 1<-3 is at 0.8, just reversed
|
||||||
|
tester.pump();
|
||||||
|
expect(state(), equals('BDF')); // transition 1<-3 is at 0.8
|
||||||
|
|
||||||
|
tester.pump(kTwoTenthsOfTheTransitionDuration); // notice that dT=0.2 here, not 0.4
|
||||||
|
expect(state(), equals('BDF')); // transition 1<-3 is at 0.6
|
||||||
|
|
||||||
|
tester.pump(kFourTenthsOfTheTransitionDuration);
|
||||||
|
expect(state(), equals('BCF')); // transition 1<-3 is at 0.2
|
||||||
|
|
||||||
|
navigator.openTransaction((NavigatorTransaction transaction) => transaction.pushNamed('/4'));
|
||||||
|
expect(state(), equals('BCF')); // transition 1<-3 is at 0.2, 1->4 is not yet built
|
||||||
|
tester.pump();
|
||||||
|
expect(state(), equals('BCFG')); // transition 1<-3 is at 0.2, 1->4 is at 0.0
|
||||||
|
|
||||||
|
tester.pump(kFourTenthsOfTheTransitionDuration);
|
||||||
|
expect(state(), equals('BCG')); // transition 1<-3 is done, 1->4 is at 0.4
|
||||||
|
|
||||||
|
tester.pump(kFourTenthsOfTheTransitionDuration);
|
||||||
|
expect(state(), equals('BDG')); // transition 1->4 is at 0.8
|
||||||
|
|
||||||
|
tester.pump(kFourTenthsOfTheTransitionDuration);
|
||||||
|
expect(state(), equals('G')); // transition 1->4 is done
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
|
@ -6,11 +6,7 @@ import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
class Builder extends StatelessComponent {
|
import 'test_widgets.dart';
|
||||||
Builder({ this.builder });
|
|
||||||
final WidgetBuilder builder;
|
|
||||||
Widget build(BuildContext context) => builder(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
test('SnackBar control test', () {
|
test('SnackBar control test', () {
|
||||||
|
|
|
@ -59,3 +59,10 @@ void flipStatefulComponent(WidgetTester tester) {
|
||||||
FlipComponentState state = stateElement.state;
|
FlipComponentState state = stateElement.state;
|
||||||
state.flip();
|
state.flip();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Builder extends StatelessComponent {
|
||||||
|
Builder({ Key key, this.builder }) : super(key: key);
|
||||||
|
final WidgetBuilder builder;
|
||||||
|
Widget build(BuildContext context) => builder(context);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue