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
72267a6c
Unverified
Commit
72267a6c
authored
Oct 31, 2020
by
najeira
Committed by
GitHub
Oct 31, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add HeroMode widget (#48223)
parent
f03ac0be
Changes
3
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
288 additions
and
9 deletions
+288
-9
tab_scaffold.dart
packages/flutter/lib/src/cupertino/tab_scaffold.dart
+12
-9
heroes.dart
packages/flutter/lib/src/widgets/heroes.dart
+42
-0
heroes_test.dart
packages/flutter/test/widgets/heroes_test.dart
+234
-0
No files found.
packages/flutter/lib/src/cupertino/tab_scaffold.dart
View file @
72267a6c
...
...
@@ -576,7 +576,9 @@ class _TabSwitchingViewState extends State<_TabSwitchingView> {
final
bool
active
=
index
==
widget
.
currentTabIndex
;
shouldBuildTab
[
index
]
=
active
||
shouldBuildTab
[
index
];
return
Offstage
(
return
HeroMode
(
enabled:
active
,
child:
Offstage
(
offstage:
!
active
,
child:
TickerMode
(
enabled:
active
,
...
...
@@ -587,6 +589,7 @@ class _TabSwitchingViewState extends State<_TabSwitchingView> {
}),
),
),
),
);
}),
);
...
...
packages/flutter/lib/src/widgets/heroes.dart
View file @
72267a6c
...
...
@@ -305,6 +305,8 @@ class Hero extends StatefulWidget {
inviteHero
(
hero
,
tag
);
}
}
}
else
if
(
widget
is
HeroMode
&&
!
widget
.
enabled
)
{
return
;
}
element
.
visitChildren
(
visitor
);
}
...
...
@@ -917,3 +919,43 @@ class HeroController extends NavigatorObserver {
return
toHero
.
child
;
};
}
/// Enables or disables [Hero]es in the widget subtree.
///
/// When [enabled] is false, all [Hero] widgets in this subtree will not be
/// involved in hero animations.
///
/// When [enabled] is true (the default), [Hero] widgets may be involved in
/// hero animations, as usual.
class
HeroMode
extends
StatelessWidget
{
/// Creates a widget that enables or disables [Hero]es.
///
/// The [child] and [enabled] arguments must not be null.
const
HeroMode
({
Key
?
key
,
required
this
.
child
,
this
.
enabled
=
true
,
})
:
assert
(
child
!=
null
),
assert
(
enabled
!=
null
),
super
(
key:
key
);
/// The subtree to place inside the [HeroMode].
final
Widget
child
;
/// Whether or not [Hero]es are enabled in this subtree.
///
/// If this property is false, the [Hero]es in this subtree will not animate
/// on route changes. Otherwise, they will animate as usual.
///
/// Defaults to true and must not be null.
final
bool
enabled
;
@override
Widget
build
(
BuildContext
context
)
=>
child
;
@override
void
debugFillProperties
(
DiagnosticPropertiesBuilder
properties
)
{
super
.
debugFillProperties
(
properties
);
properties
.
add
(
FlagProperty
(
'mode'
,
value:
enabled
,
ifTrue:
'enabled'
,
ifFalse:
'disabled'
,
showName:
true
));
}
}
packages/flutter/test/widgets/heroes_test.dart
View file @
72267a6c
...
...
@@ -2022,6 +2022,90 @@ Future<void> main() async {
expect
(
tester
.
takeException
(),
isAssertionError
);
});
testWidgets
(
'Can push/pop on outer Navigator if nested Navigators contains same Heroes'
,
(
WidgetTester
tester
)
async
{
const
String
heroTag
=
'foo'
;
final
GlobalKey
<
NavigatorState
>
rootNavigator
=
GlobalKey
<
NavigatorState
>();
final
Key
rootRouteHero
=
UniqueKey
();
final
Key
nestedRouteHeroOne
=
UniqueKey
();
final
Key
nestedRouteHeroTwo
=
UniqueKey
();
final
List
<
Key
>
keys
=
<
Key
>[
nestedRouteHeroOne
,
nestedRouteHeroTwo
];
await
tester
.
pumpWidget
(
CupertinoApp
(
navigatorKey:
rootNavigator
,
home:
CupertinoTabScaffold
(
tabBar:
CupertinoTabBar
(
items:
const
<
BottomNavigationBarItem
>[
BottomNavigationBarItem
(
icon:
Icon
(
Icons
.
home
)),
BottomNavigationBarItem
(
icon:
Icon
(
Icons
.
favorite
)),
],
),
tabBuilder:
(
BuildContext
context
,
int
index
)
{
return
CupertinoTabView
(
builder:
(
BuildContext
context
)
=>
Hero
(
tag:
heroTag
,
child:
Placeholder
(
key:
keys
[
index
],
),
),
);
},
),
),
);
// Show both tabs to init.
await
tester
.
tap
(
find
.
byIcon
(
Icons
.
home
));
await
tester
.
pump
();
await
tester
.
tap
(
find
.
byIcon
(
Icons
.
favorite
));
await
tester
.
pump
();
// Inner heroes are in the tree, one is offstage.
expect
(
find
.
byKey
(
nestedRouteHeroTwo
),
findsOneWidget
);
expect
(
find
.
byKey
(
nestedRouteHeroOne
),
findsNothing
);
expect
(
find
.
byKey
(
nestedRouteHeroOne
,
skipOffstage:
false
),
findsOneWidget
);
// Root hero is not in the tree.
expect
(
find
.
byKey
(
rootRouteHero
),
findsNothing
);
rootNavigator
.
currentState
!.
push
(
MaterialPageRoute
<
void
>(
builder:
(
BuildContext
context
)
=>
Hero
(
tag:
heroTag
,
child:
Placeholder
(
key:
rootRouteHero
,
),
),
),
);
await
tester
.
pumpAndSettle
();
// Inner heroes are still in the tree, both are offstage.
expect
(
find
.
byKey
(
nestedRouteHeroOne
),
findsNothing
);
expect
(
find
.
byKey
(
nestedRouteHeroTwo
),
findsNothing
);
expect
(
find
.
byKey
(
nestedRouteHeroOne
,
skipOffstage:
false
),
findsOneWidget
);
expect
(
find
.
byKey
(
nestedRouteHeroTwo
,
skipOffstage:
false
),
findsOneWidget
);
// Root hero is in the tree.
expect
(
find
.
byKey
(
rootRouteHero
),
findsOneWidget
);
// Doesn't crash.
expect
(
tester
.
takeException
(),
isNull
);
rootNavigator
.
currentState
!.
pop
();
await
tester
.
pumpAndSettle
();
// Root hero is not in the tree
expect
(
find
.
byKey
(
rootRouteHero
),
findsNothing
);
// Both heroes are in the tree, one is offstage
expect
(
find
.
byKey
(
nestedRouteHeroTwo
),
findsOneWidget
);
expect
(
find
.
byKey
(
nestedRouteHeroOne
),
findsNothing
);
expect
(
find
.
byKey
(
nestedRouteHeroOne
,
skipOffstage:
false
),
findsOneWidget
);
});
testWidgets
(
'Hero within a Hero subtree, throws'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
MaterialApp
(
...
...
@@ -2546,4 +2630,154 @@ Future<void> main() async {
heroSize
=
tester
.
getSize
(
find
.
byKey
(
container1
));
expect
(
heroSize
,
tween
.
transform
(
1.0
));
});
testWidgets
(
'Heroes in enabled HeroMode do transition'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Material
(
child:
Column
(
children:
<
Widget
>[
HeroMode
(
enabled:
true
,
child:
Card
(
child:
Hero
(
tag:
'a'
,
child:
SizedBox
(
height:
100.0
,
width:
100.0
,
key:
firstKey
,
),
),
),
),
Builder
(
builder:
(
BuildContext
context
)
{
return
FlatButton
(
child:
const
Text
(
'push'
),
onPressed:
()
{
Navigator
.
push
(
context
,
PageRouteBuilder
<
void
>(
pageBuilder:
(
BuildContext
context
,
Animation
<
double
>
_
,
Animation
<
double
>
__
)
{
return
Card
(
child:
Hero
(
tag:
'a'
,
child:
SizedBox
(
height:
150.0
,
width:
150.0
,
key:
secondKey
,
),
),
);
},
));
},
);
},
),
],
),
),
));
expect
(
find
.
byKey
(
firstKey
),
isOnstage
);
expect
(
find
.
byKey
(
firstKey
),
isInCard
);
expect
(
find
.
byKey
(
secondKey
),
findsNothing
);
await
tester
.
tap
(
find
.
text
(
'push'
));
await
tester
.
pump
();
expect
(
find
.
byKey
(
firstKey
),
isOnstage
);
expect
(
find
.
byKey
(
firstKey
),
isInCard
);
expect
(
find
.
byKey
(
secondKey
,
skipOffstage:
false
),
isOffstage
);
expect
(
find
.
byKey
(
secondKey
,
skipOffstage:
false
),
isInCard
);
await
tester
.
pump
();
expect
(
find
.
byKey
(
firstKey
),
findsNothing
);
expect
(
find
.
byKey
(
secondKey
),
findsOneWidget
);
expect
(
find
.
byKey
(
secondKey
),
isNotInCard
);
expect
(
find
.
byKey
(
secondKey
),
isOnstage
);
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
expect
(
find
.
byKey
(
firstKey
),
findsNothing
);
expect
(
find
.
byKey
(
secondKey
),
isOnstage
);
expect
(
find
.
byKey
(
secondKey
),
isInCard
);
});
testWidgets
(
'Heroes in disabled HeroMode do not transition'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Material
(
child:
Column
(
children:
<
Widget
>[
HeroMode
(
enabled:
false
,
child:
Card
(
child:
Hero
(
tag:
'a'
,
child:
SizedBox
(
height:
100.0
,
width:
100.0
,
key:
firstKey
,
),
),
),
),
Builder
(
builder:
(
BuildContext
context
)
{
return
FlatButton
(
child:
const
Text
(
'push'
),
onPressed:
()
{
Navigator
.
push
(
context
,
PageRouteBuilder
<
void
>(
pageBuilder:
(
BuildContext
context
,
Animation
<
double
>
_
,
Animation
<
double
>
__
)
{
return
Card
(
child:
Hero
(
tag:
'a'
,
child:
SizedBox
(
height:
150.0
,
width:
150.0
,
key:
secondKey
,
),
),
);
},
));
},
);
},
),
],
),
),
));
expect
(
find
.
byKey
(
firstKey
),
isOnstage
);
expect
(
find
.
byKey
(
firstKey
),
isInCard
);
expect
(
find
.
byKey
(
secondKey
),
findsNothing
);
await
tester
.
tap
(
find
.
text
(
'push'
));
await
tester
.
pump
();
expect
(
find
.
byKey
(
firstKey
),
isOnstage
);
expect
(
find
.
byKey
(
firstKey
),
isInCard
);
expect
(
find
.
byKey
(
secondKey
,
skipOffstage:
false
),
isOffstage
);
expect
(
find
.
byKey
(
secondKey
,
skipOffstage:
false
),
isInCard
);
await
tester
.
pump
();
// When HeroMode is disabled, heroes will not move.
// So the original page contains the hero.
expect
(
find
.
byKey
(
firstKey
),
findsOneWidget
);
// The hero should be in the new page, onstage, soon.
expect
(
find
.
byKey
(
secondKey
),
findsOneWidget
);
expect
(
find
.
byKey
(
secondKey
),
isInCard
);
expect
(
find
.
byKey
(
secondKey
),
isOnstage
);
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
expect
(
find
.
byKey
(
firstKey
),
findsNothing
);
expect
(
find
.
byKey
(
secondKey
),
findsOneWidget
);
expect
(
find
.
byKey
(
secondKey
),
isInCard
);
expect
(
find
.
byKey
(
secondKey
),
isOnstage
);
});
}
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