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
c44aa266
Commit
c44aa266
authored
Sep 22, 2017
by
Hans Muller
Committed by
GitHub
Sep 22, 2017
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Aborted Hero push transitions should retrace their flight path (#12203)
parent
f2d09601
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
156 additions
and
12 deletions
+156
-12
tween.dart
packages/flutter/lib/src/animation/tween.dart
+16
-0
heroes.dart
packages/flutter/lib/src/widgets/heroes.dart
+17
-12
heroes_test.dart
packages/flutter/test/widgets/heroes_test.dart
+123
-0
No files found.
packages/flutter/lib/src/animation/tween.dart
View file @
c44aa266
...
...
@@ -178,6 +178,22 @@ class Tween<T extends dynamic> extends Animatable<T> {
String
toString
()
=>
'
$runtimeType
(
$begin
\
u2192
$end
)'
;
}
/// A [Tween] that evaluates its [parent] in reverse.
class
ReverseTween
<
T
>
extends
Tween
<
T
>
{
/// Construct a [Tween] that evaluates its [parent] in reverse.
ReverseTween
(
this
.
parent
)
:
assert
(
parent
!=
null
),
super
(
begin:
parent
.
end
,
end:
parent
.
begin
);
/// This tween's value is the same as the parent's value evaluated in reverse.
///
/// This tween's [begin] is the parent's [end] and its [end] is the parent's
/// [begin]. The [lerp] method returns `parent.lerp(1.0 - t)` and its
/// [evaluate] method is similar.
final
Tween
<
T
>
parent
;
@override
T
lerp
(
double
t
)
=>
parent
.
lerp
(
1.0
-
t
);
}
/// An interpolation between two colors.
///
/// This class specializes the interpolation of [Tween<Color>] to use
...
...
packages/flutter/lib/src/widgets/heroes.dart
View file @
c44aa266
...
...
@@ -18,7 +18,7 @@ import 'transitions.dart';
/// This is typically used with a [HeroController] to provide an animation for
/// [Hero] positions that looks nicer than a linear movement. For example, see
/// [MaterialRectArcTween].
typedef
RectTween
CreateRectTween
(
Rect
begin
,
Rect
end
);
typedef
Tween
<
Rect
>
CreateRectTween
(
Rect
begin
,
Rect
end
);
typedef
void
_OnFlightEnded
(
_HeroFlight
flight
);
...
...
@@ -95,7 +95,7 @@ class Hero extends StatefulWidget {
/// route to the destination route.
///
/// A hero flight begins with the destination hero's [child] aligned with the
/// starting hero's child. The [
RectTween
] returned by this callback is used
/// starting hero's child. The [
Tween<Rect>
] returned by this callback is used
/// to compute the hero's bounds as the flight animation's value goes from 0.0
/// to 1.0.
///
...
...
@@ -236,14 +236,14 @@ class _HeroFlight {
final
_OnFlightEnded
onFlightEnded
;
RectTween
heroRect
;
Tween
<
Rect
>
heroRect
;
Animation
<
double
>
_heroOpacity
=
kAlwaysCompleteAnimation
;
ProxyAnimation
_proxyAnimation
;
_HeroFlightManifest
manifest
;
OverlayEntry
overlayEntry
;
bool
_aborted
=
false
;
RectTween
_doCreateRectTween
(
Rect
begin
,
Rect
end
)
{
Tween
<
Rect
>
_doCreateRectTween
(
Rect
begin
,
Rect
end
)
{
final
CreateRectTween
createRectTween
=
manifest
.
toHero
.
widget
.
createRectTween
??
manifest
.
createRectTween
;
if
(
createRectTween
!=
null
)
return
createRectTween
(
begin
,
end
);
...
...
@@ -268,11 +268,11 @@ class _HeroFlight {
}
}
else
if
(
toHeroBox
.
hasSize
)
{
// The toHero has been laid out. If it's no longer where the hero animation is
// supposed to end up
(heroRect.end)
then recreate the heroRect tween.
final
RenderBox
r
outeBox
=
manifest
.
toRoute
.
subtreeContext
?.
findRenderObject
();
final
Offset
heroOriginEnd
=
toHeroBox
.
localToGlobal
(
Offset
.
zero
,
ancestor:
r
outeBox
);
if
(
heroOriginEnd
!=
heroRect
.
end
.
topLeft
)
{
final
Rect
heroRectEnd
=
heroOriginEnd
&
heroRect
.
end
.
size
;
// supposed to end up then recreate the heroRect tween.
final
RenderBox
finalR
outeBox
=
manifest
.
toRoute
.
subtreeContext
?.
findRenderObject
();
final
Offset
toHeroOrigin
=
toHeroBox
.
localToGlobal
(
Offset
.
zero
,
ancestor:
finalR
outeBox
);
if
(
toHeroOrigin
!=
heroRect
.
end
.
topLeft
)
{
final
Rect
heroRectEnd
=
toHeroOrigin
&
heroRect
.
end
.
size
;
heroRect
=
_doCreateRectTween
(
heroRect
.
begin
,
heroRectEnd
);
}
}
...
...
@@ -359,9 +359,13 @@ class _HeroFlight {
assert
(
manifest
.
fromRoute
==
newManifest
.
toRoute
);
assert
(
manifest
.
toRoute
==
newManifest
.
fromRoute
);
// The same heroRect tween is used in reverse, rather than creating
// a new heroRect with _doCreateRectTween(heroRect.end, heroRect.begin).
// That's because tweens like MaterialRectArcTween may create a different
// path for swapped begin and end parameters. We want the pop flight
// path to be the same (in reverse) as the push flight path.
_proxyAnimation
.
parent
=
new
ReverseAnimation
(
newManifest
.
animation
);
heroRect
=
_doCreateRectTween
(
heroRect
.
end
,
heroRect
.
begin
);
heroRect
=
new
ReverseTween
<
Rect
>(
heroRect
);
}
else
if
(
manifest
.
type
==
_HeroFlightType
.
pop
&&
newManifest
.
type
==
_HeroFlightType
.
push
)
{
// A pop flight was interrupted by a push.
assert
(
newManifest
.
animation
.
status
==
AnimationStatus
.
forward
);
...
...
@@ -378,6 +382,7 @@ class _HeroFlight {
newManifest
.
toHero
.
startFlight
();
heroRect
=
_doCreateRectTween
(
heroRect
.
end
,
_globalBoundingBoxFor
(
newManifest
.
toHero
.
context
));
}
else
{
// TODO(hansmuller): Use ReverseTween here per github.com/flutter/flutter/pull/12203.
heroRect
=
_doCreateRectTween
(
heroRect
.
end
,
heroRect
.
begin
);
}
}
else
{
...
...
@@ -425,7 +430,7 @@ class HeroController extends NavigatorObserver {
/// Creates a hero controller with the given [RectTween] constructor if any.
///
/// The [createRectTween] argument is optional. If null, the controller uses a
/// linear [
RectTween
].
/// linear [
Tween<Rect>
].
HeroController
({
this
.
createRectTween
});
/// Used to create [RectTween]s that interpolate the position of heros in flight.
...
...
packages/flutter/test/widgets/heroes_test.dart
View file @
c44aa266
...
...
@@ -26,6 +26,10 @@ final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
child:
const
Text
(
'two'
),
onPressed:
()
{
Navigator
.
pushNamed
(
context
,
'/two'
);
}
),
new
FlatButton
(
child:
const
Text
(
'twoInset'
),
onPressed:
()
{
Navigator
.
pushNamed
(
context
,
'/twoInset'
);
}
),
]
)
),
...
...
@@ -47,6 +51,34 @@ final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
]
)
),
// This route is the same as /two except that Hero 'a' is shifted to the right by
// 50 pixels. When the hero's in-flight bounds between / and /twoInset are animated
// using MaterialRectArcTween (the default) they'll follow a different path
// then when the flight starts at /twoInset and returns to /.
'/twoInset'
:
(
BuildContext
context
)
=>
new
Material
(
child:
new
ListView
(
key:
routeTwoKey
,
children:
<
Widget
>[
new
FlatButton
(
child:
const
Text
(
'pop'
),
onPressed:
()
{
Navigator
.
pop
(
context
);
}
),
new
Container
(
height:
150.0
,
width:
150.0
),
new
Card
(
child:
new
Padding
(
padding:
const
EdgeInsets
.
only
(
left:
50.0
),
child:
new
Hero
(
tag:
'a'
,
child:
new
Container
(
height:
150.0
,
width:
150.0
,
key:
secondKey
))
),
),
new
Container
(
height:
150.0
,
width:
150.0
),
new
FlatButton
(
child:
const
Text
(
'three'
),
onPressed:
()
{
Navigator
.
push
(
context
,
new
ThreeRoute
());
},
),
]
)
),
};
class
ThreeRoute
extends
MaterialPageRoute
<
Null
>
{
...
...
@@ -1119,5 +1151,96 @@ void main() {
expect
(
tester
.
getCenter
(
find
.
byKey
(
firstKey
)),
const
Offset
(
50.0
,
50.0
));
});
testWidgets
(
'Pop interrupts push, reverses flight'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
new
MaterialApp
(
routes:
routes
));
await
tester
.
tap
(
find
.
text
(
'twoInset'
));
await
tester
.
pump
();
// begin navigation from / to /twoInset.
final
double
epsilon
=
0.001
;
final
Duration
duration
=
const
Duration
(
milliseconds:
300
);
await
tester
.
pump
();
final
double
x0
=
tester
.
getTopLeft
(
find
.
byKey
(
secondKey
)).
dx
;
// Flight begins with the secondKey Hero widget lined up with the firstKey widget.
expect
(
x0
,
4.0
);
await
tester
.
pump
(
duration
*
0.1
);
final
double
x1
=
tester
.
getTopLeft
(
find
.
byKey
(
secondKey
)).
dx
;
await
tester
.
pump
(
duration
*
0.1
);
final
double
x2
=
tester
.
getTopLeft
(
find
.
byKey
(
secondKey
)).
dx
;
await
tester
.
pump
(
duration
*
0.1
);
final
double
x3
=
tester
.
getTopLeft
(
find
.
byKey
(
secondKey
)).
dx
;
await
tester
.
pump
(
duration
*
0.1
);
final
double
x4
=
tester
.
getTopLeft
(
find
.
byKey
(
secondKey
)).
dx
;
// Pop route /twoInset before the push transition from / to /twoInset has finished.
await
tester
.
tap
(
find
.
text
(
'pop'
));
// We expect the hero to take the same path as it did flying from /
// to /twoInset as it does now, flying from '/twoInset' back to /. The most
// important checks below are the first (x4) and last (x0): the hero should
// not jump from where it was when the push transition was interrupted by a
// pop, and it should end up where the push started.
await
tester
.
pump
();
expect
(
tester
.
getTopLeft
(
find
.
byKey
(
secondKey
)).
dx
,
closeTo
(
x4
,
epsilon
));
await
tester
.
pump
(
duration
*
0.1
);
expect
(
tester
.
getTopLeft
(
find
.
byKey
(
secondKey
)).
dx
,
closeTo
(
x3
,
epsilon
));
await
tester
.
pump
(
duration
*
0.1
);
expect
(
tester
.
getTopLeft
(
find
.
byKey
(
secondKey
)).
dx
,
closeTo
(
x2
,
epsilon
));
await
tester
.
pump
(
duration
*
0.1
);
expect
(
tester
.
getTopLeft
(
find
.
byKey
(
secondKey
)).
dx
,
closeTo
(
x1
,
epsilon
));
await
tester
.
pump
(
duration
*
0.1
);
expect
(
tester
.
getTopLeft
(
find
.
byKey
(
secondKey
)).
dx
,
closeTo
(
x0
,
epsilon
));
// Below: show that a different pop Hero path is in fact taken after
// a completed push transition.
// Complete the pop transition and we're back to showing /.
await
tester
.
pumpAndSettle
();
expect
(
tester
.
getTopLeft
(
find
.
byKey
(
firstKey
)).
dx
,
4.0
);
// Card contents are inset by 4.0.
// Push /twoInset and wait for the transition to finish.
await
tester
.
tap
(
find
.
text
(
'twoInset'
));
await
tester
.
pumpAndSettle
();
expect
(
tester
.
getTopLeft
(
find
.
byKey
(
secondKey
)).
dx
,
54.0
);
// Start the pop transition from /twoInset to /.
await
tester
.
tap
(
find
.
text
(
'pop'
));
await
tester
.
pump
();
// Now the firstKey widget is the flying hero widget and it starts
// out lined up with the secondKey widget.
await
tester
.
pump
();
expect
(
tester
.
getTopLeft
(
find
.
byKey
(
firstKey
)).
dx
,
54.0
);
// x0-x4 are the top left x coordinates for the beginning 40% of
// the incoming flight. Advance the outgoing flight to the same
// place.
await
tester
.
pump
(
duration
*
0.6
);
await
tester
.
pump
(
duration
*
0.1
);
expect
(
tester
.
getTopLeft
(
find
.
byKey
(
firstKey
)).
dx
,
isNot
(
closeTo
(
x4
,
epsilon
)));
await
tester
.
pump
(
duration
*
0.1
);
expect
(
tester
.
getTopLeft
(
find
.
byKey
(
firstKey
)).
dx
,
isNot
(
closeTo
(
x3
,
epsilon
)));
// At this point the flight path arcs do start to get pretty close so
// there's no point in comparing them.
await
tester
.
pump
(
duration
*
0.1
);
// After the remaining 40% of the incoming flight is complete, we
// expect to end up where the outgoing flight started.
await
tester
.
pump
(
duration
*
0.1
);
expect
(
tester
.
getTopLeft
(
find
.
byKey
(
firstKey
)).
dx
,
x0
);
});
}
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