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
Expand all
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
/// 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
This diff is collapsed.
Click to expand it.
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