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
dc06326c
Unverified
Commit
dc06326c
authored
Jun 13, 2022
by
Youssef Attia
Committed by
GitHub
Jun 13, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Fixed issue with Hero Animations and BoxScrollViews in Scaffolds (#105654)
parent
0be4a8e9
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
128 additions
and
7 deletions
+128
-7
heroes.dart
packages/flutter/lib/src/widgets/heroes.dart
+42
-7
heroes_test.dart
packages/flutter/test/widgets/heroes_test.dart
+86
-0
No files found.
packages/flutter/lib/src/widgets/heroes.dart
View file @
dc06326c
...
...
@@ -3,10 +3,11 @@
// found in the LICENSE file.
import
'package:flutter/foundation.dart'
;
import
'basic.dart'
;
import
'binding.dart'
;
import
'framework.dart'
;
import
'implicit_animations.dart'
;
import
'media_query.dart'
;
import
'navigator.dart'
;
import
'overlay.dart'
;
import
'pages.dart'
;
...
...
@@ -135,9 +136,15 @@ enum HeroFlightDirection {
/// To make the animations look good, it's critical that the widget tree for the
/// hero in both locations be essentially identical. The widget of the *target*
/// is, by default, used to do the transition: when going from route A to route
/// B, route B's hero's widget is placed over route A's hero's widget. If a
/// [flightShuttleBuilder] is supplied, its output widget is shown during the
/// flight transition instead.
/// B, route B's hero's widget is placed over route A's hero's widget. Additionally,
/// if the [Hero] subtree changes appearance based on an [InheritedWidget] (such
/// as [MediaQuery] or [Theme]), then the hero animation may have discontinuity
/// at the start or the end of the animation because route A and route B provides
/// different such [InheritedWidget]s. Consider providing a custom [flightShuttleBuilder]
/// to ensure smooth transitions. The default [flightShuttleBuilder] interpolates
/// [MediaQuery]'s paddings. If your [Hero] widget uses custom [InheritedWidget]s
/// and displays a discontinuity in the animation, try to provide custom in-flight
/// transition using [flightShuttleBuilder].
///
/// By default, both route A and route B's heroes are hidden while the
/// transitioning widget is animating in-flight above the 2 routes.
...
...
@@ -910,8 +917,8 @@ class HeroController extends NavigatorObserver {
final
NavigatorState
?
navigator
=
this
.
navigator
;
final
OverlayState
?
overlay
=
navigator
?.
overlay
;
// If the navigator or the overlay was removed before this end-of-frame
// callback was called, then don't actually start a transition, and we don'
//
t
have to worry about any Hero widget we might have hidden in a previous
// callback was called, then don't actually start a transition, and we don'
t
// have to worry about any Hero widget we might have hidden in a previous
// flight, or ongoing flights.
if
(
navigator
==
null
||
overlay
==
null
)
{
return
;
...
...
@@ -998,7 +1005,35 @@ class HeroController extends NavigatorObserver {
BuildContext
toHeroContext
,
)
{
final
Hero
toHero
=
toHeroContext
.
widget
as
Hero
;
return
toHero
.
child
;
final
MediaQueryData
?
toMediaQueryData
=
MediaQuery
.
maybeOf
(
toHeroContext
);
final
MediaQueryData
?
fromMediaQueryData
=
MediaQuery
.
maybeOf
(
fromHeroContext
);
if
(
toMediaQueryData
==
null
||
fromMediaQueryData
==
null
)
{
return
toHero
.
child
;
}
final
EdgeInsets
fromHeroPadding
=
fromMediaQueryData
.
padding
;
final
EdgeInsets
toHeroPadding
=
toMediaQueryData
.
padding
;
return
AnimatedBuilder
(
animation:
animation
,
builder:
(
BuildContext
context
,
Widget
?
child
)
{
return
MediaQuery
(
data:
toMediaQueryData
.
copyWith
(
padding:
(
flightDirection
==
HeroFlightDirection
.
push
)
?
EdgeInsetsTween
(
begin:
fromHeroPadding
,
end:
toHeroPadding
,
).
evaluate
(
animation
)
:
EdgeInsetsTween
(
begin:
toHeroPadding
,
end:
fromHeroPadding
,
).
evaluate
(
animation
),
),
child:
toHero
.
child
);
},
);
}
}
...
...
packages/flutter/test/widgets/heroes_test.dart
View file @
dc06326c
...
...
@@ -3,6 +3,7 @@
// found in the LICENSE file.
import
'dart:ui'
as
ui
;
import
'dart:ui'
show
WindowPadding
;
import
'package:flutter/cupertino.dart'
;
import
'package:flutter/foundation.dart'
;
...
...
@@ -183,6 +184,25 @@ class MyStatefulWidgetState extends State<MyStatefulWidget> {
Widget
build
(
BuildContext
context
)
=>
Text
(
widget
.
value
);
}
class
FakeWindowPadding
implements
WindowPadding
{
const
FakeWindowPadding
({
this
.
left
=
0.0
,
this
.
top
=
0.0
,
this
.
right
=
0.0
,
this
.
bottom
=
0.0
,
});
@override
final
double
left
;
@override
final
double
top
;
@override
final
double
right
;
@override
final
double
bottom
;
}
Future
<
void
>
main
()
async
{
final
ui
.
Image
testImage
=
await
createTestImage
();
assert
(
testImage
!=
null
);
...
...
@@ -3073,4 +3093,70 @@ Future<void> main() async {
await
tester
.
pumpAndSettle
();
expect
(
tester
.
takeException
(),
isNull
);
});
testWidgets
(
'smooth transition between different incoming data'
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
<
NavigatorState
>
navigatorKey
=
GlobalKey
();
const
Key
imageKey1
=
Key
(
'image1'
);
const
Key
imageKey2
=
Key
(
'image2'
);
final
TestImageProvider
imageProvider
=
TestImageProvider
(
testImage
);
final
TestWidgetsFlutterBinding
testBinding
=
tester
.
binding
;
testBinding
.
window
.
paddingTestValue
=
const
FakeWindowPadding
(
top:
50
);
await
tester
.
pumpWidget
(
MaterialApp
(
navigatorKey:
navigatorKey
,
home:
Scaffold
(
appBar:
AppBar
(
title:
const
Text
(
'test'
)),
body:
Hero
(
tag:
'imageHero'
,
child:
GridView
.
count
(
crossAxisCount:
3
,
shrinkWrap:
true
,
children:
<
Widget
>[
Image
(
image:
imageProvider
,
key:
imageKey1
),
],
),
),
),
),
);
final
MaterialPageRoute
<
void
>
route2
=
MaterialPageRoute
<
void
>(
builder:
(
BuildContext
context
)
{
return
Scaffold
(
body:
Hero
(
tag:
'imageHero'
,
child:
GridView
.
count
(
crossAxisCount:
3
,
shrinkWrap:
true
,
children:
<
Widget
>[
Image
(
image:
imageProvider
,
key:
imageKey2
),
],
),
),
);
},
);
// Load images.
imageProvider
.
complete
();
await
tester
.
pump
();
final
double
forwardRest
=
tester
.
getTopLeft
(
find
.
byType
(
Image
)).
dy
;
navigatorKey
.
currentState
!.
push
(
route2
);
await
tester
.
pump
();
await
tester
.
pump
(
const
Duration
(
milliseconds:
1
));
expect
(
tester
.
getTopLeft
(
find
.
byType
(
Image
)).
dy
,
moreOrLessEquals
(
forwardRest
,
epsilon:
0.1
));
await
tester
.
pumpAndSettle
();
navigatorKey
.
currentState
!.
pop
(
route2
);
await
tester
.
pump
();
await
tester
.
pump
(
const
Duration
(
milliseconds:
300
));
expect
(
tester
.
getTopLeft
(
find
.
byType
(
Image
)).
dy
,
moreOrLessEquals
(
forwardRest
,
epsilon:
0.1
));
await
tester
.
pumpAndSettle
();
expect
(
tester
.
getTopLeft
(
find
.
byType
(
Image
)).
dy
,
moreOrLessEquals
(
forwardRest
,
epsilon:
0.1
));
testBinding
.
window
.
clearAllTestValues
();
},
);
}
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