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
03b117a5
Commit
03b117a5
authored
Aug 19, 2016
by
Hans Muller
Committed by
GitHub
Aug 19, 2016
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Remove the "most valuable keys" Hero feature (#5500)
parent
f2afd05f
Changes
9
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
56 additions
and
160 deletions
+56
-160
grid_list_demo.dart
examples/flutter_gallery/lib/demo/grid_list_demo.dart
+3
-13
shrine_home.dart
examples/flutter_gallery/lib/demo/shrine/shrine_home.dart
+1
-1
shrine_order.dart
examples/flutter_gallery/lib/demo/shrine/shrine_order.dart
+1
-1
stock_home.dart
examples/stocks/lib/stock_home.dart
+5
-10
stock_list.dart
examples/stocks/lib/stock_list.dart
+1
-3
stock_row.dart
examples/stocks/lib/stock_row.dart
+4
-38
stock_symbol_viewer.dart
examples/stocks/lib/stock_symbol_viewer.dart
+18
-11
heroes.dart
packages/flutter/lib/src/widgets/heroes.dart
+15
-54
navigator.dart
packages/flutter/lib/src/widgets/navigator.dart
+8
-29
No files found.
examples/flutter_gallery/lib/demo/grid_list_demo.dart
View file @
03b117a5
...
...
@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'dart:collection'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/widgets.dart'
;
...
...
@@ -21,12 +19,11 @@ class Photo {
final
String
caption
;
bool
isFavorite
;
String
get
tag
=>
assetName
;
// Assuming that all asset names are unique.
bool
get
isValid
=>
assetName
!=
null
&&
title
!=
null
&&
caption
!=
null
&&
isFavorite
!=
null
;
}
const
String
photoHeroTag
=
'Photo'
;
typedef
void
BannerTapCallback
(
Photo
photo
);
class
GridDemoPhotoItem
extends
StatelessWidget
{
...
...
@@ -46,21 +43,14 @@ class GridDemoPhotoItem extends StatelessWidget {
final
BannerTapCallback
onBannerTap
;
// User taps on the photo's header or footer.
void
showPhoto
(
BuildContext
context
)
{
Key
photoKey
=
new
Key
(
photo
.
assetName
);
Set
<
Key
>
mostValuableKeys
=
new
HashSet
<
Key
>();
mostValuableKeys
.
add
(
photoKey
);
Navigator
.
push
(
context
,
new
MaterialPageRoute
<
Null
>(
settings:
new
RouteSettings
(
mostValuableKeys:
mostValuableKeys
),
builder:
(
BuildContext
context
)
{
return
new
Scaffold
(
appBar:
new
AppBar
(
title:
new
Text
(
photo
.
title
)
),
body:
new
Hero
(
tag:
photo
HeroT
ag
,
tag:
photo
.
t
ag
,
child:
new
Image
.
asset
(
photo
.
assetName
,
fit:
ImageFit
.
cover
)
)
);
...
...
@@ -74,7 +64,7 @@ class GridDemoPhotoItem extends StatelessWidget {
onTap:
()
{
showPhoto
(
context
);
},
child:
new
Hero
(
key:
new
Key
(
photo
.
assetName
),
tag:
photo
HeroT
ag
,
tag:
photo
.
t
ag
,
child:
new
Image
.
asset
(
photo
.
assetName
,
fit:
ImageFit
.
cover
)
)
);
...
...
examples/flutter_gallery/lib/demo/shrine/shrine_home.dart
View file @
03b117a5
...
...
@@ -292,7 +292,7 @@ class ShrineHome extends StatefulWidget {
}
class
_ShrineHomeState
extends
State
<
ShrineHome
>
{
static
final
GlobalKey
<
ScaffoldState
>
scaffoldKey
=
new
GlobalKey
<
ScaffoldState
>(
debugLabel:
'
Order pag
e'
);
static
final
GlobalKey
<
ScaffoldState
>
scaffoldKey
=
new
GlobalKey
<
ScaffoldState
>(
debugLabel:
'
Shrine Hom
e'
);
static
final
GlobalKey
<
ScrollableState
>
scrollableKey
=
new
GlobalKey
<
ScrollableState
>();
static
final
GridDelegate
gridDelegate
=
new
ShrineGridDelegate
();
...
...
examples/flutter_gallery/lib/demo/shrine/shrine_order.dart
View file @
03b117a5
...
...
@@ -136,7 +136,7 @@ class OrderPage extends StatefulWidget {
/// arranged in two columns. Enables the user to specify a quantity and add an
/// order to the shopping cart.
class
_OrderPageState
extends
State
<
OrderPage
>
{
static
final
GlobalKey
<
ScaffoldState
>
scaffoldKey
=
new
GlobalKey
<
ScaffoldState
>(
debugLabel:
'
Order page
'
);
static
final
GlobalKey
<
ScaffoldState
>
scaffoldKey
=
new
GlobalKey
<
ScaffoldState
>(
debugLabel:
'
Shrine Order
'
);
static
final
GlobalKey
<
ScrollableState
>
scrollableKey
=
new
GlobalKey
<
ScrollableState
>();
Order
get
currentOrder
=>
ShrineOrderRoute
.
of
(
context
).
order
;
...
...
examples/stocks/lib/stock_home.dart
View file @
03b117a5
...
...
@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'dart:collection'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/rendering.dart'
show
debugDumpRenderTree
,
debugDumpLayerTree
,
debugDumpSemanticsTree
;
import
'package:flutter/scheduler.dart'
show
timeDilation
;
...
...
@@ -245,7 +243,7 @@ class StockHomeState extends State<StockHome> {
return
stocks
.
where
((
Stock
stock
)
=>
stock
.
symbol
.
contains
(
regexp
));
}
void
_buyStock
(
Stock
stock
,
Key
arrowKey
)
{
void
_buyStock
(
Stock
stock
)
{
setState
(()
{
stock
.
percentChange
=
100.0
*
(
1.0
/
stock
.
lastSale
);
stock
.
lastSale
+=
1.0
;
...
...
@@ -255,7 +253,7 @@ class StockHomeState extends State<StockHome> {
action:
new
SnackBarAction
(
label:
"BUY MORE"
,
onPressed:
()
{
_buyStock
(
stock
,
arrowKey
);
_buyStock
(
stock
);
}
)
));
...
...
@@ -263,15 +261,12 @@ class StockHomeState extends State<StockHome> {
Widget
_buildStockList
(
BuildContext
context
,
Iterable
<
Stock
>
stocks
,
StockHomeTab
tab
)
{
return
new
StockList
(
keySalt:
tab
,
stocks:
stocks
.
toList
(),
onAction:
_buyStock
,
onOpen:
(
Stock
stock
,
Key
arrowKey
)
{
Set
<
Key
>
mostValuableKeys
=
new
HashSet
<
Key
>();
mostValuableKeys
.
add
(
arrowKey
);
Navigator
.
pushNamed
(
context
,
'/stock/
${stock.symbol}
'
,
mostValuableKeys:
mostValuableKeys
);
onOpen:
(
Stock
stock
)
{
Navigator
.
pushNamed
(
context
,
'/stock/
${stock.symbol}
'
);
},
onShow:
(
Stock
stock
,
Key
arrowKey
)
{
onShow:
(
Stock
stock
)
{
_scaffoldKey
.
currentState
.
showBottomSheet
((
BuildContext
context
)
=>
new
StockSymbolBottomSheet
(
stock:
stock
));
}
);
...
...
examples/stocks/lib/stock_list.dart
View file @
03b117a5
...
...
@@ -8,9 +8,8 @@ import 'stock_data.dart';
import
'stock_row.dart'
;
class
StockList
extends
StatelessWidget
{
StockList
({
Key
key
,
this
.
keySalt
,
this
.
stocks
,
this
.
onOpen
,
this
.
onShow
,
this
.
onAction
})
:
super
(
key:
key
);
StockList
({
Key
key
,
this
.
stocks
,
this
.
onOpen
,
this
.
onShow
,
this
.
onAction
})
:
super
(
key:
key
);
final
Object
keySalt
;
final
List
<
Stock
>
stocks
;
final
StockRowActionCallback
onOpen
;
final
StockRowActionCallback
onShow
;
...
...
@@ -23,7 +22,6 @@ class StockList extends StatelessWidget {
itemExtent:
StockRow
.
kHeight
,
children:
stocks
.
map
((
Stock
stock
)
{
return
new
StockRow
(
keySalt:
keySalt
,
stock:
stock
,
onPressed:
onOpen
,
onDoubleTap:
onShow
,
...
...
examples/stocks/lib/stock_row.dart
View file @
03b117a5
...
...
@@ -7,58 +7,25 @@ import 'package:flutter/material.dart';
import
'stock_data.dart'
;
import
'stock_arrow.dart'
;
enum
StockRowPartKind
{
arrow
}
class
StockRowPartKey
extends
LocalKey
{
const
StockRowPartKey
(
this
.
keySalt
,
this
.
stock
,
this
.
part
);
final
Object
keySalt
;
final
Stock
stock
;
final
StockRowPartKind
part
;
@override
bool
operator
==(
dynamic
other
)
{
if
(
identical
(
this
,
other
))
return
true
;
if
(
other
.
runtimeType
!=
runtimeType
)
return
false
;
final
StockRowPartKey
typedOther
=
other
;
return
keySalt
==
typedOther
.
keySalt
&&
stock
==
typedOther
.
stock
&&
part
==
typedOther
.
part
;
}
@override
int
get
hashCode
=>
hashValues
(
keySalt
,
stock
,
part
);
@override
String
toString
()
=>
'[
$runtimeType
${keySalt.toString().split(".")[1]}
:
${stock.symbol}
:
${part.toString().split(".")[1]}
]'
;
}
typedef
void
StockRowActionCallback
(
Stock
stock
,
Key
arrowKey
);
typedef
void
StockRowActionCallback
(
Stock
stock
);
class
StockRow
extends
StatelessWidget
{
StockRow
({
Stock
stock
,
Object
keySalt
,
this
.
onPressed
,
this
.
onDoubleTap
,
this
.
onLongPressed
})
:
this
.
stock
=
stock
,
_arrowKey
=
new
StockRowPartKey
(
keySalt
,
stock
,
StockRowPartKind
.
arrow
),
super
(
key:
new
ObjectKey
(
stock
));
})
:
this
.
stock
=
stock
,
super
(
key:
new
ObjectKey
(
stock
));
final
Stock
stock
;
final
StockRowActionCallback
onPressed
;
final
StockRowActionCallback
onDoubleTap
;
final
StockRowActionCallback
onLongPressed
;
final
Key
_arrowKey
;
static
const
double
kHeight
=
79.0
;
GestureTapCallback
_getHandler
(
StockRowActionCallback
callback
)
{
return
callback
==
null
?
null
:
()
=>
callback
(
stock
,
_arrowKey
);
return
callback
==
null
?
null
:
()
=>
callback
(
stock
);
}
@override
...
...
@@ -83,8 +50,7 @@ class StockRow extends StatelessWidget {
new
Container
(
margin:
const
EdgeInsets
.
only
(
right:
5.0
),
child:
new
Hero
(
tag:
StockRowPartKind
.
arrow
,
key:
_arrowKey
,
tag:
stock
,
child:
new
StockArrow
(
percentChange:
stock
.
percentChange
)
)
),
...
...
examples/stocks/lib/stock_symbol_viewer.dart
View file @
03b117a5
...
...
@@ -6,12 +6,12 @@ import 'package:flutter/material.dart';
import
'stock_data.dart'
;
import
'stock_arrow.dart'
;
import
'stock_row.dart'
;
class
StockSymbolView
extends
StatelessWidget
{
StockSymbolView
({
this
.
stock
});
class
_
StockSymbolView
extends
StatelessWidget
{
_StockSymbolView
({
this
.
stock
,
this
.
arrow
});
final
Stock
stock
;
final
Widget
arrow
;
@override
Widget
build
(
BuildContext
context
)
{
...
...
@@ -31,12 +31,7 @@ class StockSymbolView extends StatelessWidget {
'
${stock.symbol}
'
,
style:
Theme
.
of
(
context
).
textTheme
.
display2
),
new
Hero
(
key:
new
ObjectKey
(
stock
),
tag:
StockRowPartKind
.
arrow
,
turns:
2
,
child:
new
StockArrow
(
percentChange:
stock
.
percentChange
)
),
arrow
,
],
mainAxisAlignment:
MainAxisAlignment
.
spaceBetween
),
...
...
@@ -82,7 +77,16 @@ class StockSymbolPage extends StatelessWidget {
children:
<
Widget
>[
new
Container
(
margin:
new
EdgeInsets
.
all
(
20.0
),
child:
new
Card
(
child:
new
StockSymbolView
(
stock:
stock
))
child:
new
Card
(
child:
new
_StockSymbolView
(
stock:
stock
,
arrow:
new
Hero
(
tag:
stock
,
turns:
2
,
child:
new
StockArrow
(
percentChange:
stock
.
percentChange
)
)
)
)
)
]
)
...
...
@@ -102,7 +106,10 @@ class StockSymbolBottomSheet extends StatelessWidget {
decoration:
new
BoxDecoration
(
border:
new
Border
(
top:
new
BorderSide
(
color:
Colors
.
black26
))
),
child:
new
StockSymbolView
(
stock:
stock
)
child:
new
_StockSymbolView
(
stock:
stock
,
arrow:
new
StockArrow
(
percentChange:
stock
.
percentChange
)
)
);
}
}
packages/flutter/lib/src/widgets/heroes.dart
View file @
03b117a5
...
...
@@ -20,14 +20,9 @@ import 'transitions.dart';
// album's details view. In this context, a screen is a navigator ModalRoute.
// To get this effect, all you have to do is wrap each hero on each route with a
// Hero widget, and give each hero a tag. Tag must either be unique within the
// current route's widget subtree, or all the Heroes with that tag on a
// particular route must have a key. When the app transitions from one route to
// another, each tag present is animated. When there's exactly one hero with
// that tag, that hero will be animated for that tag. When there are multiple
// heroes in a route with the same tag, then whichever hero has a key that
// matches one of the keys in the "most important key" list given to the
// navigator when the route was pushed will be animated. If a hero is only
// Hero widget, and give each hero a tag. The tag must either be unique within the
// current route's widget subtree. When the app transitions from one route to
// another, each hero is animated to its new location. If a hero is only
// present on one of the routes and not the other, then it will be made to
// appear or disappear as needed.
...
...
@@ -96,57 +91,35 @@ class Hero extends StatefulWidget {
final
int
turns
;
/// If true, the hero will always animate, even if it has no matching hero to
/// animate to or from. (This only applies if the hero is relevant; if there
/// are multiple heroes with the same tag, only the one whose key matches the
/// "most valuable keys" will be used.)
/// animate to or from.
final
bool
alwaysAnimate
;
static
Map
<
Object
,
HeroHandle
>
of
(
BuildContext
context
,
Set
<
Key
>
mostValuableKeys
)
{
mostValuableKeys
??=
new
HashSet
<
Key
>();
assert
(!
mostValuableKeys
.
contains
(
null
));
// first we collect ALL the heroes, sorted by their tags
Map
<
Object
,
Map
<
Key
,
HeroState
>>
heroes
=
<
Object
,
Map
<
Key
,
HeroState
>>{};
/// Return a hero tag to HeroState map of all of the heroes within the given subtree.
static
Map
<
Object
,
HeroHandle
>
of
(
BuildContext
context
)
{
final
Map
<
Object
,
HeroHandle
>
result
=
<
Object
,
HeroHandle
>{};
void
visitor
(
Element
element
)
{
if
(
element
.
widget
is
Hero
)
{
StatefulElement
hero
=
element
;
Hero
heroWidget
=
element
.
widget
;
Object
tag
=
heroWidget
.
tag
;
assert
(
tag
!=
null
);
Key
key
=
heroWidget
.
key
;
final
Map
<
Key
,
HeroState
>
tagHeroes
=
heroes
.
putIfAbsent
(
tag
,
()
=>
<
Key
,
HeroState
>{});
assert
(()
{
if
(
tagHeroes
.
containsKey
(
key
))
{
if
(
result
.
containsKey
(
tag
))
{
new
FlutterError
(
'There are multiple heroes that share the same
key within the same
subtree.
\n
'
'There are multiple heroes that share the same
tag within a
subtree.
\n
'
'Within each subtree for which heroes are to be animated (typically a PageRoute subtree), '
'either each Hero must have a unique tag, or, all the heroes with a particular tag must '
'have different keys.
\n
'
'In this case, the tag "
$tag
" had multiple heroes with the key "
$key
".'
'each Hero must have a unique non-null tag.
\n
'
'In this case, multiple heroes had the tag "
$tag
".'
);
}
return
true
;
});
tagHeroes
[
key
]
=
hero
.
state
;
HeroState
heroState
=
hero
.
state
;
result
[
tag
]
=
heroState
;
}
element
.
visitChildren
(
visitor
);
}
context
.
visitChildElements
(
visitor
);
// next, for each tag, we're going to decide on the one hero we care about for that tag
Map
<
Object
,
HeroHandle
>
result
=
<
Object
,
HeroHandle
>{};
for
(
Object
tag
in
heroes
.
keys
)
{
assert
(
tag
!=
null
);
if
(
heroes
[
tag
].
length
==
1
)
{
result
[
tag
]
=
heroes
[
tag
].
values
.
first
;
}
else
{
assert
(
heroes
[
tag
].
length
>
1
);
assert
(!
heroes
[
tag
].
containsKey
(
null
));
assert
(
heroes
[
tag
].
keys
.
where
((
Key
key
)
=>
mostValuableKeys
.
contains
(
key
)).
length
<=
1
);
Key
mostValuableKey
=
mostValuableKeys
.
firstWhere
((
Key
key
)
=>
heroes
[
tag
].
containsKey
(
key
),
orElse:
()
=>
null
);
if
(
mostValuableKey
!=
null
)
result
[
tag
]
=
heroes
[
tag
][
mostValuableKey
];
}
}
assert
(!
result
.
containsKey
(
null
));
return
result
;
}
...
...
@@ -532,27 +505,15 @@ class HeroController extends NavigatorObserver {
return
entry
;
}
Set
<
Key
>
_getMostValuableKeys
()
{
assert
(
_from
!=
null
);
assert
(
_to
!=
null
);
Set
<
Key
>
result
=
new
HashSet
<
Key
>();
if
(
_from
.
settings
.
mostValuableKeys
!=
null
)
result
.
addAll
(
_from
.
settings
.
mostValuableKeys
);
if
(
_to
.
settings
.
mostValuableKeys
!=
null
)
result
.
addAll
(
_to
.
settings
.
mostValuableKeys
);
return
result
;
}
void
_updateQuest
(
Duration
timeStamp
)
{
if
(
navigator
==
null
)
{
// The navigator was removed before this end-of-frame callback was called.
return
;
}
Set
<
Key
>
mostValuableKeys
=
_getMostValuableKeys
();
Map
<
Object
,
HeroHandle
>
heroesFrom
=
_party
.
isEmpty
?
Hero
.
of
(
_from
.
subtreeContext
,
mostValuableKeys
)
:
_party
.
getHeroesToAnimate
();
Hero
.
of
(
_from
.
subtreeContext
)
:
_party
.
getHeroesToAnimate
();
Map
<
Object
,
HeroHandle
>
heroesTo
=
Hero
.
of
(
_to
.
subtreeContext
,
mostValuableKeys
);
Map
<
Object
,
HeroHandle
>
heroesTo
=
Hero
.
of
(
_to
.
subtreeContext
);
_to
.
offstage
=
false
;
Animation
<
double
>
animation
=
_animation
;
...
...
packages/flutter/lib/src/widgets/navigator.dart
View file @
03b117a5
...
...
@@ -111,7 +111,6 @@ class RouteSettings {
/// Creates data used to construct routes.
const
RouteSettings
({
this
.
name
,
this
.
mostValuableKeys
,
this
.
isInitialRoute
:
false
});
...
...
@@ -120,29 +119,13 @@ class RouteSettings {
/// If null, the route is anonymous.
final
String
name
;
/// The set of keys that are most relevant for constructoring [Hero]
/// transitions. For example, if the current route contains a list of music
/// albums and the user triggered this navigation by tapping one of the
/// albums, the most valuable album cover is the one associated with the album
/// the user tapped and is the one that should heroically transition when
/// opening the details page for that album.
final
Set
<
Key
>
mostValuableKeys
;
/// Whether this route is the very first route being pushed onto this [Navigator].
///
/// The initial route typically skips any entrance transition to speed startup.
final
bool
isInitialRoute
;
@override
String
toString
()
{
String
result
=
'"
$name
"'
;
if
(
mostValuableKeys
!=
null
&&
mostValuableKeys
.
isNotEmpty
)
{
result
+=
'; keys:'
;
for
(
Key
key
in
mostValuableKeys
)
result
+=
'
$key
'
;
}
return
result
;
}
String
toString
()
=>
'"
$name
"'
;
}
/// Creates a route for the given route settings.
...
...
@@ -211,10 +194,9 @@ class Navigator extends StatefulWidget {
/// Push a named route onto the navigator that most tightly encloses the given context.
///
/// The route name will be passed to that navigator's [onGenerateRoute]
/// callback. The returned route will be pushed into the navigator. The set of
/// most valuable keys will be used to construct an appropriate [Hero] transition.
static
void
pushNamed
(
BuildContext
context
,
String
routeName
,
{
Set
<
Key
>
mostValuableKeys
})
{
Navigator
.
of
(
context
).
pushNamed
(
routeName
,
mostValuableKeys:
mostValuableKeys
);
/// callback. The returned route will be pushed into the navigator.
static
void
pushNamed
(
BuildContext
context
,
String
routeName
)
{
Navigator
.
of
(
context
).
pushNamed
(
routeName
);
}
/// Push a route onto the navigator that most tightly encloses the given context.
...
...
@@ -265,10 +247,10 @@ class Navigator extends StatefulWidget {
/// Executes a simple transaction that both pops the current route off and
/// pushes a named route into the navigator that most tightly encloses the given context.
static
void
popAndPushNamed
(
BuildContext
context
,
String
routeName
,
{
Set
<
Key
>
mostValuableKeys
}
)
{
static
void
popAndPushNamed
(
BuildContext
context
,
String
routeName
)
{
Navigator
.
of
(
context
)
..
pop
()
..
pushNamed
(
routeName
,
mostValuableKeys:
mostValuableKeys
);
..
pushNamed
(
routeName
);
}
static
NavigatorState
of
(
BuildContext
context
)
{
...
...
@@ -340,13 +322,10 @@ class NavigatorState extends State<Navigator> {
bool
_debugLocked
=
false
;
// used to prevent re-entrant calls to push, pop, and friends
void
pushNamed
(
String
name
,
{
Set
<
Key
>
mostValuableKeys
}
)
{
void
pushNamed
(
String
name
)
{
assert
(!
_debugLocked
);
assert
(
name
!=
null
);
RouteSettings
settings
=
new
RouteSettings
(
name:
name
,
mostValuableKeys:
mostValuableKeys
);
RouteSettings
settings
=
new
RouteSettings
(
name:
name
);
Route
<
dynamic
>
route
=
config
.
onGenerateRoute
(
settings
);
if
(
route
==
null
)
{
assert
(
config
.
onUnknownRoute
!=
null
);
...
...
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