Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
02c21447
Commit
02c21447
authored
Aug 07, 2017
by
Ian Hickson
Committed by
GitHub
Aug 07, 2017
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
CupertinoPageRoute cleanup (#11473)
parent
35496d00
Changes
9
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
508 additions
and
293 deletions
+508
-293
page.dart
packages/flutter/lib/src/cupertino/page.dart
+265
-88
page.dart
packages/flutter/lib/src/material/page.dart
+16
-24
scaffold.dart
packages/flutter/lib/src/material/scaffold.dart
+0
-55
framework.dart
packages/flutter/lib/src/widgets/framework.dart
+23
-1
navigator.dart
packages/flutter/lib/src/widgets/navigator.dart
+27
-83
pages.dart
packages/flutter/lib/src/widgets/pages.dart
+2
-2
routes.dart
packages/flutter/lib/src/widgets/routes.dart
+87
-34
page_test.dart
packages/flutter/test/material/page_test.dart
+67
-0
page_transitions_test.dart
packages/flutter/test/widgets/page_transitions_test.dart
+21
-6
No files found.
packages/flutter/lib/src/cupertino/page.dart
View file @
02c21447
...
...
@@ -3,9 +3,12 @@
// found in the LICENSE file.
import
'package:flutter/foundation.dart'
;
import
'package:flutter/gestures.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/widgets.dart'
;
const
double
_kMinFlingVelocity
=
1.0
;
// screen width per second.
const
double
_kBackGestureWidth
=
20.0
;
const
double
_kMinFlingVelocity
=
1.0
;
// Screen widths per second.
// Fractional offset from offscreen to the right to fully on screen.
final
FractionalOffsetTween
_kRightMiddleTween
=
new
FractionalOffsetTween
(
...
...
@@ -48,11 +51,11 @@ final DecorationTween _kGradientShadowTween = new DecorationTween(
/// A modal route that replaces the entire screen with an iOS transition.
///
/// The page slides in from the right and exits in reverse.
///
The page also shifts
to the left in parallax when another page enters to cover it.
/// The page slides in from the right and exits in reverse.
The page also shifts
/// to the left in parallax when another page enters to cover it.
///
/// The page slides in from the bottom and exits in reverse with no parallax
effect
/// for fullscreen dialogs.
/// The page slides in from the bottom and exits in reverse with no parallax
///
effect
for fullscreen dialogs.
///
/// By default, when a modal route is replaced by another, the previous route
/// remains in memory. To free all the resources when this is not necessary, set
...
...
@@ -60,16 +63,24 @@ final DecorationTween _kGradientShadowTween = new DecorationTween(
///
/// See also:
///
/// * [MaterialPageRoute] for an adaptive [PageRoute] that uses a platform appropriate transition.
/// * [MaterialPageRoute] for an adaptive [PageRoute] that uses a platform
/// appropriate transition.
class
CupertinoPageRoute
<
T
>
extends
PageRoute
<
T
>
{
/// Creates a page route for use in an iOS designed app.
///
/// The [builder], [settings], [maintainState], and [fullscreenDialog]
/// arguments must not be null.
CupertinoPageRoute
({
@required
this
.
builder
,
RouteSettings
settings:
const
RouteSettings
(),
this
.
maintainState
:
true
,
bool
fullscreenDialog:
false
,
this
.
hostRoute
,
})
:
assert
(
builder
!=
null
),
assert
(
opaque
),
assert
(
settings
!=
null
),
assert
(
maintainState
!=
null
),
assert
(
fullscreenDialog
!=
null
),
assert
(
opaque
),
// PageRoute makes it return true.
super
(
settings:
settings
,
fullscreenDialog:
fullscreenDialog
);
/// Builds the primary contents of the route.
...
...
@@ -78,6 +89,16 @@ class CupertinoPageRoute<T> extends PageRoute<T> {
@override
final
bool
maintainState
;
/// The route that owns this one.
///
/// The [MaterialPageRoute] creates a [CupertinoPageRoute] to handle iOS-style
/// navigation. When this happens, the [MaterialPageRoute] is the [hostRoute]
/// of this [CupertinoPageRoute].
///
/// The [hostRoute] is responsible for calling [dispose] on the route. When
/// there is a [hostRoute], the [CupertinoPageRoute] must not be [install]ed.
final
PageRoute
<
T
>
hostRoute
;
@override
Duration
get
transitionDuration
=>
const
Duration
(
milliseconds:
350
);
...
...
@@ -85,8 +106,8 @@ class CupertinoPageRoute<T> extends PageRoute<T> {
Color
get
barrierColor
=>
null
;
@override
bool
canTransitionFrom
(
TransitionRoute
<
dynamic
>
next
Route
)
{
return
nextRoute
is
CupertinoPageRoute
<
dynamic
>
;
bool
canTransitionFrom
(
TransitionRoute
<
dynamic
>
previous
Route
)
{
return
previousRoute
is
CupertinoPageRoute
;
}
@override
...
...
@@ -95,66 +116,111 @@ class CupertinoPageRoute<T> extends PageRoute<T> {
return
nextRoute
is
CupertinoPageRoute
&&
!
nextRoute
.
fullscreenDialog
;
}
@override
void
install
(
OverlayEntry
insertionPoint
)
{
assert
(()
{
if
(
hostRoute
==
null
)
return
true
;
throw
new
FlutterError
(
'Cannot install a subsidiary route (one with a hostRoute).
\n
'
'This route (
$this
) cannot be installed, because it has a host route (
$hostRoute
).'
);
});
super
.
install
(
insertionPoint
);
}
@override
void
dispose
()
{
_backGestureController
?.
dispose
();
// If the route is never installed (i.e. pushed into a Navigator) such as the
// case when [MaterialPageRoute] delegates transition building to [CupertinoPageRoute],
// don't dispose super.
if
(
overlayEntries
.
isNotEmpty
)
super
.
dispose
();
_backGestureController
=
null
;
super
.
dispose
();
}
CupertinoBackGestureController
_backGestureController
;
_
CupertinoBackGestureController
_backGestureController
;
///
Support for dismissing this route with a horizontal swipe
.
///
Whether a pop gesture is currently underway
.
///
///
Swiping will be disabled if the page is a fullscreen dialog or if
///
dismissals can be overriden because a [WillPopCallback] was
///
defined for the route
.
///
This starts returning true when the [startPopGesture] method returns a new
///
[NavigationGestureController]. It returns false if that has not yet
///
occurred or if the most recent such gesture has completed
.
///
/// See also:
///
/// * [hasScopedWillPopCallback], which is true if a `willPop` callback
/// is defined for this route.
@override
NavigationGestureController
startPopGesture
()
{
return
startPopGestureForRoute
(
this
);
}
/// * [popGestureEnabled], which returns whether a pop gesture is appropriate
/// in the first place.
bool
get
popGestureInProgress
=>
_backGestureController
!=
null
;
///
Create a CupertinoBackGestureController using a specific PageRoute
.
///
Whether a pop gesture will be considered acceptable by [startPopGesture]
.
///
/// Used when [MaterialPageRoute] delegates the back gesture to [CupertinoPageRoute]
/// since the [CupertinoPageRoute] is not actually inserted into the Navigator.
NavigationGestureController
startPopGestureForRoute
(
PageRoute
<
T
>
hostRoute
)
{
/// This returns true if the user can edge-swipe to a previous route,
/// otherwise false.
///
/// This will return false if [popGestureInProgress] is true.
///
/// This should only be used between frames, not during build.
bool
get
popGestureEnabled
{
final
PageRoute
<
T
>
route
=
hostRoute
??
this
;
// If there's nothing to go back to, then obviously we don't support
// the back gesture.
if
(
route
.
isFirst
)
return
false
;
// If the route wouldn't actually pop if we popped it, then the gesture
// would be really confusing (or would skip internal routes), so disallow it.
if
(
route
.
willHandlePopInternally
)
return
false
;
// If attempts to dismiss this route might be vetoed such as in a page
// with forms, then do not allow the user to dismiss the route with a swipe.
if
(
hostR
oute
.
hasScopedWillPopCallback
)
return
null
;
if
(
r
oute
.
hasScopedWillPopCallback
)
return
false
;
// Fullscreen dialogs aren't dismissable by back swipe.
if
(
fullscreenDialog
)
return
null
;
if
(
hostRoute
.
controller
.
status
!=
AnimationStatus
.
completed
)
return
null
;
assert
(
_backGestureController
==
null
);
_backGestureController
=
new
CupertinoBackGestureController
(
navigator:
hostRoute
.
navigator
,
controller:
hostRoute
.
controller
,
);
Function
handleBackGestureEnded
;
handleBackGestureEnded
=
(
AnimationStatus
status
)
{
if
(
status
==
AnimationStatus
.
completed
)
{
_backGestureController
?.
dispose
();
_backGestureController
=
null
;
hostRoute
.
controller
.
removeStatusListener
(
handleBackGestureEnded
);
}
};
return
false
;
// If we're in an animation already, we cannot be manually swiped.
if
(
route
.
controller
.
status
!=
AnimationStatus
.
completed
)
return
false
;
// If we're in a gesture already, we cannot start another.
if
(
popGestureInProgress
)
return
false
;
// Looks like a back gesture would be welcome!
return
true
;
}
hostRoute
.
controller
.
addStatusListener
(
handleBackGestureEnded
);
/// Begin dismissing this route from a horizontal swipe, if appropriate.
///
/// Swiping will be disabled if the page is a fullscreen dialog or if
/// dismissals can be overriden because a [WillPopCallback] was
/// defined for the route.
///
/// When this method decides a pop gesture is appropriate, it returns a
/// [CupertinoBackGestureController].
///
/// See also:
///
/// * [hasScopedWillPopCallback], which is true if a `willPop` callback
/// is defined for this route.
/// * [popGestureEnabled], which returns whether a pop gesture is
/// appropriate.
/// * [Route.startPopGesture], which describes the contract that this method
/// must implement.
_CupertinoBackGestureController
_startPopGesture
()
{
assert
(!
popGestureInProgress
);
assert
(
popGestureEnabled
);
final
PageRoute
<
T
>
route
=
hostRoute
??
this
;
_backGestureController
=
new
_CupertinoBackGestureController
(
navigator:
route
.
navigator
,
controller:
route
.
controller
,
onEnded:
_endPopGesture
,
);
return
_backGestureController
;
}
void
_endPopGesture
()
{
// In practice this only gets called if for some reason popping the route
// did not cause this route to get disposed.
_backGestureController
?.
dispose
();
_backGestureController
=
null
;
}
@override
Widget
buildPage
(
BuildContext
context
,
Animation
<
double
>
animation
,
Animation
<
double
>
secondaryAnimation
)
{
final
Widget
result
=
builder
(
context
);
...
...
@@ -172,20 +238,25 @@ class CupertinoPageRoute<T> extends PageRoute<T> {
@override
Widget
buildTransitions
(
BuildContext
context
,
Animation
<
double
>
animation
,
Animation
<
double
>
secondaryAnimation
,
Widget
child
)
{
if
(
fullscreenDialog
)
if
(
fullscreenDialog
)
{
return
new
CupertinoFullscreenDialogTransition
(
animation:
animation
,
child:
child
,
);
else
}
else
{
return
new
CupertinoPageTransition
(
primaryRouteAnimation:
animation
,
secondaryRouteAnimation:
secondaryAnimation
,
child:
child
,
// In the middle of a back gesture drag, let the transition be linear to match finger
// motions.
linearTransition:
_backGestureController
!=
null
,
// In the middle of a back gesture drag, let the transition be linear to
// match finger motions.
linearTransition:
popGestureInProgress
,
child:
new
_CupertinoBackGestureDetector
(
enabledCallback:
()
=>
popGestureEnabled
,
onStartPopGesture:
_startPopGesture
,
child:
child
,
),
);
}
}
@override
...
...
@@ -294,49 +365,145 @@ class CupertinoFullscreenDialogTransition extends StatelessWidget {
}
}
/// This is the widget side of [_CupertinoBackGestureController].
///
/// This widget provides a gesture recognizer which, when it determines the
/// route can be closed with a back gesture, creates the controller and
/// feeds it the input from the gesture recognizer.
class
_CupertinoBackGestureDetector
extends
StatefulWidget
{
const
_CupertinoBackGestureDetector
({
Key
key
,
@required
this
.
enabledCallback
,
@required
this
.
onStartPopGesture
,
@required
this
.
child
,
})
:
assert
(
enabledCallback
!=
null
),
assert
(
onStartPopGesture
!=
null
),
assert
(
child
!=
null
),
super
(
key:
key
);
final
Widget
child
;
final
ValueGetter
<
bool
>
enabledCallback
;
final
ValueGetter
<
_CupertinoBackGestureController
>
onStartPopGesture
;
@override
_CupertinoBackGestureDetectorState
createState
()
=>
new
_CupertinoBackGestureDetectorState
();
}
class
_CupertinoBackGestureDetectorState
extends
State
<
_CupertinoBackGestureDetector
>
{
_CupertinoBackGestureController
_backGestureController
;
HorizontalDragGestureRecognizer
_recognizer
;
@override
void
initState
()
{
super
.
initState
();
_recognizer
=
new
HorizontalDragGestureRecognizer
(
debugOwner:
this
)
..
onStart
=
_handleDragStart
..
onUpdate
=
_handleDragUpdate
..
onEnd
=
_handleDragEnd
..
onCancel
=
_handleDragCancel
;
}
@override
void
dispose
()
{
_recognizer
.
dispose
();
super
.
dispose
();
}
void
_handleDragStart
(
DragStartDetails
details
)
{
assert
(
mounted
);
assert
(
_backGestureController
==
null
);
_backGestureController
=
widget
.
onStartPopGesture
();
}
void
_handleDragUpdate
(
DragUpdateDetails
details
)
{
assert
(
mounted
);
assert
(
_backGestureController
!=
null
);
_backGestureController
.
dragUpdate
(
details
.
primaryDelta
/
context
.
size
.
width
);
}
void
_handleDragEnd
(
DragEndDetails
details
)
{
assert
(
mounted
);
assert
(
_backGestureController
!=
null
);
_backGestureController
.
dragEnd
(
details
.
velocity
.
pixelsPerSecond
.
dx
/
context
.
size
.
width
);
_backGestureController
=
null
;
}
void
_handleDragCancel
()
{
assert
(
mounted
);
// This can be called even if start is not called, paired with the "down" event
// that we don't consider here.
_backGestureController
?.
dragEnd
(
0.0
);
_backGestureController
=
null
;
}
void
_handlePointerDown
(
PointerDownEvent
event
)
{
if
(
widget
.
enabledCallback
())
_recognizer
.
addPointer
(
event
);
}
@override
Widget
build
(
BuildContext
context
)
{
return
new
Stack
(
fit:
StackFit
.
passthrough
,
children:
<
Widget
>[
widget
.
child
,
new
Listener
(
onPointerDown:
_handlePointerDown
,
behavior:
HitTestBehavior
.
translucent
,
child:
new
SizedBox
(
width:
_kBackGestureWidth
),
),
],
);
}
}
/// A controller for an iOS-style back gesture.
///
/// Uses a drag gesture to control the route's transition animation progress.
class
CupertinoBackGestureController
extends
NavigationGestureController
{
/// This is created by a [CupertinoPageRoute] in response from a gesture caught
/// by a [_CupertinoBackGestureDetector] widget, which then also feeds it input
/// from the gesture. It controls the animation controller owned by the route,
/// based on the input provided by the gesture detector.
class
_CupertinoBackGestureController
{
/// Creates a controller for an iOS-style back gesture.
///
/// The [navigator] and [controller] arguments must not be null.
CupertinoBackGestureController
({
@required
NavigatorState
navigator
,
_
CupertinoBackGestureController
({
@required
this
.
navigator
,
@required
this
.
controller
,
})
:
assert
(
controller
!=
null
),
super
(
navigator
);
@required
this
.
onEnded
,
})
:
assert
(
navigator
!=
null
),
assert
(
controller
!=
null
),
assert
(
onEnded
!=
null
)
{
navigator
.
didStartUserGesture
();
}
/// The navigator that this object is controlling.
final
NavigatorState
navigator
;
/// The animation controller that the route uses to drive its transition
/// animation.
final
AnimationController
controller
;
@override
void
dispose
()
{
controller
.
removeStatusListener
(
_handleStatusChanged
);
super
.
dispose
();
}
final
VoidCallback
onEnded
;
@override
bool
_animating
=
false
;
/// The drag gesture has changed by [fractionalDelta]. The total range of the
/// drag should be 0.0 to 1.0.
void
dragUpdate
(
double
delta
)
{
// This assert can be triggered the Scaffold is reparented out of the route
// associated with this gesture controller and continues to feed it events.
// TODO(abarth): Change the ownership of the gesture controller so that the
// object feeding it these events (e.g., the Scaffold) is responsible for
// calling dispose on it as well.
assert
(
controller
!=
null
);
controller
.
value
-=
delta
;
}
@override
bool
dragEnd
(
double
velocity
)
{
// This assert can be triggered the Scaffold is reparented out of the route
// associated with this gesture controller and continues to feed it events.
// TODO(abarth): Change the ownership of the gesture controller so that the
// object feeding it these events (e.g., the Scaffold) is responsible for
// calling dispose on it as well.
assert
(
controller
!=
null
);
/// The drag gesture has ended with a horizontal motion of
/// [fractionalVelocity] as a fraction of screen width per second.
void
dragEnd
(
double
velocity
)
{
// Fling in the appropriate direction.
// AnimationController.fling is guaranteed to
// take at least one frame.
if
(
velocity
.
abs
()
>=
_kMinFlingVelocity
)
{
controller
.
fling
(
velocity:
-
velocity
);
}
else
if
(
controller
.
value
<=
0.5
)
{
...
...
@@ -344,18 +511,28 @@ class CupertinoBackGestureController extends NavigationGestureController {
}
else
{
controller
.
fling
(
velocity:
1.0
);
}
assert
(
controller
.
isAnimating
);
assert
(
controller
.
status
!=
AnimationStatus
.
completed
);
assert
(
controller
.
status
!=
AnimationStatus
.
dismissed
);
// Don't end the gesture until the transition completes.
final
AnimationStatus
status
=
controller
.
status
;
_handleStatusChanged
(
status
);
controller
?.
addStatusListener
(
_handleStatusChanged
);
return
(
status
==
AnimationStatus
.
reverse
||
status
==
AnimationStatus
.
dismissed
);
_animating
=
true
;
controller
.
addStatusListener
(
_handleStatusChanged
);
}
void
_handleStatusChanged
(
AnimationStatus
status
)
{
assert
(
_animating
);
controller
.
removeStatusListener
(
_handleStatusChanged
);
_animating
=
false
;
if
(
status
==
AnimationStatus
.
dismissed
)
navigator
.
pop
();
navigator
.
pop
();
// this will cause the route to get disposed, which will dispose us
onEnded
();
// this will call dispose if popping the route failed to do so
}
void
dispose
()
{
if
(
_animating
)
controller
.
removeStatusListener
(
_handleStatusChanged
);
navigator
.
didStopUserGesture
();
}
}
...
...
packages/flutter/lib/src/material/page.dart
View file @
02c21447
...
...
@@ -72,19 +72,28 @@ class MaterialPageRoute<T> extends PageRoute<T> {
/// Builds the primary contents of the route.
final
WidgetBuilder
builder
;
@override
final
bool
maintainState
;
/// A delegate PageRoute to which iOS themed page operations are delegated to.
/// It's lazily created on first use.
CupertinoPageRoute
<
T
>
_internalCupertinoPageRoute
;
CupertinoPageRoute
<
T
>
get
_cupertinoPageRoute
{
assert
(
_useCupertinoTransitions
);
_internalCupertinoPageRoute
??=
new
CupertinoPageRoute
<
T
>(
builder:
builder
,
// Not used.
fullscreenDialog:
fullscreenDialog
,
hostRoute:
this
,
);
return
_internalCupertinoPageRoute
;
}
CupertinoPageRoute
<
T
>
_internalCupertinoPageRoute
;
@override
final
bool
maintainState
;
/// Whether we should currently be using Cupertino transitions. This is true
/// if the theme says we're on iOS, or if we're in an active gesture.
bool
get
_useCupertinoTransitions
{
return
_internalCupertinoPageRoute
?.
popGestureInProgress
==
true
||
Theme
.
of
(
navigator
.
context
).
platform
==
TargetPlatform
.
iOS
;
}
@override
Duration
get
transitionDuration
=>
const
Duration
(
milliseconds:
300
);
...
...
@@ -93,8 +102,8 @@ class MaterialPageRoute<T> extends PageRoute<T> {
Color
get
barrierColor
=>
null
;
@override
bool
canTransitionFrom
(
TransitionRoute
<
dynamic
>
next
Route
)
{
return
nextRoute
is
MaterialPageRoute
<
dynamic
>
||
nextRoute
is
CupertinoPageRoute
<
dynamic
>
;
bool
canTransitionFrom
(
TransitionRoute
<
dynamic
>
previous
Route
)
{
return
(
previousRoute
is
MaterialPageRoute
||
previousRoute
is
CupertinoPageRoute
)
;
}
@override
...
...
@@ -110,23 +119,6 @@ class MaterialPageRoute<T> extends PageRoute<T> {
super
.
dispose
();
}
/// Support for dismissing this route with a horizontal swipe is enabled
/// for [TargetPlatform.iOS]. If attempts to dismiss this route might be
/// vetoed because a [WillPopCallback] was defined for the route then the
/// platform-specific back gesture is disabled.
///
/// See also:
///
/// * [CupertinoPageRoute] that backs the gesture for iOS.
/// * [hasScopedWillPopCallback], which is true if a `willPop` callback
/// is defined for this route.
@override
NavigationGestureController
startPopGesture
()
{
return
Theme
.
of
(
navigator
.
context
).
platform
==
TargetPlatform
.
iOS
?
_cupertinoPageRoute
.
startPopGestureForRoute
(
this
)
:
null
;
}
@override
Widget
buildPage
(
BuildContext
context
,
Animation
<
double
>
animation
,
Animation
<
double
>
secondaryAnimation
)
{
final
Widget
result
=
builder
(
context
);
...
...
@@ -144,12 +136,12 @@ class MaterialPageRoute<T> extends PageRoute<T> {
@override
Widget
buildTransitions
(
BuildContext
context
,
Animation
<
double
>
animation
,
Animation
<
double
>
secondaryAnimation
,
Widget
child
)
{
if
(
Theme
.
of
(
context
).
platform
==
TargetPlatform
.
iOS
)
{
if
(
_useCupertinoTransitions
)
{
return
_cupertinoPageRoute
.
buildTransitions
(
context
,
animation
,
secondaryAnimation
,
child
);
}
else
{
return
new
_MountainViewPageTransition
(
routeAnimation:
animation
,
child:
child
child:
child
,
);
}
}
...
...
packages/flutter/lib/src/material/scaffold.dart
View file @
02c21447
...
...
@@ -23,8 +23,6 @@ const double _kFloatingActionButtonMargin = 16.0; // TODO(hmuller): should be de
const
Duration
_kFloatingActionButtonSegue
=
const
Duration
(
milliseconds:
200
);
final
Tween
<
double
>
_kFloatingActionButtonTurnTween
=
new
Tween
<
double
>(
begin:
-
0.125
,
end:
0.0
);
const
double
_kBackGestureWidth
=
20.0
;
enum
_ScaffoldSlot
{
body
,
appBar
,
...
...
@@ -717,40 +715,6 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin {
}
}
final
GlobalKey
_backGestureKey
=
new
GlobalKey
();
NavigationGestureController
_backGestureController
;
bool
_shouldHandleBackGesture
()
{
assert
(
mounted
);
return
Theme
.
of
(
context
).
platform
==
TargetPlatform
.
iOS
&&
Navigator
.
canPop
(
context
);
}
void
_handleDragStart
(
DragStartDetails
details
)
{
assert
(
mounted
);
_backGestureController
=
Navigator
.
of
(
context
).
startPopGesture
();
}
void
_handleDragUpdate
(
DragUpdateDetails
details
)
{
assert
(
mounted
);
_backGestureController
?.
dragUpdate
(
details
.
primaryDelta
/
context
.
size
.
width
);
}
void
_handleDragEnd
(
DragEndDetails
details
)
{
assert
(
mounted
);
final
bool
willPop
=
_backGestureController
?.
dragEnd
(
details
.
velocity
.
pixelsPerSecond
.
dx
/
context
.
size
.
width
)
??
false
;
if
(
willPop
)
_currentBottomSheet
?.
close
();
_backGestureController
=
null
;
}
void
_handleDragCancel
()
{
assert
(
mounted
);
final
bool
willPop
=
_backGestureController
?.
dragEnd
(
0.0
)
??
false
;
if
(
willPop
)
_currentBottomSheet
?.
close
();
_backGestureController
=
null
;
}
// INTERNALS
...
...
@@ -887,25 +851,6 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin {
child:
widget
.
drawer
,
)
));
}
else
if
(
_shouldHandleBackGesture
())
{
assert
(!
hasDrawer
);
// Add a gesture for navigating back.
children
.
add
(
new
LayoutId
(
id:
_ScaffoldSlot
.
drawer
,
child:
new
Align
(
alignment:
FractionalOffset
.
centerLeft
,
child:
new
GestureDetector
(
key:
_backGestureKey
,
onHorizontalDragStart:
_handleDragStart
,
onHorizontalDragUpdate:
_handleDragUpdate
,
onHorizontalDragEnd:
_handleDragEnd
,
onHorizontalDragCancel:
_handleDragCancel
,
behavior:
HitTestBehavior
.
translucent
,
excludeFromSemantics:
true
,
child:
new
Container
(
width:
_kBackGestureWidth
)
)
)
));
}
return
new
_ScaffoldScope
(
...
...
packages/flutter/lib/src/widgets/framework.dart
View file @
02c21447
...
...
@@ -3113,6 +3113,8 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
'resulting object.
\n
'
'The size getter was called for the following element:
\n
'
'
$this
\n
'
'The associated render sliver was:
\n
'
'
${renderObject.toStringShallow("\n ")}
'
);
}
if
(
renderObject
is
!
RenderBox
)
{
...
...
@@ -3124,10 +3126,12 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
'and extracting its size manually.
\n
'
'The size getter was called for the following element:
\n
'
'
$this
\n
'
'The associated render object was:
\n
'
'
${renderObject.toStringShallow("\n ")}
'
);
}
final
RenderBox
box
=
renderObject
;
if
(!
box
.
hasSize
||
box
.
debugNeedsLayout
)
{
if
(!
box
.
hasSize
)
{
throw
new
FlutterError
(
'Cannot get size from a render object that has not been through layout.
\n
'
'The size of this render object has not yet been determined because '
...
...
@@ -3137,6 +3141,24 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
'the size and position of the render objects during layout.
\n
'
'The size getter was called for the following element:
\n
'
'
$this
\n
'
'The render object from which the size was to be obtained was:
\n
'
'
${box.toStringShallow("\n ")}
'
);
}
if
(
box
.
debugNeedsLayout
)
{
throw
new
FlutterError
(
'Cannot get size from a render object that has been marked dirty for layout.
\n
'
'The size of this render object is ambiguous because this render object has '
'been modified since it was last laid out, which typically means that the size '
'getter was called too early in the pipeline (e.g., during the build phase) '
'before the framework has determined the size and position of the render '
'objects during layout.
\n
'
'The size getter was called for the following element:
\n
'
'
$this
\n
'
'The render object from which the size was to be obtained was:
\n
'
'
${box.toStringShallow("\n ")}
\n
'
'Consider using debugPrintMarkNeedsLayoutStacks to determine why the render '
'object in question is dirty, if you did not expect this.'
);
}
return
true
;
...
...
packages/flutter/lib/src/widgets/navigator.dart
View file @
02c21447
...
...
@@ -178,29 +178,9 @@ abstract class Route<T> {
@mustCallSuper
@protected
void
dispose
()
{
assert
(()
{
if
(
navigator
==
null
)
{
throw
new
FlutterError
(
'
$runtimeType
.dipose() called more than once.
\n
'
'A given route cannot be disposed more than once.'
);
}
return
true
;
});
_navigator
=
null
;
}
/// If the route's transition can be popped via a user gesture (e.g. the iOS
/// back gesture), this should return a controller object that can be used to
/// control the transition animation's progress. Otherwise, it should return
/// null.
///
/// If attempts to dismiss this route might be vetoed, for example because
/// a [WillPopCallback] was defined for the route, then it may make sense
/// to disable the pop gesture. For example, the iOS back gesture is disabled
/// when [ModalRoute.hasScopedWillPopCallback] is true.
NavigationGestureController
startPopGesture
()
=>
null
;
/// Whether this route is the top-most route on the navigator.
///
/// If this is true, then [isActive] is also true.
...
...
@@ -288,54 +268,16 @@ class NavigatorObserver {
/// The [Navigator] removed `route`.
void
didRemove
(
Route
<
dynamic
>
route
,
Route
<
dynamic
>
previousRoute
)
{
}
/// The [Navigator]
is being controll
ed by a user gesture.
/// The [Navigator]
's routes are being mov
ed by a user gesture.
///
/// Used for the iOS back gesture.
/// For example, this is called when an iOS back gesture starts, and is used
/// to disabled hero animations during such interactions.
void
didStartUserGesture
()
{
}
/// User gesture is no longer controlling the [Navigator].
void
didStopUserGesture
()
{
}
}
/// Interface describing an object returned by the [Route.startPopGesture]
/// method, allowing the route's transition animations to be controlled by a
/// drag or other user gesture.
abstract
class
NavigationGestureController
{
/// Configures the NavigationGestureController and tells the given [Navigator] that
/// a gesture has started.
NavigationGestureController
(
this
.
_navigator
)
:
assert
(
_navigator
!=
null
)
{
// Disable Hero transitions until the gesture is complete.
_navigator
.
didStartUserGesture
();
}
/// The navigator that this object is controlling.
@protected
NavigatorState
get
navigator
=>
_navigator
;
NavigatorState
_navigator
;
/// Release the resources used by this object. The object is no longer usable
/// after this method is called.
///
/// Must be called when the gesture is done.
///
/// Calling this method notifies the navigator that the gesture has completed.
@mustCallSuper
void
dispose
()
{
_navigator
.
didStopUserGesture
();
_navigator
=
null
;
}
/// The drag gesture has changed by [fractionalDelta]. The total range of the
/// drag should be 0.0 to 1.0.
void
dragUpdate
(
double
fractionalDelta
);
/// The drag gesture has ended with a horizontal motion of
/// [fractionalVelocity] as a fraction of screen width per second.
///
/// Returns true if the gesture will complete (i.e. a back gesture will
/// result in a pop).
bool
dragEnd
(
double
fractionalVelocity
);
/// Paired with an earlier call to [didStartUserGesture].
void
didStopUserGesture
()
{
}
}
/// Signature for the [Navigator.popUntil] predicate argument.
...
...
@@ -1326,33 +1268,35 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
return
_history
.
length
>
1
||
_history
[
0
].
willHandlePopInternally
;
}
/// Starts a gesture that results in popping the navigator.
NavigationGestureController
startPopGesture
()
{
if
(
canPop
())
return
_history
.
last
.
startPopGesture
();
return
null
;
}
/// Whether a gesture controlled by a [NavigationGestureController] is currently in progress.
bool
get
userGestureInProgress
=>
_userGestureInProgress
;
// TODO(mpcomplete): remove this bool when we fix
// https://github.com/flutter/flutter/issues/5577
bool
_userGestureInProgress
=
false
;
/// Whether a route is currently being manipulated by the user, e.g.
/// as during an iOS back gesture.
bool
get
userGestureInProgress
=>
_userGesturesInProgress
>
0
;
int
_userGesturesInProgress
=
0
;
/// The navigator is being controlled by a user gesture.
///
/// Used for the iOS back gesture.
/// For example, called when the user beings an iOS back gesture.
///
/// When the gesture finishes, call [didStopUserGesture].
void
didStartUserGesture
()
{
_userGestureInProgress
=
true
;
for
(
NavigatorObserver
observer
in
widget
.
observers
)
observer
.
didStartUserGesture
();
_userGesturesInProgress
+=
1
;
if
(
_userGesturesInProgress
==
1
)
{
for
(
NavigatorObserver
observer
in
widget
.
observers
)
observer
.
didStartUserGesture
();
}
}
/// A user gesture is no longer controlling the navigator.
/// A user gesture completed.
///
/// Notifies the navigator that a gesture regarding which the navigator was
/// previously notified with [didStartUserGesture] has completed.
void
didStopUserGesture
()
{
_userGestureInProgress
=
false
;
for
(
NavigatorObserver
observer
in
widget
.
observers
)
observer
.
didStopUserGesture
();
assert
(
_userGesturesInProgress
>
0
);
_userGesturesInProgress
-=
1
;
if
(
_userGesturesInProgress
==
0
)
{
for
(
NavigatorObserver
observer
in
widget
.
observers
)
observer
.
didStopUserGesture
();
}
}
final
Set
<
int
>
_activePointers
=
new
Set
<
int
>();
...
...
packages/flutter/lib/src/widgets/pages.dart
View file @
02c21447
...
...
@@ -32,10 +32,10 @@ abstract class PageRoute<T> extends ModalRoute<T> {
bool
get
barrierDismissible
=>
false
;
@override
bool
canTransitionTo
(
TransitionRoute
<
dynamic
>
nextRoute
)
=>
nextRoute
is
PageRoute
<
dynamic
>
;
bool
canTransitionTo
(
TransitionRoute
<
dynamic
>
nextRoute
)
=>
nextRoute
is
PageRoute
;
@override
bool
canTransitionFrom
(
TransitionRoute
<
dynamic
>
nextRoute
)
=>
nextRoute
is
PageRoute
<
dynamic
>
;
bool
canTransitionFrom
(
TransitionRoute
<
dynamic
>
previousRoute
)
=>
previousRoute
is
PageRoute
;
@override
AnimationController
createAnimationController
()
{
...
...
packages/flutter/lib/src/widgets/routes.dart
View file @
02c21447
...
...
@@ -105,6 +105,7 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
/// this route from the previous one, and back to the previous route from this
/// one.
AnimationController
createAnimationController
()
{
assert
(!
_transitionCompleter
.
isCompleted
,
'Cannot reuse a
$runtimeType
after disposing it.'
);
final
Duration
duration
=
transitionDuration
;
assert
(
duration
!=
null
&&
duration
>=
Duration
.
ZERO
);
return
new
AnimationController
(
...
...
@@ -118,6 +119,7 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
/// the transition controlled by the animation controller created by
/// [createAnimationController()].
Animation
<
double
>
createAnimation
()
{
assert
(!
_transitionCompleter
.
isCompleted
,
'Cannot reuse a
$runtimeType
after disposing it.'
);
assert
(
_controller
!=
null
);
return
_controller
.
view
;
}
...
...
@@ -157,21 +159,26 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
@override
void
install
(
OverlayEntry
insertionPoint
)
{
assert
(!
_transitionCompleter
.
isCompleted
,
'Cannot install a
$runtimeType
after disposing it.'
);
_controller
=
createAnimationController
();
assert
(
_controller
!=
null
);
assert
(
_controller
!=
null
,
'
$runtimeType
.createAnimationController() returned null.'
);
_animation
=
createAnimation
();
assert
(
_animation
!=
null
);
assert
(
_animation
!=
null
,
'
$runtimeType
.createAnimation() returned null.'
);
super
.
install
(
insertionPoint
);
}
@override
TickerFuture
didPush
()
{
assert
(
_controller
!=
null
,
'
$runtimeType
.didPush called before calling install() or after calling dispose().'
);
assert
(!
_transitionCompleter
.
isCompleted
,
'Cannot reuse a
$runtimeType
after disposing it.'
);
_animation
.
addStatusListener
(
_handleStatusChanged
);
return
_controller
.
forward
();
}
@override
void
didReplace
(
Route
<
dynamic
>
oldRoute
)
{
assert
(
_controller
!=
null
,
'
$runtimeType
.didReplace called before calling install() or after calling dispose().'
);
assert
(!
_transitionCompleter
.
isCompleted
,
'Cannot reuse a
$runtimeType
after disposing it.'
);
if
(
oldRoute
is
TransitionRoute
<
dynamic
>)
_controller
.
value
=
oldRoute
.
_controller
.
value
;
_animation
.
addStatusListener
(
_handleStatusChanged
);
...
...
@@ -180,6 +187,8 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
@override
bool
didPop
(
T
result
)
{
assert
(
_controller
!=
null
,
'
$runtimeType
.didPop called before calling install() or after calling dispose().'
);
assert
(!
_transitionCompleter
.
isCompleted
,
'Cannot reuse a
$runtimeType
after disposing it.'
);
_result
=
result
;
_controller
.
reverse
();
return
super
.
didPop
(
result
);
...
...
@@ -187,12 +196,16 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
@override
void
didPopNext
(
Route
<
dynamic
>
nextRoute
)
{
assert
(
_controller
!=
null
,
'
$runtimeType
.didPopNext called before calling install() or after calling dispose().'
);
assert
(!
_transitionCompleter
.
isCompleted
,
'Cannot reuse a
$runtimeType
after disposing it.'
);
_updateSecondaryAnimation
(
nextRoute
);
super
.
didPopNext
(
nextRoute
);
}
@override
void
didChangeNext
(
Route
<
dynamic
>
nextRoute
)
{
assert
(
_controller
!=
null
,
'
$runtimeType
.didChangeNext called before calling install() or after calling dispose().'
);
assert
(!
_transitionCompleter
.
isCompleted
,
'Cannot reuse a
$runtimeType
after disposing it.'
);
_updateSecondaryAnimation
(
nextRoute
);
super
.
didChangeNext
(
nextRoute
);
}
...
...
@@ -236,11 +249,12 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
///
/// Subclasses can override this method to restrict the set of routes they
/// need to coordinate transitions with.
bool
canTransitionFrom
(
TransitionRoute
<
dynamic
>
next
Route
)
=>
true
;
bool
canTransitionFrom
(
TransitionRoute
<
dynamic
>
previous
Route
)
=>
true
;
@override
void
dispose
()
{
_controller
.
dispose
();
assert
(!
_transitionCompleter
.
isCompleted
,
'Cannot dispose a
$runtimeType
twice.'
);
_controller
?.
dispose
();
_transitionCompleter
.
complete
(
_result
);
super
.
dispose
();
}
...
...
@@ -502,19 +516,24 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
/// ```dart
/// ModalRoute<dynamic> route = ModalRoute.of(context);
/// ```
///
/// The given [BuildContext] will be rebuilt if the state of the route changes
/// (specifically, if [Route.isCurrent] or [Route.canPop] change value).
static
ModalRoute
<
dynamic
>
of
(
BuildContext
context
)
{
final
_ModalScopeStatus
widget
=
context
.
inheritFromWidgetOfExactType
(
_ModalScopeStatus
);
return
widget
?.
route
;
}
/// Schedule a call to [buildTransitions].
///
/// Whenever you need to change internal state for a ModalRoute object, make
/// the change in a function that you pass to
setState()
, as in:
/// the change in a function that you pass to
[setState]
, as in:
///
/// ```dart
/// setState(() { myState = newValue });
/// ```
///
/// If you just change the state directly without calling
setState()
, then the
/// If you just change the state directly without calling
[setState]
, then the
/// route will not be scheduled for rebuilding, meaning that its rendering
/// will not be updated.
@protected
...
...
@@ -537,7 +556,8 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
static
RoutePredicate
withName
(
String
name
)
{
return
(
Route
<
dynamic
>
route
)
{
return
!
route
.
willHandlePopInternally
&&
route
is
ModalRoute
&&
route
.
settings
.
name
==
name
;
&&
route
is
ModalRoute
&&
route
.
settings
.
name
==
name
;
};
}
...
...
@@ -545,21 +565,40 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
/// Override this method to build the primary content of this route.
///
/// * [context] The context in which the route is being built.
/// * [animation] The animation for this route's transition. When entering,
/// the animation runs forward from 0.0 to 1.0. When exiting, this animation
/// runs backwards from 1.0 to 0.0.
/// * [secondaryAnimation] The animation for the route being pushed on top of
/// this route. This animation lets this route coordinate with the entrance
/// and exit transition of routes pushed on top of this route.
/// The arguments have the following meanings:
///
/// * `context`: The context in which the route is being built.
/// * [animation]: The animation for this route's transition. When entering,
/// the animation runs forward from 0.0 to 1.0. When exiting, this animation
/// runs backwards from 1.0 to 0.0.
/// * [secondaryAnimation]: The animation for the route being pushed on top of
/// this route. This animation lets this route coordinate with the entrance
/// and exit transition of routes pushed on top of this route.
///
/// This method is called when the route is first built, and rarely
/// thereafter. In particular, it is not called again when the route's state
/// changes. For a builder that is called every time the route's state
/// changes, consider [buildTransitions]. For widgets that change their
/// behavior when the route's state changes, consider [ModalRoute.of] to
/// obtain a reference to the route; this will cause the widget to be rebuilt
/// each time the route changes state.
///
/// In general, [buildPage] should be used to build the page contents, and
/// [buildTransitions] for the widgets that change as the page is brought in
/// and out of view. Avoid using [buildTransitions] for content that never
/// changes; building such content once from [buildPage] is more efficient.
Widget
buildPage
(
BuildContext
context
,
Animation
<
double
>
animation
,
Animation
<
double
>
secondaryAnimation
);
/// Override this method to wrap the [child] with one or more transition
/// widgets that define how the route arrives on and leaves the screen.
///
/// By default, the child is not wrapped in any transition widgets.
/// By default, the child (which contains the widget returned by [buildPage])
/// is not wrapped in any transition widgets.
///
/// The buildTransitions method is typically used to define transitions
/// The [buildTransitions] method, in contrast to [buildPage], is called each
/// time the [Route]'s state changes (e.g. the value of [canPop]).
///
/// The [buildTransitions] method is typically used to define transitions
/// that animate the new topmost route's comings and goings. When the
/// [Navigator] pushes a route on the top of its stack, the new route's
/// primary [animation] runs from 0.0 to 1.0. When the Navigator pops the
...
...
@@ -603,11 +642,11 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
/// );
///```
///
/// We've used [PageRouteBuilder] to demonstrate the
buildTransitions
method
/// here. The body of an override of the
buildTransitions
method would be
/// We've used [PageRouteBuilder] to demonstrate the
[buildTransitions]
method
/// here. The body of an override of the
[buildTransitions]
method would be
/// defined in the same way.
///
/// When the
Navigator
pushes a route on the top of its stack, the
/// When the
[Navigator]
pushes a route on the top of its stack, the
/// [secondaryAnimation] can be used to define how the route that was on
/// the top of the stack leaves the screen. Similarly when the topmost route
/// is popped, the secondaryAnimation can be used to define how the route
...
...
@@ -617,7 +656,7 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
/// secondaryAnimation for the route below it runs from 1.0 to 0.0.
///
/// The example below adds a transition that's driven by the
///
secondaryAnimation
. When this route disappears because a new route has
///
[secondaryAnimation]
. When this route disappears because a new route has
/// been pushed on top of it, it translates in the opposite direction of
/// the new route. Likewise when the route is exposed because the topmost
/// route has been popped off.
...
...
@@ -643,18 +682,26 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
/// ),
/// );
/// }
///```
/// ```
///
/// In practice the `secondaryAnimation` is used pretty rarely.
///
///
In practice the secondaryAnimation is used pretty rarely.
///
The arguments to this method are as follows:
///
/// *
[context]
The context in which the route is being built.
/// * [animation] When the [Navigator] pushes a route on the top of its stack,
/// the new route's primary [animation] runs from 0.0 to 1.0. When the
Navigator
/// *
`context`:
The context in which the route is being built.
/// * [animation]
:
When the [Navigator] pushes a route on the top of its stack,
/// the new route's primary [animation] runs from 0.0 to 1.0. When the
[Navigator]
/// pops the topmost route this animation runs from 1.0 to 0.0.
/// * [secondaryAnimation] When the Navigator pushes a new route
/// on the top of its stack, the old topmost route's secondaryAnimation
/// runs from 0.0 to 1.0. When the Navigator pops the topmost route, the
/// secondaryAnimation for the route below it runs from 1.0 to 0.0.
/// * [secondaryAnimation]: When the Navigator pushes a new route
/// on the top of its stack, the old topmost route's [secondaryAnimation]
/// runs from 0.0 to 1.0. When the [Navigator] pops the topmost route, the
/// [secondaryAnimation] for the route below it runs from 1.0 to 0.0.
/// * `child`, the page contents.
///
/// See also:
///
/// * [buildPage], which is used to describe the actual contents of the page,
/// and whose result is passed to the `child` argument of this method.
Widget
buildTransitions
(
BuildContext
context
,
Animation
<
double
>
animation
,
...
...
@@ -837,9 +884,9 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
///
/// See also:
///
/// * [Form], which provides an `onWillPop` callback that uses this mechanism.
/// * [addScopedWillPopCallback], which adds callback to the list
/// checked by [willPop].
///
* [Form], which provides an `onWillPop` callback that uses this mechanism.
///
* [addScopedWillPopCallback], which adds callback to the list
///
checked by [willPop].
void
removeScopedWillPopCallback
(
WillPopCallback
callback
)
{
assert
(
_scopeKey
.
currentState
!=
null
);
_scopeKey
.
currentState
.
removeWillPopCallback
(
callback
);
...
...
@@ -851,10 +898,16 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
/// supported by [MaterialPageRoute] for [TargetPlatform.iOS].
/// If a pop might be vetoed, then the back gesture is disabled.
///
/// The [buildTransitions] method will not be called again if this changes,
/// since it can change during the build as descendants of the route add or
/// remove callbacks.
///
/// See also:
///
/// * [addScopedWillPopCallback], which adds a callback.
/// * [removeScopedWillPopCallback], which removes a callback.
/// * [addScopedWillPopCallback], which adds a callback.
/// * [removeScopedWillPopCallback], which removes a callback.
/// * [willHandlePopInternally], which reports on another reason why
/// a pop might be vetoed.
@protected
bool
get
hasScopedWillPopCallback
{
return
_scopeKey
.
currentState
==
null
||
_scopeKey
.
currentState
.
_willPopCallbacks
.
isNotEmpty
;
...
...
packages/flutter/test/material/page_test.dart
View file @
02c21447
...
...
@@ -272,6 +272,73 @@ void main() {
expect
(
tester
.
getTopLeft
(
find
.
text
(
'Page 2'
)),
const
Offset
(
100.0
,
0.0
));
});
testWidgets
(
'back gesture while OS changes'
,
(
WidgetTester
tester
)
async
{
final
Map
<
String
,
WidgetBuilder
>
routes
=
<
String
,
WidgetBuilder
>{
'/'
:
(
BuildContext
context
)
=>
new
Material
(
child:
new
FlatButton
(
child:
new
Text
(
'PUSH'
),
onPressed:
()
{
Navigator
.
of
(
context
).
pushNamed
(
'/b'
);
},
),
),
'/b'
:
(
BuildContext
context
)
=>
new
Container
(
child:
new
Text
(
'HELLO'
)),
};
await
tester
.
pumpWidget
(
new
MaterialApp
(
theme:
new
ThemeData
(
platform:
TargetPlatform
.
iOS
),
routes:
routes
,
),
);
await
tester
.
tap
(
find
.
text
(
'PUSH'
));
expect
(
await
tester
.
pumpAndSettle
(
const
Duration
(
minutes:
1
)),
2
);
expect
(
find
.
text
(
'PUSH'
),
findsNothing
);
expect
(
find
.
text
(
'HELLO'
),
findsOneWidget
);
final
Offset
helloPosition1
=
tester
.
getCenter
(
find
.
text
(
'HELLO'
));
final
TestGesture
gesture
=
await
tester
.
startGesture
(
const
Offset
(
2.5
,
300.0
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
20
));
await
gesture
.
moveBy
(
const
Offset
(
100.0
,
0.0
));
expect
(
find
.
text
(
'PUSH'
),
findsNothing
);
expect
(
find
.
text
(
'HELLO'
),
findsOneWidget
);
await
tester
.
pump
(
const
Duration
(
milliseconds:
20
));
expect
(
find
.
text
(
'PUSH'
),
findsOneWidget
);
expect
(
find
.
text
(
'HELLO'
),
findsOneWidget
);
final
Offset
helloPosition2
=
tester
.
getCenter
(
find
.
text
(
'HELLO'
));
expect
(
helloPosition1
.
dx
,
lessThan
(
helloPosition2
.
dx
));
expect
(
helloPosition1
.
dy
,
helloPosition2
.
dy
);
expect
(
Theme
.
of
(
tester
.
element
(
find
.
text
(
'HELLO'
))).
platform
,
TargetPlatform
.
iOS
);
await
tester
.
pumpWidget
(
new
MaterialApp
(
theme:
new
ThemeData
(
platform:
TargetPlatform
.
android
),
routes:
routes
,
),
);
// Now we have to let the theme animation run through.
// This takes three frames (including the first one above):
// 1. Start the Theme animation. It's at t=0 so everything else is identical.
// 2. Start any animations that are informed by the Theme, for example, the
// DefaultTextStyle, on the first frame that the theme is not at t=0. In
// this case, it's at t=1.0 of the theme animation, so this is also the
// frame in which the theme animation ends.
// 3. End all the other animations.
expect
(
await
tester
.
pumpAndSettle
(
const
Duration
(
minutes:
1
)),
2
);
expect
(
Theme
.
of
(
tester
.
element
(
find
.
text
(
'HELLO'
))).
platform
,
TargetPlatform
.
android
);
final
Offset
helloPosition3
=
tester
.
getCenter
(
find
.
text
(
'HELLO'
));
expect
(
helloPosition3
,
helloPosition2
);
expect
(
find
.
text
(
'PUSH'
),
findsOneWidget
);
expect
(
find
.
text
(
'HELLO'
),
findsOneWidget
);
await
gesture
.
moveBy
(
const
Offset
(
100.0
,
0.0
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
20
));
expect
(
find
.
text
(
'PUSH'
),
findsOneWidget
);
expect
(
find
.
text
(
'HELLO'
),
findsOneWidget
);
final
Offset
helloPosition4
=
tester
.
getCenter
(
find
.
text
(
'HELLO'
));
expect
(
helloPosition3
.
dx
,
lessThan
(
helloPosition4
.
dx
));
expect
(
helloPosition3
.
dy
,
helloPosition4
.
dy
);
await
gesture
.
moveBy
(
const
Offset
(
500.0
,
0.0
));
await
gesture
.
up
();
expect
(
await
tester
.
pumpAndSettle
(
const
Duration
(
minutes:
1
)),
2
);
expect
(
find
.
text
(
'PUSH'
),
findsOneWidget
);
expect
(
find
.
text
(
'HELLO'
),
findsNothing
);
});
testWidgets
(
'test no back gesture on iOS fullscreen dialogs'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
new
MaterialApp
(
...
...
packages/flutter/test/widgets/page_transitions_test.dart
View file @
02c21447
...
...
@@ -268,24 +268,39 @@ void main() {
expect
(
find
.
text
(
'Home'
),
findsNothing
);
expect
(
find
.
text
(
'Sheet'
),
isOnstage
);
// Drag from left edge to invoke the gesture. We should go back.
TestGesture
gesture
=
await
tester
.
startGesture
(
const
Offset
(
5.0
,
100.0
));
await
gesture
.
moveBy
(
const
Offset
(
500.0
,
0.0
));
await
gesture
.
up
();
await
tester
.
pump
();
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
Navigator
.
pushNamed
(
containerKey1
.
currentContext
,
'/sheet'
);
await
tester
.
pump
();
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
expect
(
find
.
text
(
'Home'
),
findsNothing
);
expect
(
find
.
text
(
'Sheet'
),
isOnstage
);
// Show the bottom sheet.
final
PersistentBottomSheetTestState
sheet
=
containerKey2
.
currentState
;
sheet
.
showBottomSheet
();
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
// Drag from left edge to invoke the gesture.
final
TestGesture
gesture
=
await
tester
.
startGesture
(
const
Offset
(
5.0
,
100.0
));
// Drag from left edge to invoke the gesture.
Nothing should happen.
gesture
=
await
tester
.
startGesture
(
const
Offset
(
5.0
,
100.0
));
await
gesture
.
moveBy
(
const
Offset
(
500.0
,
0.0
));
await
gesture
.
up
();
await
tester
.
pump
();
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
expect
(
find
.
text
(
'Home'
),
isOnstage
);
expect
(
find
.
text
(
'Sheet'
),
findsNothing
);
expect
(
find
.
text
(
'Home'
),
findsNothing
);
expect
(
find
.
text
(
'Sheet'
),
isOnstage
);
// Sheet
called setState and didn't crash
.
expect
(
sheet
.
setStateCalled
,
is
Tru
e
);
// Sheet
did not call setState (since the gesture did nothing)
.
expect
(
sheet
.
setStateCalled
,
is
Fals
e
);
});
testWidgets
(
'Test completed future'
,
(
WidgetTester
tester
)
async
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment