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
87ca3d52
Unverified
Commit
87ca3d52
authored
Oct 26, 2018
by
xster
Committed by
GitHub
Oct 26, 2018
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Back swipe hero (#23320)
parent
c7b10a2d
Changes
7
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
361 additions
and
55 deletions
+361
-55
nav_bar.dart
packages/flutter/lib/src/cupertino/nav_bar.dart
+6
-0
route.dart
packages/flutter/lib/src/cupertino/route.dart
+1
-1
heroes.dart
packages/flutter/lib/src/widgets/heroes.dart
+84
-45
navigator.dart
packages/flutter/lib/src/widgets/navigator.dart
+19
-5
nav_bar_transition_test.dart
packages/flutter/test/cupertino/nav_bar_transition_test.dart
+106
-0
heroes_test.dart
packages/flutter/test/widgets/heroes_test.dart
+106
-4
navigator_test.dart
packages/flutter/test/widgets/navigator_test.dart
+39
-0
No files found.
packages/flutter/lib/src/cupertino/nav_bar.dart
View file @
87ca3d52
...
...
@@ -316,6 +316,10 @@ class CupertinoNavigationBar extends StatefulWidget implements ObstructingPrefer
/// to also has a [CupertinoNavigationBar] or a [CupertinoSliverNavigationBar]
/// with [transitionBetweenRoutes] set to true.
///
/// This transition will also occur on edge back swipe gestures like on iOS
/// but only if the previous page below has `maintainState` set to true on the
/// [PageRoute].
///
/// When set to true, only one navigation bar can be present per route unless
/// [heroTag] is also set.
///
...
...
@@ -398,6 +402,7 @@ class _CupertinoNavigationBarState extends State<CupertinoNavigationBar> {
createRectTween:
_linearTranslateWithLargestRectSizeTween
,
placeholderBuilder:
_navBarHeroLaunchPadBuilder
,
flightShuttleBuilder:
_navBarHeroFlightShuttleBuilder
,
transitionOnUserGestures:
true
,
child:
_TransitionableNavigationBar
(
componentsKeys:
keys
,
backgroundColor:
widget
.
backgroundColor
,
...
...
@@ -732,6 +737,7 @@ class _LargeTitleNavigationBarSliverDelegate
createRectTween:
_linearTranslateWithLargestRectSizeTween
,
flightShuttleBuilder:
_navBarHeroFlightShuttleBuilder
,
placeholderBuilder:
_navBarHeroLaunchPadBuilder
,
transitionOnUserGestures:
true
,
// This is all the way down here instead of being at the top level of
// CupertinoSliverNavigationBar like CupertinoNavigationBar because it
// needs to wrap the top level RenderBox rather than a RenderSliver.
...
...
packages/flutter/lib/src/cupertino/route.dart
View file @
87ca3d52
...
...
@@ -253,7 +253,7 @@ class CupertinoPageRoute<T> extends PageRoute<T> {
}
// Called by _CupertinoBackGestureDetector when a pop ("back") drag start
// gesture is detected. The returned controller handles all of the subsquent
// gesture is detected. The returned controller handles all of the subs
e
quent
// drag events.
static
_CupertinoBackGestureController
<
T
>
_startPopGesture
<
T
>(
PageRoute
<
T
>
route
)
{
assert
(!
_popGestureInProgress
.
contains
(
route
));
...
...
packages/flutter/lib/src/widgets/heroes.dart
View file @
87ca3d52
...
...
@@ -123,8 +123,10 @@ class Hero extends StatefulWidget {
this
.
createRectTween
,
this
.
flightShuttleBuilder
,
this
.
placeholderBuilder
,
this
.
transitionOnUserGestures
=
false
,
@required
this
.
child
,
})
:
assert
(
tag
!=
null
),
assert
(
transitionOnUserGestures
!=
null
),
assert
(
child
!=
null
),
super
(
key:
key
);
...
...
@@ -176,14 +178,31 @@ class Hero extends StatefulWidget {
/// left in place once the Hero shuttle has taken flight.
final
TransitionBuilder
placeholderBuilder
;
/// Whether to perform the hero transition if the [PageRoute] transition was
/// triggered by a user gesture, such as a back swipe on iOS.
///
/// If [Hero]s with the same [tag] on both the from and the to routes have
/// [transitionOnUserGestures] set to true, a back swipe gesture will
/// trigger the same hero animation as a programmatically triggered push or
/// pop.
///
/// The route being popped to or the bottom route must also have
/// [PageRoute.maintainState] set to true for a gesture triggered hero
/// transition to work.
///
/// Defaults to false and cannot be null.
final
bool
transitionOnUserGestures
;
// Returns a map of all of the heroes in context, indexed by hero tag.
static
Map
<
Object
,
_HeroState
>
_allHeroesFor
(
BuildContext
context
)
{
static
Map
<
Object
,
_HeroState
>
_allHeroesFor
(
BuildContext
context
,
bool
isUserGestureTransition
)
{
assert
(
context
!=
null
);
assert
(
isUserGestureTransition
!=
null
);
final
Map
<
Object
,
_HeroState
>
result
=
<
Object
,
_HeroState
>{};
void
visitor
(
Element
element
)
{
if
(
element
.
widget
is
Hero
)
{
final
StatefulElement
hero
=
element
;
final
Hero
heroWidget
=
element
.
widget
;
if
(!
isUserGestureTransition
||
heroWidget
.
transitionOnUserGestures
)
{
final
Object
tag
=
heroWidget
.
tag
;
assert
(
tag
!=
null
);
assert
(()
{
...
...
@@ -202,6 +221,7 @@ class Hero extends StatefulWidget {
final
_HeroState
heroState
=
hero
.
state
;
result
[
tag
]
=
heroState
;
}
}
// Don't perform transitions across different Navigators.
if
(
element
.
widget
is
Navigator
)
{
return
;
...
...
@@ -274,6 +294,7 @@ class _HeroFlightManifest {
@required
this
.
toHero
,
@required
this
.
createRectTween
,
@required
this
.
shuttleBuilder
,
@required
this
.
isUserGestureTransition
,
})
:
assert
(
fromHero
.
widget
.
tag
==
toHero
.
widget
.
tag
);
final
HeroFlightDirection
type
;
...
...
@@ -285,6 +306,7 @@ class _HeroFlightManifest {
final
_HeroState
toHero
;
final
CreateRectTween
createRectTween
;
final
HeroFlightShuttleBuilder
shuttleBuilder
;
final
bool
isUserGestureTransition
;
Object
get
tag
=>
fromHero
.
widget
.
tag
;
...
...
@@ -410,7 +432,12 @@ class _HeroFlight {
assert
(
type
!=
null
);
switch
(
type
)
{
case
HeroFlightDirection
.
pop
:
return
initial
.
value
==
1.0
&&
initial
.
status
==
AnimationStatus
.
reverse
;
return
initial
.
value
==
1.0
&&
initialManifest
.
isUserGestureTransition
// During user gesture transitions, the animation controller isn't
// driving the reverse transition, but should still be in a previously
// completed stage with the initial value at 1.0.
?
initial
.
status
==
AnimationStatus
.
completed
:
initial
.
status
==
AnimationStatus
.
reverse
;
case
HeroFlightDirection
.
push
:
return
initial
.
value
==
0.0
&&
initial
.
status
==
AnimationStatus
.
forward
;
}
...
...
@@ -532,14 +559,11 @@ class HeroController extends NavigatorObserver {
/// linear [Tween<Rect>].
HeroController
({
this
.
createRectTween
});
/// Used to create [RectTween]s that interpolate the position of heros in flight.
/// Used to create [RectTween]s that interpolate the position of hero
e
s in flight.
///
/// If null, the controller uses a linear [RectTween].
final
CreateRectTween
createRectTween
;
// Disable Hero animations while a user gesture is controlling the navigation.
bool
_questsEnabled
=
true
;
// All of the heroes that are currently in the overlay and in motion.
// Indexed by the hero tag.
final
Map
<
Object
,
_HeroFlight
>
_flights
=
<
Object
,
_HeroFlight
>{};
...
...
@@ -548,37 +572,49 @@ class HeroController extends NavigatorObserver {
void
didPush
(
Route
<
dynamic
>
route
,
Route
<
dynamic
>
previousRoute
)
{
assert
(
navigator
!=
null
);
assert
(
route
!=
null
);
_maybeStartHeroTransition
(
previousRoute
,
route
,
HeroFlightDirection
.
push
);
_maybeStartHeroTransition
(
previousRoute
,
route
,
HeroFlightDirection
.
push
,
false
);
}
@override
void
didPop
(
Route
<
dynamic
>
route
,
Route
<
dynamic
>
previousRoute
)
{
assert
(
navigator
!=
null
);
assert
(
route
!=
null
);
_maybeStartHeroTransition
(
route
,
previousRoute
,
HeroFlightDirection
.
pop
);
}
@override
void
didStartUserGesture
()
{
_questsEnabled
=
false
;
_maybeStartHeroTransition
(
route
,
previousRoute
,
HeroFlightDirection
.
pop
,
false
);
}
@override
void
didStopUserGesture
()
{
_questsEnabled
=
true
;
void
didStartUserGesture
(
Route
<
dynamic
>
route
,
Route
<
dynamic
>
previousRoute
)
{
assert
(
navigator
!=
null
);
assert
(
route
!=
null
);
_maybeStartHeroTransition
(
route
,
previousRoute
,
HeroFlightDirection
.
pop
,
true
);
}
// If we're transitioning between different page routes, start a hero transition
// after the toRoute has been laid out with its animation's value at 1.0.
void
_maybeStartHeroTransition
(
Route
<
dynamic
>
fromRoute
,
Route
<
dynamic
>
toRoute
,
HeroFlightDirection
flightType
)
{
if
(
_questsEnabled
&&
toRoute
!=
fromRoute
&&
toRoute
is
PageRoute
<
dynamic
>
&&
fromRoute
is
PageRoute
<
dynamic
>)
{
void
_maybeStartHeroTransition
(
Route
<
dynamic
>
fromRoute
,
Route
<
dynamic
>
toRoute
,
HeroFlightDirection
flightType
,
bool
isUserGestureTransition
,
)
{
if
(
toRoute
!=
fromRoute
&&
toRoute
is
PageRoute
<
dynamic
>
&&
fromRoute
is
PageRoute
<
dynamic
>)
{
final
PageRoute
<
dynamic
>
from
=
fromRoute
;
final
PageRoute
<
dynamic
>
to
=
toRoute
;
final
Animation
<
double
>
animation
=
(
flightType
==
HeroFlightDirection
.
push
)
?
to
.
animation
:
from
.
animation
;
// A user gesture may have already completed the pop.
if
(
flightType
==
HeroFlightDirection
.
pop
&&
animation
.
status
==
AnimationStatus
.
dismissed
)
if
(
flightType
==
HeroFlightDirection
.
pop
&&
animation
.
status
==
AnimationStatus
.
dismissed
)
{
return
;
}
// For pop transitions driven by a user gesture: if the "to" page has
// maintainState = true, then the hero's final dimensions can be measured
// immediately because their page's layout is still valid.
if
(
isUserGestureTransition
&&
flightType
==
HeroFlightDirection
.
pop
&&
to
.
maintainState
)
{
_startHeroTransition
(
from
,
to
,
animation
,
flightType
,
isUserGestureTransition
);
}
else
{
// Otherwise, delay measuring until the end of the next frame to allow
// the 'to' route to build and layout.
// Putting a route offstage changes its animation value to 1.0. Once this
// frame completes, we'll know where the heroes in the `to` route are
...
...
@@ -586,18 +622,20 @@ class HeroController extends NavigatorObserver {
to
.
offstage
=
to
.
animation
.
value
==
0.0
;
WidgetsBinding
.
instance
.
addPostFrameCallback
((
Duration
value
)
{
_startHeroTransition
(
from
,
to
,
animation
,
flightType
);
_startHeroTransition
(
from
,
to
,
animation
,
flightType
,
isUserGestureTransition
);
});
}
}
}
// Find the matching pairs of heros in from and to and either start or a new
// Find the matching pairs of hero
e
s in from and to and either start or a new
// hero flight, or divert an existing one.
void
_startHeroTransition
(
PageRoute
<
dynamic
>
from
,
PageRoute
<
dynamic
>
to
,
Animation
<
double
>
animation
,
HeroFlightDirection
flightType
,
bool
isUserGestureTransition
,
)
{
// If the navigator or one of the routes subtrees was removed before this
// end-of-frame callback was called, then don't actually start a transition.
...
...
@@ -609,8 +647,8 @@ class HeroController extends NavigatorObserver {
final
Rect
navigatorRect
=
_globalBoundingBoxFor
(
navigator
.
context
);
// At this point the toHeroes may have been built and laid out for the first time.
final
Map
<
Object
,
_HeroState
>
fromHeroes
=
Hero
.
_allHeroesFor
(
from
.
subtreeContext
);
final
Map
<
Object
,
_HeroState
>
toHeroes
=
Hero
.
_allHeroesFor
(
to
.
subtreeContext
);
final
Map
<
Object
,
_HeroState
>
fromHeroes
=
Hero
.
_allHeroesFor
(
from
.
subtreeContext
,
isUserGestureTransition
);
final
Map
<
Object
,
_HeroState
>
toHeroes
=
Hero
.
_allHeroesFor
(
to
.
subtreeContext
,
isUserGestureTransition
);
// If the `to` route was offstage, then we're implicitly restoring its
// animation value back to what it was before it was "moved" offstage.
...
...
@@ -632,6 +670,7 @@ class HeroController extends NavigatorObserver {
createRectTween:
createRectTween
,
shuttleBuilder:
toShuttleBuilder
??
fromShuttleBuilder
??
_defaultHeroFlightShuttleBuilder
,
isUserGestureTransition:
isUserGestureTransition
,
);
if
(
_flights
[
tag
]
!=
null
)
...
...
packages/flutter/lib/src/widgets/navigator.dart
View file @
87ca3d52
...
...
@@ -348,11 +348,18 @@ class NavigatorObserver {
/// The [Navigator] replaced `oldRoute` with `newRoute`.
void
didReplace
({
Route
<
dynamic
>
newRoute
,
Route
<
dynamic
>
oldRoute
})
{
}
/// The [Navigator]'s route
s are
being moved by a user gesture.
/// The [Navigator]'s route
`route` is
being moved by a user gesture.
///
/// For example, this is called when an iOS back gesture starts, and is used
/// to disabled hero animations during such interactions.
void
didStartUserGesture
()
{
}
/// For example, this is called when an iOS back gesture starts.
///
/// Paired with a call to [didStopUserGesture] when the route is no longer
/// being manipulated via user gesture.
///
/// If present, the route immediately below `route` is `previousRoute`.
/// Though the gesture may not necessarily conclude at `previousRoute` if
/// the gesture is canceled. In that case, [didStopUserGesture] is still
/// called but a follow-up [didPop] is not.
void
didStartUserGesture
(
Route
<
dynamic
>
route
,
Route
<
dynamic
>
previousRoute
)
{
}
/// User gesture is no longer controlling the [Navigator].
///
...
...
@@ -1911,8 +1918,15 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
void
didStartUserGesture
()
{
_userGesturesInProgress
+=
1
;
if
(
_userGesturesInProgress
==
1
)
{
final
Route
<
dynamic
>
route
=
_history
.
last
;
final
Route
<
dynamic
>
previousRoute
=
!
route
.
willHandlePopInternally
&&
_history
.
length
>
1
?
_history
[
_history
.
length
-
2
]
:
null
;
// Don't operate the _history list since the gesture may be cancelled.
// In case of a back swipe, the gesture controller will call .pop() itself.
for
(
NavigatorObserver
observer
in
widget
.
observers
)
observer
.
didStartUserGesture
();
observer
.
didStartUserGesture
(
route
,
previousRoute
);
}
}
...
...
packages/flutter/test/cupertino/nav_bar_transition_test.dart
View file @
87ca3d52
...
...
@@ -1015,4 +1015,110 @@ void main() {
expect
(
bottomBuildTimes
,
2
);
expect
(
topBuildTimes
,
3
);
});
testWidgets
(
'Back swipe gesture transitions'
,
(
WidgetTester
tester
)
async
{
await
startTransitionBetween
(
tester
,
fromTitle:
'Page 1'
,
toTitle:
'Page 2'
,
);
// Go to the next page.
await
tester
.
pump
(
const
Duration
(
milliseconds:
500
));
// Start the gesture at the edge of the screen.
final
TestGesture
gesture
=
await
tester
.
startGesture
(
const
Offset
(
5.0
,
200.0
));
// Trigger the swipe.
await
gesture
.
moveBy
(
const
Offset
(
100.0
,
0.0
));
// Back gestures should trigger and draw the hero transition in the very same
// frame (since the "from" route has already moved to reveal the "to" route).
await
tester
.
pump
();
// Page 2, which is the middle of the top route, start to fly back to the right.
expect
(
tester
.
getTopLeft
(
flying
(
tester
,
find
.
text
(
'Page 2'
))),
const
Offset
(
352.5802058875561
,
13.5
),
);
// Page 1 is in transition in 2 places. Once as the top back label and once
// as the bottom middle.
expect
(
flying
(
tester
,
find
.
text
(
'Page 1'
)),
findsNWidgets
(
2
));
// Past the halfway point now.
await
gesture
.
moveBy
(
const
Offset
(
500.0
,
0.0
));
await
gesture
.
up
();
await
tester
.
pump
();
// Transition continues.
expect
(
tester
.
getTopLeft
(
flying
(
tester
,
find
.
text
(
'Page 2'
))),
const
Offset
(
654.2055835723877
,
13.5
),
);
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
expect
(
tester
.
getTopLeft
(
flying
(
tester
,
find
.
text
(
'Page 2'
))),
const
Offset
(
720.8727767467499
,
13.5
),
);
await
tester
.
pump
(
const
Duration
(
milliseconds:
500
));
// Cleans up properly
expect
(()
=>
flying
(
tester
,
find
.
text
(
'Page 1'
)),
throwsAssertionError
);
expect
(()
=>
flying
(
tester
,
find
.
text
(
'Page 2'
)),
throwsAssertionError
);
// Just the bottom route's middle now.
expect
(
find
.
text
(
'Page 1'
),
findsOneWidget
);
});
testWidgets
(
'Back swipe gesture cancels properly with transition'
,
(
WidgetTester
tester
)
async
{
await
startTransitionBetween
(
tester
,
fromTitle:
'Page 1'
,
toTitle:
'Page 2'
,
);
// Go to the next page.
await
tester
.
pump
(
const
Duration
(
milliseconds:
500
));
// Start the gesture at the edge of the screen.
final
TestGesture
gesture
=
await
tester
.
startGesture
(
const
Offset
(
5.0
,
200.0
));
// Trigger the swipe.
await
gesture
.
moveBy
(
const
Offset
(
100.0
,
0.0
));
// Back gestures should trigger and draw the hero transition in the very same
// frame (since the "from" route has already moved to reveal the "to" route).
await
tester
.
pump
();
// Page 2, which is the middle of the top route, start to fly back to the right.
expect
(
tester
.
getTopLeft
(
flying
(
tester
,
find
.
text
(
'Page 2'
))),
const
Offset
(
352.5802058875561
,
13.5
),
);
await
gesture
.
up
();
await
tester
.
pump
();
// Transition continues from the point we let off.
expect
(
tester
.
getTopLeft
(
flying
(
tester
,
find
.
text
(
'Page 2'
))),
const
Offset
(
352.5802058875561
,
13.5
),
);
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
expect
(
tester
.
getTopLeft
(
flying
(
tester
,
find
.
text
(
'Page 2'
))),
const
Offset
(
350.00985169410706
,
13.5
),
);
// Finish the snap back animation.
await
tester
.
pump
(
const
Duration
(
milliseconds:
500
));
// Cleans up properly
expect
(()
=>
flying
(
tester
,
find
.
text
(
'Page 1'
)),
throwsAssertionError
);
expect
(()
=>
flying
(
tester
,
find
.
text
(
'Page 2'
)),
throwsAssertionError
);
// Back to page 2.
expect
(
find
.
text
(
'Page 2'
),
findsOneWidget
);
});
}
packages/flutter/test/widgets/heroes_test.dart
View file @
87ca3d52
...
...
@@ -14,13 +14,19 @@ Key homeRouteKey = const Key('homeRoute');
Key
routeTwoKey
=
const
Key
(
'routeTwo'
);
Key
routeThreeKey
=
const
Key
(
'routeThree'
);
bool
transitionFromUserGestures
=
false
;
final
Map
<
String
,
WidgetBuilder
>
routes
=
<
String
,
WidgetBuilder
>{
'/'
:
(
BuildContext
context
)
=>
Material
(
child:
ListView
(
key:
homeRouteKey
,
children:
<
Widget
>[
Container
(
height:
100.0
,
width:
100.0
),
Card
(
child:
Hero
(
tag:
'a'
,
child:
Container
(
height:
100.0
,
width:
100.0
,
key:
firstKey
))),
Card
(
child:
Hero
(
tag:
'a'
,
transitionOnUserGestures:
transitionFromUserGestures
,
child:
Container
(
height:
100.0
,
width:
100.0
,
key:
firstKey
),
)),
Container
(
height:
100.0
,
width:
100.0
),
FlatButton
(
child:
const
Text
(
'two'
),
...
...
@@ -42,7 +48,11 @@ final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
onPressed:
()
{
Navigator
.
pop
(
context
);
}
),
Container
(
height:
150.0
,
width:
150.0
),
Card
(
child:
Hero
(
tag:
'a'
,
child:
Container
(
height:
150.0
,
width:
150.0
,
key:
secondKey
))),
Card
(
child:
Hero
(
tag:
'a'
,
transitionOnUserGestures:
transitionFromUserGestures
,
child:
Container
(
height:
150.0
,
width:
150.0
,
key:
secondKey
),
)),
Container
(
height:
150.0
,
width:
150.0
),
FlatButton
(
child:
const
Text
(
'three'
),
...
...
@@ -67,7 +77,11 @@ final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
Card
(
child:
Padding
(
padding:
const
EdgeInsets
.
only
(
left:
50.0
),
child:
Hero
(
tag:
'a'
,
child:
Container
(
height:
150.0
,
width:
150.0
,
key:
secondKey
))
child:
Hero
(
tag:
'a'
,
transitionOnUserGestures:
transitionFromUserGestures
,
child:
Container
(
height:
150.0
,
width:
150.0
,
key:
secondKey
),
)
),
),
Container
(
height:
150.0
,
width:
150.0
),
...
...
@@ -78,7 +92,6 @@ final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
]
)
),
};
class
ThreeRoute
extends
MaterialPageRoute
<
void
>
{
...
...
@@ -121,6 +134,10 @@ class MyStatefulWidgetState extends State<MyStatefulWidget> {
}
void
main
(
)
{
setUp
(()
{
transitionFromUserGestures
=
false
;
});
testWidgets
(
'Heroes animate'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
MaterialApp
(
routes:
routes
));
...
...
@@ -1335,4 +1352,89 @@ void main() {
expect
(
find
.
text
(
'Venom'
),
findsOneWidget
);
expect
(
find
.
text
(
'Joker'
),
findsOneWidget
);
});
testWidgets
(
'Heroes do not transition on back gestures by default'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
MaterialApp
(
theme:
ThemeData
(
platform:
TargetPlatform
.
iOS
,
),
routes:
routes
,
));
expect
(
find
.
byKey
(
firstKey
),
isOnstage
);
expect
(
find
.
byKey
(
firstKey
),
isInCard
);
expect
(
find
.
byKey
(
secondKey
),
findsNothing
);
await
tester
.
tap
(
find
.
text
(
'two'
));
await
tester
.
pump
();
await
tester
.
pump
(
const
Duration
(
milliseconds:
500
));
expect
(
find
.
byKey
(
firstKey
),
findsNothing
);
expect
(
find
.
byKey
(
secondKey
),
isOnstage
);
expect
(
find
.
byKey
(
secondKey
),
isInCard
);
final
TestGesture
gesture
=
await
tester
.
startGesture
(
const
Offset
(
5.0
,
200.0
));
await
gesture
.
moveBy
(
const
Offset
(
200.0
,
0.0
));
await
tester
.
pump
();
// Both Heros exist and seated in their normal parents.
expect
(
find
.
byKey
(
firstKey
),
isOnstage
);
expect
(
find
.
byKey
(
firstKey
),
isInCard
);
expect
(
find
.
byKey
(
secondKey
),
isOnstage
);
expect
(
find
.
byKey
(
secondKey
),
isInCard
);
// To make sure the hero had all chances of starting.
await
tester
.
pump
(
const
Duration
(
milliseconds:
100
));
expect
(
find
.
byKey
(
firstKey
),
isOnstage
);
expect
(
find
.
byKey
(
firstKey
),
isInCard
);
expect
(
find
.
byKey
(
secondKey
),
isOnstage
);
expect
(
find
.
byKey
(
secondKey
),
isInCard
);
});
testWidgets
(
'Heroes can transition on gesture in one frame'
,
(
WidgetTester
tester
)
async
{
transitionFromUserGestures
=
true
;
await
tester
.
pumpWidget
(
MaterialApp
(
theme:
ThemeData
(
platform:
TargetPlatform
.
iOS
,
),
routes:
routes
,
));
await
tester
.
tap
(
find
.
text
(
'two'
));
await
tester
.
pump
();
await
tester
.
pump
(
const
Duration
(
milliseconds:
500
));
expect
(
find
.
byKey
(
firstKey
),
findsNothing
);
expect
(
find
.
byKey
(
secondKey
),
isOnstage
);
expect
(
find
.
byKey
(
secondKey
),
isInCard
);
final
TestGesture
gesture
=
await
tester
.
startGesture
(
const
Offset
(
5.0
,
200.0
));
await
gesture
.
moveBy
(
const
Offset
(
200.0
,
0.0
));
await
tester
.
pump
();
// We're going to page 1 so page 1's Hero is lifted into flight.
expect
(
find
.
byKey
(
firstKey
),
isOnstage
);
expect
(
find
.
byKey
(
firstKey
),
isNotInCard
);
expect
(
find
.
byKey
(
secondKey
),
findsNothing
);
// Move further along.
await
gesture
.
moveBy
(
const
Offset
(
500.0
,
0.0
));
await
tester
.
pump
();
// Same results.
expect
(
find
.
byKey
(
firstKey
),
isOnstage
);
expect
(
find
.
byKey
(
firstKey
),
isNotInCard
);
expect
(
find
.
byKey
(
secondKey
),
findsNothing
);
await
gesture
.
up
();
// Finish transition.
await
tester
.
pump
();
await
tester
.
pump
(
const
Duration
(
milliseconds:
500
));
// Hero A is back in the card.
expect
(
find
.
byKey
(
firstKey
),
isOnstage
);
expect
(
find
.
byKey
(
firstKey
),
isInCard
);
expect
(
find
.
byKey
(
secondKey
),
findsNothing
);
});
}
packages/flutter/test/widgets/navigator_test.dart
View file @
87ca3d52
...
...
@@ -96,6 +96,7 @@ class TestObserver extends NavigatorObserver {
OnObservation
onPopped
;
OnObservation
onRemoved
;
OnObservation
onReplaced
;
OnObservation
onStartUserGesture
;
@override
void
didPush
(
Route
<
dynamic
>
route
,
Route
<
dynamic
>
previousRoute
)
{
...
...
@@ -122,6 +123,12 @@ class TestObserver extends NavigatorObserver {
if
(
onReplaced
!=
null
)
onReplaced
(
newRoute
,
oldRoute
);
}
@override
void
didStartUserGesture
(
Route
<
dynamic
>
route
,
Route
<
dynamic
>
previousRoute
)
{
if
(
onStartUserGesture
!=
null
)
onStartUserGesture
(
route
,
previousRoute
);
}
}
void
main
(
)
{
...
...
@@ -715,6 +722,38 @@ void main() {
expect
(
log
,
<
String
>[
'pushed / (previous is <none>)'
,
'pushed B (previous is /)'
,
'pushed C (previous is B)'
,
'replaced B with D'
]);
});
testWidgets
(
'didStartUserGesture observable'
,
(
WidgetTester
tester
)
async
{
final
Map
<
String
,
WidgetBuilder
>
routes
=
<
String
,
WidgetBuilder
>{
'/'
:
(
BuildContext
context
)
=>
OnTapPage
(
id:
'/'
,
onTap:
()
{
Navigator
.
pushNamed
(
context
,
'/A'
);
}),
'/A'
:
(
BuildContext
context
)
=>
OnTapPage
(
id:
'A'
,
onTap:
()
{
Navigator
.
pop
(
context
);
}),
};
Route
<
dynamic
>
observedRoute
;
Route
<
dynamic
>
observedPreviousRoute
;
final
TestObserver
observer
=
TestObserver
()
..
onStartUserGesture
=
(
Route
<
dynamic
>
route
,
Route
<
dynamic
>
previousRoute
)
{
observedRoute
=
route
;
observedPreviousRoute
=
previousRoute
;
};
await
tester
.
pumpWidget
(
MaterialApp
(
routes:
routes
,
navigatorObservers:
<
NavigatorObserver
>[
observer
],
));
await
tester
.
tap
(
find
.
text
(
'/'
));
await
tester
.
pump
();
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
expect
(
find
.
text
(
'/'
),
findsNothing
);
expect
(
find
.
text
(
'A'
),
findsOneWidget
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
didStartUserGesture
();
expect
(
observedRoute
.
settings
.
name
,
'/A'
);
expect
(
observedPreviousRoute
.
settings
.
name
,
'/'
);
});
testWidgets
(
'ModalRoute.of sets up a route to rebuild if its state changes'
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
<
NavigatorState
>
key
=
GlobalKey
<
NavigatorState
>();
final
List
<
String
>
log
=
<
String
>[];
...
...
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