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
Hide 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
...
@@ -316,6 +316,10 @@ class CupertinoNavigationBar extends StatefulWidget implements ObstructingPrefer
/// to also has a [CupertinoNavigationBar] or a [CupertinoSliverNavigationBar]
/// to also has a [CupertinoNavigationBar] or a [CupertinoSliverNavigationBar]
/// with [transitionBetweenRoutes] set to true.
/// 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
/// When set to true, only one navigation bar can be present per route unless
/// [heroTag] is also set.
/// [heroTag] is also set.
///
///
...
@@ -398,6 +402,7 @@ class _CupertinoNavigationBarState extends State<CupertinoNavigationBar> {
...
@@ -398,6 +402,7 @@ class _CupertinoNavigationBarState extends State<CupertinoNavigationBar> {
createRectTween:
_linearTranslateWithLargestRectSizeTween
,
createRectTween:
_linearTranslateWithLargestRectSizeTween
,
placeholderBuilder:
_navBarHeroLaunchPadBuilder
,
placeholderBuilder:
_navBarHeroLaunchPadBuilder
,
flightShuttleBuilder:
_navBarHeroFlightShuttleBuilder
,
flightShuttleBuilder:
_navBarHeroFlightShuttleBuilder
,
transitionOnUserGestures:
true
,
child:
_TransitionableNavigationBar
(
child:
_TransitionableNavigationBar
(
componentsKeys:
keys
,
componentsKeys:
keys
,
backgroundColor:
widget
.
backgroundColor
,
backgroundColor:
widget
.
backgroundColor
,
...
@@ -732,6 +737,7 @@ class _LargeTitleNavigationBarSliverDelegate
...
@@ -732,6 +737,7 @@ class _LargeTitleNavigationBarSliverDelegate
createRectTween:
_linearTranslateWithLargestRectSizeTween
,
createRectTween:
_linearTranslateWithLargestRectSizeTween
,
flightShuttleBuilder:
_navBarHeroFlightShuttleBuilder
,
flightShuttleBuilder:
_navBarHeroFlightShuttleBuilder
,
placeholderBuilder:
_navBarHeroLaunchPadBuilder
,
placeholderBuilder:
_navBarHeroLaunchPadBuilder
,
transitionOnUserGestures:
true
,
// This is all the way down here instead of being at the top level of
// This is all the way down here instead of being at the top level of
// CupertinoSliverNavigationBar like CupertinoNavigationBar because it
// CupertinoSliverNavigationBar like CupertinoNavigationBar because it
// needs to wrap the top level RenderBox rather than a RenderSliver.
// 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> {
...
@@ -253,7 +253,7 @@ class CupertinoPageRoute<T> extends PageRoute<T> {
}
}
// Called by _CupertinoBackGestureDetector when a pop ("back") drag start
// 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.
// drag events.
static
_CupertinoBackGestureController
<
T
>
_startPopGesture
<
T
>(
PageRoute
<
T
>
route
)
{
static
_CupertinoBackGestureController
<
T
>
_startPopGesture
<
T
>(
PageRoute
<
T
>
route
)
{
assert
(!
_popGestureInProgress
.
contains
(
route
));
assert
(!
_popGestureInProgress
.
contains
(
route
));
...
...
packages/flutter/lib/src/widgets/heroes.dart
View file @
87ca3d52
...
@@ -123,8 +123,10 @@ class Hero extends StatefulWidget {
...
@@ -123,8 +123,10 @@ class Hero extends StatefulWidget {
this
.
createRectTween
,
this
.
createRectTween
,
this
.
flightShuttleBuilder
,
this
.
flightShuttleBuilder
,
this
.
placeholderBuilder
,
this
.
placeholderBuilder
,
this
.
transitionOnUserGestures
=
false
,
@required
this
.
child
,
@required
this
.
child
,
})
:
assert
(
tag
!=
null
),
})
:
assert
(
tag
!=
null
),
assert
(
transitionOnUserGestures
!=
null
),
assert
(
child
!=
null
),
assert
(
child
!=
null
),
super
(
key:
key
);
super
(
key:
key
);
...
@@ -176,31 +178,49 @@ class Hero extends StatefulWidget {
...
@@ -176,31 +178,49 @@ class Hero extends StatefulWidget {
/// left in place once the Hero shuttle has taken flight.
/// left in place once the Hero shuttle has taken flight.
final
TransitionBuilder
placeholderBuilder
;
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.
// 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
(
context
!=
null
);
assert
(
isUserGestureTransition
!=
null
);
final
Map
<
Object
,
_HeroState
>
result
=
<
Object
,
_HeroState
>{};
final
Map
<
Object
,
_HeroState
>
result
=
<
Object
,
_HeroState
>{};
void
visitor
(
Element
element
)
{
void
visitor
(
Element
element
)
{
if
(
element
.
widget
is
Hero
)
{
if
(
element
.
widget
is
Hero
)
{
final
StatefulElement
hero
=
element
;
final
StatefulElement
hero
=
element
;
final
Hero
heroWidget
=
element
.
widget
;
final
Hero
heroWidget
=
element
.
widget
;
final
Object
tag
=
heroWidget
.
tag
;
if
(!
isUserGestureTransition
||
heroWidget
.
transitionOnUserGestures
)
{
assert
(
tag
!=
null
);
final
Object
tag
=
heroWidget
.
tag
;
assert
(()
{
assert
(
tag
!=
null
);
if
(
result
.
containsKey
(
tag
))
{
assert
(()
{
throw
FlutterError
(
if
(
result
.
containsKey
(
tag
))
{
'There are multiple heroes that share the same tag within a subtree.
\n
'
throw
FlutterError
(
'Within each subtree for which heroes are to be animated (typically a PageRoute subtree), '
'There are multiple heroes that share the same tag within a subtree.
\n
'
'each Hero must have a unique non-null tag.
\n
'
'Within each subtree for which heroes are to be animated (typically a PageRoute subtree), '
'In this case, multiple heroes had the following tag:
$tag
\n
'
'each Hero must have a unique non-null tag.
\n
'
'Here is the subtree for one of the offending heroes:
\n
'
'In this case, multiple heroes had the following tag:
$tag
\n
'
'
${element.toStringDeep(prefixLineOne: "# ")}
'
'Here is the subtree for one of the offending heroes:
\n
'
);
'
${element.toStringDeep(prefixLineOne: "# ")}
'
}
);
return
true
;
}
}());
return
true
;
final
_HeroState
heroState
=
hero
.
state
;
}());
result
[
tag
]
=
heroState
;
final
_HeroState
heroState
=
hero
.
state
;
result
[
tag
]
=
heroState
;
}
}
}
// Don't perform transitions across different Navigators.
// Don't perform transitions across different Navigators.
if
(
element
.
widget
is
Navigator
)
{
if
(
element
.
widget
is
Navigator
)
{
...
@@ -274,6 +294,7 @@ class _HeroFlightManifest {
...
@@ -274,6 +294,7 @@ class _HeroFlightManifest {
@required
this
.
toHero
,
@required
this
.
toHero
,
@required
this
.
createRectTween
,
@required
this
.
createRectTween
,
@required
this
.
shuttleBuilder
,
@required
this
.
shuttleBuilder
,
@required
this
.
isUserGestureTransition
,
})
:
assert
(
fromHero
.
widget
.
tag
==
toHero
.
widget
.
tag
);
})
:
assert
(
fromHero
.
widget
.
tag
==
toHero
.
widget
.
tag
);
final
HeroFlightDirection
type
;
final
HeroFlightDirection
type
;
...
@@ -285,6 +306,7 @@ class _HeroFlightManifest {
...
@@ -285,6 +306,7 @@ class _HeroFlightManifest {
final
_HeroState
toHero
;
final
_HeroState
toHero
;
final
CreateRectTween
createRectTween
;
final
CreateRectTween
createRectTween
;
final
HeroFlightShuttleBuilder
shuttleBuilder
;
final
HeroFlightShuttleBuilder
shuttleBuilder
;
final
bool
isUserGestureTransition
;
Object
get
tag
=>
fromHero
.
widget
.
tag
;
Object
get
tag
=>
fromHero
.
widget
.
tag
;
...
@@ -410,7 +432,12 @@ class _HeroFlight {
...
@@ -410,7 +432,12 @@ class _HeroFlight {
assert
(
type
!=
null
);
assert
(
type
!=
null
);
switch
(
type
)
{
switch
(
type
)
{
case
HeroFlightDirection
.
pop
:
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
:
case
HeroFlightDirection
.
push
:
return
initial
.
value
==
0.0
&&
initial
.
status
==
AnimationStatus
.
forward
;
return
initial
.
value
==
0.0
&&
initial
.
status
==
AnimationStatus
.
forward
;
}
}
...
@@ -532,14 +559,11 @@ class HeroController extends NavigatorObserver {
...
@@ -532,14 +559,11 @@ class HeroController extends NavigatorObserver {
/// linear [Tween<Rect>].
/// linear [Tween<Rect>].
HeroController
({
this
.
createRectTween
});
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].
/// If null, the controller uses a linear [RectTween].
final
CreateRectTween
createRectTween
;
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.
// All of the heroes that are currently in the overlay and in motion.
// Indexed by the hero tag.
// Indexed by the hero tag.
final
Map
<
Object
,
_HeroFlight
>
_flights
=
<
Object
,
_HeroFlight
>{};
final
Map
<
Object
,
_HeroFlight
>
_flights
=
<
Object
,
_HeroFlight
>{};
...
@@ -548,56 +572,70 @@ class HeroController extends NavigatorObserver {
...
@@ -548,56 +572,70 @@ class HeroController extends NavigatorObserver {
void
didPush
(
Route
<
dynamic
>
route
,
Route
<
dynamic
>
previousRoute
)
{
void
didPush
(
Route
<
dynamic
>
route
,
Route
<
dynamic
>
previousRoute
)
{
assert
(
navigator
!=
null
);
assert
(
navigator
!=
null
);
assert
(
route
!=
null
);
assert
(
route
!=
null
);
_maybeStartHeroTransition
(
previousRoute
,
route
,
HeroFlightDirection
.
push
);
_maybeStartHeroTransition
(
previousRoute
,
route
,
HeroFlightDirection
.
push
,
false
);
}
}
@override
@override
void
didPop
(
Route
<
dynamic
>
route
,
Route
<
dynamic
>
previousRoute
)
{
void
didPop
(
Route
<
dynamic
>
route
,
Route
<
dynamic
>
previousRoute
)
{
assert
(
navigator
!=
null
);
assert
(
navigator
!=
null
);
assert
(
route
!=
null
);
assert
(
route
!=
null
);
_maybeStartHeroTransition
(
route
,
previousRoute
,
HeroFlightDirection
.
pop
);
_maybeStartHeroTransition
(
route
,
previousRoute
,
HeroFlightDirection
.
pop
,
false
);
}
@override
void
didStartUserGesture
()
{
_questsEnabled
=
false
;
}
}
@override
@override
void
didStopUserGesture
()
{
void
didStartUserGesture
(
Route
<
dynamic
>
route
,
Route
<
dynamic
>
previousRoute
)
{
_questsEnabled
=
true
;
assert
(
navigator
!=
null
);
assert
(
route
!=
null
);
_maybeStartHeroTransition
(
route
,
previousRoute
,
HeroFlightDirection
.
pop
,
true
);
}
}
// If we're transitioning between different page routes, start a hero transition
// 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.
// 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
)
{
void
_maybeStartHeroTransition
(
if
(
_questsEnabled
&&
toRoute
!=
fromRoute
&&
toRoute
is
PageRoute
<
dynamic
>
&&
fromRoute
is
PageRoute
<
dynamic
>)
{
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
>
from
=
fromRoute
;
final
PageRoute
<
dynamic
>
to
=
toRoute
;
final
PageRoute
<
dynamic
>
to
=
toRoute
;
final
Animation
<
double
>
animation
=
(
flightType
==
HeroFlightDirection
.
push
)
?
to
.
animation
:
from
.
animation
;
final
Animation
<
double
>
animation
=
(
flightType
==
HeroFlightDirection
.
push
)
?
to
.
animation
:
from
.
animation
;
// A user gesture may have already completed the pop.
// 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
;
return
;
}
// Putting a route offstage changes its animation value to 1.0. Once this
// For pop transitions driven by a user gesture: if the "to" page has
// frame completes, we'll know where the heroes in the `to` route are
// maintainState = true, then the hero's final dimensions can be measured
// going to end up, and the `to` route will go back onstage.
// immediately because their page's layout is still valid.
to
.
offstage
=
to
.
animation
.
value
==
0.0
;
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.
WidgetsBinding
.
instance
.
addPostFrameCallback
((
Duration
value
)
{
// Putting a route offstage changes its animation value to 1.0. Once this
_startHeroTransition
(
from
,
to
,
animation
,
flightType
);
// frame completes, we'll know where the heroes in the `to` route are
});
// going to end up, and the `to` route will go back onstage.
to
.
offstage
=
to
.
animation
.
value
==
0.0
;
WidgetsBinding
.
instance
.
addPostFrameCallback
((
Duration
value
)
{
_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.
// hero flight, or divert an existing one.
void
_startHeroTransition
(
void
_startHeroTransition
(
PageRoute
<
dynamic
>
from
,
PageRoute
<
dynamic
>
from
,
PageRoute
<
dynamic
>
to
,
PageRoute
<
dynamic
>
to
,
Animation
<
double
>
animation
,
Animation
<
double
>
animation
,
HeroFlightDirection
flightType
,
HeroFlightDirection
flightType
,
bool
isUserGestureTransition
,
)
{
)
{
// If the navigator or one of the routes subtrees was removed before this
// 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.
// end-of-frame callback was called, then don't actually start a transition.
...
@@ -609,8 +647,8 @@ class HeroController extends NavigatorObserver {
...
@@ -609,8 +647,8 @@ class HeroController extends NavigatorObserver {
final
Rect
navigatorRect
=
_globalBoundingBoxFor
(
navigator
.
context
);
final
Rect
navigatorRect
=
_globalBoundingBoxFor
(
navigator
.
context
);
// At this point the toHeroes may have been built and laid out for the first time.
// 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
>
fromHeroes
=
Hero
.
_allHeroesFor
(
from
.
subtreeContext
,
isUserGestureTransition
);
final
Map
<
Object
,
_HeroState
>
toHeroes
=
Hero
.
_allHeroesFor
(
to
.
subtreeContext
);
final
Map
<
Object
,
_HeroState
>
toHeroes
=
Hero
.
_allHeroesFor
(
to
.
subtreeContext
,
isUserGestureTransition
);
// If the `to` route was offstage, then we're implicitly restoring its
// If the `to` route was offstage, then we're implicitly restoring its
// animation value back to what it was before it was "moved" offstage.
// animation value back to what it was before it was "moved" offstage.
...
@@ -632,6 +670,7 @@ class HeroController extends NavigatorObserver {
...
@@ -632,6 +670,7 @@ class HeroController extends NavigatorObserver {
createRectTween:
createRectTween
,
createRectTween:
createRectTween
,
shuttleBuilder:
shuttleBuilder:
toShuttleBuilder
??
fromShuttleBuilder
??
_defaultHeroFlightShuttleBuilder
,
toShuttleBuilder
??
fromShuttleBuilder
??
_defaultHeroFlightShuttleBuilder
,
isUserGestureTransition:
isUserGestureTransition
,
);
);
if
(
_flights
[
tag
]
!=
null
)
if
(
_flights
[
tag
]
!=
null
)
...
...
packages/flutter/lib/src/widgets/navigator.dart
View file @
87ca3d52
...
@@ -348,11 +348,18 @@ class NavigatorObserver {
...
@@ -348,11 +348,18 @@ class NavigatorObserver {
/// The [Navigator] replaced `oldRoute` with `newRoute`.
/// The [Navigator] replaced `oldRoute` with `newRoute`.
void
didReplace
({
Route
<
dynamic
>
newRoute
,
Route
<
dynamic
>
oldRoute
})
{
}
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
/// For example, this is called when an iOS back gesture starts.
/// to disabled hero animations during such interactions.
///
void
didStartUserGesture
()
{
}
/// 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].
/// User gesture is no longer controlling the [Navigator].
///
///
...
@@ -1911,8 +1918,15 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
...
@@ -1911,8 +1918,15 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
void
didStartUserGesture
()
{
void
didStartUserGesture
()
{
_userGesturesInProgress
+=
1
;
_userGesturesInProgress
+=
1
;
if
(
_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
)
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() {
...
@@ -1015,4 +1015,110 @@ void main() {
expect
(
bottomBuildTimes
,
2
);
expect
(
bottomBuildTimes
,
2
);
expect
(
topBuildTimes
,
3
);
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');
...
@@ -14,13 +14,19 @@ Key homeRouteKey = const Key('homeRoute');
Key
routeTwoKey
=
const
Key
(
'routeTwo'
);
Key
routeTwoKey
=
const
Key
(
'routeTwo'
);
Key
routeThreeKey
=
const
Key
(
'routeThree'
);
Key
routeThreeKey
=
const
Key
(
'routeThree'
);
bool
transitionFromUserGestures
=
false
;
final
Map
<
String
,
WidgetBuilder
>
routes
=
<
String
,
WidgetBuilder
>{
final
Map
<
String
,
WidgetBuilder
>
routes
=
<
String
,
WidgetBuilder
>{
'/'
:
(
BuildContext
context
)
=>
Material
(
'/'
:
(
BuildContext
context
)
=>
Material
(
child:
ListView
(
child:
ListView
(
key:
homeRouteKey
,
key:
homeRouteKey
,
children:
<
Widget
>[
children:
<
Widget
>[
Container
(
height:
100.0
,
width:
100.0
),
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
),
Container
(
height:
100.0
,
width:
100.0
),
FlatButton
(
FlatButton
(
child:
const
Text
(
'two'
),
child:
const
Text
(
'two'
),
...
@@ -42,7 +48,11 @@ final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
...
@@ -42,7 +48,11 @@ final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
onPressed:
()
{
Navigator
.
pop
(
context
);
}
onPressed:
()
{
Navigator
.
pop
(
context
);
}
),
),
Container
(
height:
150.0
,
width:
150.0
),
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
),
Container
(
height:
150.0
,
width:
150.0
),
FlatButton
(
FlatButton
(
child:
const
Text
(
'three'
),
child:
const
Text
(
'three'
),
...
@@ -67,7 +77,11 @@ final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
...
@@ -67,7 +77,11 @@ final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
Card
(
Card
(
child:
Padding
(
child:
Padding
(
padding:
const
EdgeInsets
.
only
(
left:
50.0
),
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
),
Container
(
height:
150.0
,
width:
150.0
),
...
@@ -78,7 +92,6 @@ final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
...
@@ -78,7 +92,6 @@ final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
]
]
)
)
),
),
};
};
class
ThreeRoute
extends
MaterialPageRoute
<
void
>
{
class
ThreeRoute
extends
MaterialPageRoute
<
void
>
{
...
@@ -121,6 +134,10 @@ class MyStatefulWidgetState extends State<MyStatefulWidget> {
...
@@ -121,6 +134,10 @@ class MyStatefulWidgetState extends State<MyStatefulWidget> {
}
}
void
main
(
)
{
void
main
(
)
{
setUp
(()
{
transitionFromUserGestures
=
false
;
});
testWidgets
(
'Heroes animate'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'Heroes animate'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
MaterialApp
(
routes:
routes
));
await
tester
.
pumpWidget
(
MaterialApp
(
routes:
routes
));
...
@@ -1335,4 +1352,89 @@ void main() {
...
@@ -1335,4 +1352,89 @@ void main() {
expect
(
find
.
text
(
'Venom'
),
findsOneWidget
);
expect
(
find
.
text
(
'Venom'
),
findsOneWidget
);
expect
(
find
.
text
(
'Joker'
),
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 {
...
@@ -96,6 +96,7 @@ class TestObserver extends NavigatorObserver {
OnObservation
onPopped
;
OnObservation
onPopped
;
OnObservation
onRemoved
;
OnObservation
onRemoved
;
OnObservation
onReplaced
;
OnObservation
onReplaced
;
OnObservation
onStartUserGesture
;
@override
@override
void
didPush
(
Route
<
dynamic
>
route
,
Route
<
dynamic
>
previousRoute
)
{
void
didPush
(
Route
<
dynamic
>
route
,
Route
<
dynamic
>
previousRoute
)
{
...
@@ -122,6 +123,12 @@ class TestObserver extends NavigatorObserver {
...
@@ -122,6 +123,12 @@ class TestObserver extends NavigatorObserver {
if
(
onReplaced
!=
null
)
if
(
onReplaced
!=
null
)
onReplaced
(
newRoute
,
oldRoute
);
onReplaced
(
newRoute
,
oldRoute
);
}
}
@override
void
didStartUserGesture
(
Route
<
dynamic
>
route
,
Route
<
dynamic
>
previousRoute
)
{
if
(
onStartUserGesture
!=
null
)
onStartUserGesture
(
route
,
previousRoute
);
}
}
}
void
main
(
)
{
void
main
(
)
{
...
@@ -715,6 +722,38 @@ 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'
]);
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
{
testWidgets
(
'ModalRoute.of sets up a route to rebuild if its state changes'
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
<
NavigatorState
>
key
=
GlobalKey
<
NavigatorState
>();
final
GlobalKey
<
NavigatorState
>
key
=
GlobalKey
<
NavigatorState
>();
final
List
<
String
>
log
=
<
String
>[];
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