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
fa8c4515
Commit
fa8c4515
authored
Oct 02, 2015
by
Hixie
Committed by
Ian Hickson
Oct 21, 2015
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Heroes
parent
8414b4c1
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
615 additions
and
31 deletions
+615
-31
stock_home.dart
examples/stocks/lib/stock_home.dart
+5
-3
stock_row.dart
examples/stocks/lib/stock_row.dart
+10
-7
stock_symbol_viewer.dart
examples/stocks/lib/stock_symbol_viewer.dart
+7
-2
heroes.dart
packages/flutter/lib/src/widgets/heroes.dart
+389
-0
navigator.dart
packages/flutter/lib/src/widgets/navigator.dart
+203
-19
widgets.dart
packages/flutter/lib/widgets.dart
+1
-0
No files found.
examples/stocks/lib/stock_home.dart
View file @
fa8c4515
...
...
@@ -186,14 +186,16 @@ class StockHomeState extends State<StockHome> {
Widget
buildStockList
(
BuildContext
context
,
Iterable
<
Stock
>
stocks
)
{
return
new
StockList
(
stocks:
stocks
.
toList
(),
onAction:
(
Stock
stock
,
Global
Key
arrowKey
)
{
onAction:
(
Stock
stock
,
Key
arrowKey
)
{
setState
(()
{
stock
.
percentChange
=
100.0
*
(
1.0
/
stock
.
lastSale
);
stock
.
lastSale
+=
1.0
;
});
},
onOpen:
(
Stock
stock
,
GlobalKey
arrowKey
)
{
config
.
navigator
.
pushNamed
(
'/stock/
${stock.symbol}
'
);
onOpen:
(
Stock
stock
,
Key
arrowKey
)
{
Set
<
Key
>
mostValuableKeys
=
new
Set
<
Key
>();
mostValuableKeys
.
add
(
arrowKey
);
config
.
navigator
.
pushNamed
(
'/stock/
${stock.symbol}
'
,
mostValuableKeys:
mostValuableKeys
);
}
);
}
...
...
examples/stocks/lib/stock_row.dart
View file @
fa8c4515
...
...
@@ -6,7 +6,7 @@ part of stocks;
enum
StockRowPartKind
{
arrow
}
class
StockRowPartKey
extends
Global
Key
{
class
StockRowPartKey
extends
Key
{
const
StockRowPartKey
(
this
.
stock
,
this
.
part
)
:
super
.
constructor
();
final
Stock
stock
;
final
StockRowPartKind
part
;
...
...
@@ -21,7 +21,7 @@ class StockRowPartKey extends GlobalKey {
String
toString
()
=>
'[StockRowPartKey
${stock.symbol}
:
${part.toString().split(".")[1]}
)]'
;
}
typedef
void
StockRowActionCallback
(
Stock
stock
,
Global
Key
arrowKey
);
typedef
void
StockRowActionCallback
(
Stock
stock
,
Key
arrowKey
);
class
StockRow
extends
StatelessComponent
{
StockRow
({
...
...
@@ -30,7 +30,7 @@ class StockRow extends StatelessComponent {
this
.
onLongPressed
})
:
this
.
stock
=
stock
,
_arrowKey
=
new
StockRowPartKey
(
stock
,
StockRowPartKind
.
arrow
),
super
(
key:
new
Global
ObjectKey
(
stock
));
super
(
key:
new
ObjectKey
(
stock
));
final
Stock
stock
;
final
StockRowActionCallback
onPressed
;
...
...
@@ -53,7 +53,7 @@ class StockRow extends StatelessComponent {
}
Widget
build
(
BuildContext
context
)
{
String
lastSale
=
"
\$
${stock.lastSale.toStringAsFixed(2)}
"
;
final
String
lastSale
=
"
\$
${stock.lastSale.toStringAsFixed(2)}
"
;
String
changeInPrice
=
"
${stock.percentChange.toStringAsFixed(2)}
%"
;
if
(
stock
.
percentChange
>
0
)
changeInPrice
=
"+"
+
changeInPrice
;
...
...
@@ -69,9 +69,12 @@ class StockRow extends StatelessComponent {
),
child:
new
Row
(<
Widget
>[
new
Container
(
key:
_arrowKey
,
child:
new
StockArrow
(
percentChange:
stock
.
percentChange
),
margin:
const
EdgeDims
.
only
(
right:
5.0
)
margin:
const
EdgeDims
.
only
(
right:
5.0
),
child:
new
Hero
(
tag:
StockRowPartKind
.
arrow
,
key:
_arrowKey
,
child:
new
StockArrow
(
percentChange:
stock
.
percentChange
)
)
),
new
Flexible
(
child:
new
Row
(<
Widget
>[
...
...
examples/stocks/lib/stock_symbol_viewer.dart
View file @
fa8c4515
...
...
@@ -36,7 +36,11 @@ class StockSymbolViewer extends StatelessComponent {
'
${stock.symbol}
'
,
style:
Theme
.
of
(
context
).
text
.
display2
),
new
StockArrow
(
percentChange:
stock
.
percentChange
)
new
Hero
(
tag:
StockRowPartKind
.
arrow
,
turns:
2
,
child:
new
StockArrow
(
percentChange:
stock
.
percentChange
)
),
],
justifyContent:
FlexJustifyContent
.
spaceBetween
),
...
...
@@ -51,7 +55,8 @@ class StockSymbolViewer extends StatelessComponent {
)
)
)
])
]
)
);
}
...
...
packages/flutter/lib/src/widgets/heroes.dart
0 → 100644
View file @
fa8c4515
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'package:flutter/animation.dart'
;
import
'package:flutter/rendering.dart'
;
import
'basic.dart'
;
import
'framework.dart'
;
import
'navigator.dart'
;
import
'transitions.dart'
;
// Heroes are the parts of an application's screen-to-screen transitions where a
// component from one screen shifts to a position on the other. For example,
// album art from a list of albums growing to become the centerpiece of the
// album's details view. In this context, a screen is a navigator Route.
// 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
// present on one of the routes and not the other, then it will be made to
// appear or disappear as needed.
// TODO(ianh): Make the appear/disappear animations pretty. Right now they're
// pretty crude (just rotate and shrink the constraints). They should probably
// involve actually scaling and fading, at a minimum.
// Heroes and the Navigator's Stack must be axis-aligned for all this to work.
// The top left and bottom right coordinates of each animated Hero will be
// converted to global coordinates and then from there converted to the
// Navigator Stack's coordinate space, and the entire Hero subtree will, for the
// duration of the animation, be lifted out of its original place, and
// positioned on that stack. If the Hero isn't axis aligned, this is going to
// fail in a rather ugly fashion. Don't rotate your heroes!
// 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
// 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, and route A's hero is
// hidden. Then the widget is animated to route B's hero's position, and then
// the widget is inserted into route B. When going back from B to A, route A's
// hero's widget is placed over where route B's hero's widget was, and then the
// animation goes the other way.
// TODO(ianh): If the widgets use Inherited properties, they are taken from the
// Navigator's position in the widget hierarchy, not the source or target. We
// should interpolate the inherited properties from their value at the source to
// their value at the target. See: https://github.com/flutter/engine/issues/1698
final
Object
centerOfAttentionHeroTag
=
new
Object
();
class
_HeroManifest
{
const
_HeroManifest
({
this
.
key
,
this
.
config
,
this
.
sourceStates
,
this
.
currentRect
,
this
.
currentTurns
});
final
GlobalKey
key
;
final
Widget
config
;
final
Set
<
HeroState
>
sourceStates
;
final
RelativeRect
currentRect
;
final
double
currentTurns
;
}
abstract
class
HeroHandle
{
_HeroManifest
_takeChild
(
Rect
animationArea
);
}
class
Hero
extends
StatefulComponent
{
Hero
({
Key
key
,
this
.
navigator
,
this
.
tag
,
this
.
child
,
this
.
turns
:
1
})
:
super
(
key:
key
)
{
assert
(
tag
!=
null
);
}
final
NavigatorState
navigator
;
final
Object
tag
;
final
Widget
child
;
final
int
turns
;
static
Map
<
Object
,
HeroHandle
>
of
(
BuildContext
context
,
Set
<
Key
>
mostValuableKeys
)
{
mostValuableKeys
??=
new
Set
<
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
>>{};
void
visitor
(
Element
element
)
{
if
(
element
.
widget
is
Hero
)
{
StatefulComponentElement
<
Hero
,
HeroState
>
hero
=
element
;
Object
tag
=
hero
.
widget
.
tag
;
assert
(
tag
!=
null
);
Key
key
=
hero
.
widget
.
key
;
final
Map
<
Key
,
HeroState
>
tagHeroes
=
heroes
.
putIfAbsent
(
tag
,
()
=>
<
Key
,
HeroState
>{});
assert
(!
tagHeroes
.
containsKey
(
key
));
tagHeroes
[
key
]
=
hero
.
state
;
}
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
;
}
HeroState
createState
()
=>
new
HeroState
();
}
enum
_HeroMode
{
constructing
,
initialized
,
measured
,
taken
}
class
HeroState
extends
State
<
Hero
>
implements
HeroHandle
{
void
initState
()
{
assert
(
_mode
==
_HeroMode
.
constructing
);
super
.
initState
();
_key
=
new
GlobalKey
();
_mode
=
_HeroMode
.
initialized
;
}
GlobalKey
_key
;
_HeroMode
_mode
=
_HeroMode
.
constructing
;
Size
_size
;
_HeroManifest
_takeChild
(
Rect
animationArea
)
{
assert
(
_mode
==
_HeroMode
.
measured
||
_mode
==
_HeroMode
.
taken
);
final
RenderBox
renderObject
=
context
.
findRenderObject
();
final
Point
heroTopLeft
=
renderObject
.
localToGlobal
(
Point
.
origin
);
final
Point
heroBottomRight
=
renderObject
.
localToGlobal
(
renderObject
.
size
.
bottomRight
(
Point
.
origin
));
final
Rect
heroArea
=
new
Rect
.
fromLTRB
(
heroTopLeft
.
x
,
heroTopLeft
.
y
,
heroBottomRight
.
x
,
heroBottomRight
.
y
);
final
RelativeRect
startRect
=
new
RelativeRect
.
fromRect
(
heroArea
,
animationArea
);
_HeroManifest
result
=
new
_HeroManifest
(
key:
_key
,
config:
config
,
sourceStates:
new
Set
<
HeroState
>.
from
(<
HeroState
>[
this
]),
currentRect:
startRect
,
currentTurns:
config
.
turns
.
toDouble
()
);
setState
(()
{
_key
=
null
;
_mode
=
_HeroMode
.
taken
;
});
return
result
;
}
void
_setChild
(
GlobalKey
value
)
{
assert
(
_mode
==
_HeroMode
.
taken
);
assert
(
_key
==
null
);
assert
(
_size
!=
null
);
if
(
mounted
)
setState
(()
{
_key
=
value
;
});
_size
=
null
;
_mode
=
_HeroMode
.
initialized
;
}
void
_resetChild
()
{
assert
(
_mode
==
_HeroMode
.
taken
);
assert
(
_key
==
null
);
assert
(
_size
!=
null
);
if
(
mounted
)
setState
(()
{
_key
=
new
GlobalKey
();
});
_size
=
null
;
_mode
=
_HeroMode
.
initialized
;
}
Widget
build
(
BuildContext
context
)
{
switch
(
_mode
)
{
case
_HeroMode
.
constructing
:
assert
(
false
);
return
null
;
case
_HeroMode
.
initialized
:
case
_HeroMode
.
measured
:
return
new
SizeObserver
(
onSizeChanged:
(
Size
size
)
{
assert
(
_mode
==
_HeroMode
.
initialized
||
_mode
==
_HeroMode
.
measured
);
_size
=
size
;
_mode
=
_HeroMode
.
measured
;
},
child:
new
KeyedSubtree
(
key:
_key
,
child:
config
.
child
)
);
case
_HeroMode
.
taken
:
return
new
SizedBox
(
width:
_size
.
width
,
height:
_size
.
height
);
}
}
}
class
_HeroQuestState
implements
HeroHandle
{
_HeroQuestState
({
this
.
tag
,
this
.
key
,
this
.
child
,
this
.
sourceStates
,
this
.
targetRect
,
this
.
targetTurns
,
this
.
targetState
,
this
.
currentRect
,
this
.
currentTurns
})
{
assert
(
tag
!=
null
);
}
final
Object
tag
;
final
GlobalKey
key
;
final
Widget
child
;
final
Set
<
HeroState
>
sourceStates
;
final
RelativeRect
targetRect
;
final
int
targetTurns
;
final
HeroState
targetState
;
final
AnimatedRelativeRectValue
currentRect
;
final
AnimatedValue
<
double
>
currentTurns
;
bool
get
taken
=>
_taken
;
bool
_taken
=
false
;
_HeroManifest
_takeChild
(
Rect
animationArea
)
{
assert
(!
taken
);
_taken
=
true
;
Set
<
HeroState
>
states
=
sourceStates
;
if
(
targetState
!=
null
)
states
=
states
.
union
(
new
Set
<
HeroState
>.
from
(<
HeroState
>[
targetState
]));
return
new
_HeroManifest
(
key:
key
,
config:
child
,
sourceStates:
states
,
currentRect:
currentRect
.
value
,
currentTurns:
currentTurns
.
value
);
}
Widget
build
(
BuildContext
context
,
PerformanceView
performance
)
{
return
new
PositionedTransition
(
rect:
currentRect
,
performance:
performance
,
child:
new
RotationTransition
(
turns:
currentTurns
,
performance:
performance
,
child:
new
KeyedSubtree
(
key:
key
,
child:
child
)
)
);
}
}
class
_HeroMatch
{
const
_HeroMatch
(
this
.
from
,
this
.
to
,
this
.
tag
);
final
HeroHandle
from
;
final
HeroHandle
to
;
final
Object
tag
;
}
typedef
void
QuestFinishedHandler
(
);
class
HeroParty
{
HeroParty
({
this
.
onQuestFinished
});
final
QuestFinishedHandler
onQuestFinished
;
List
<
_HeroQuestState
>
_heroes
=
<
_HeroQuestState
>[];
bool
get
isEmpty
=>
_heroes
.
isEmpty
;
Map
<
Object
,
HeroHandle
>
getHeroesToAnimate
()
{
Map
<
Object
,
HeroHandle
>
result
=
new
Map
<
Object
,
HeroHandle
>();
for
(
_HeroQuestState
hero
in
_heroes
)
result
[
hero
.
tag
]
=
hero
;
assert
(!
result
.
containsKey
(
null
));
return
result
;
}
AnimatedRelativeRectValue
createAnimatedRelativeRect
(
RelativeRect
begin
,
RelativeRect
end
,
Curve
curve
)
{
return
new
AnimatedRelativeRectValue
(
begin
,
end:
end
,
curve:
curve
);
}
AnimatedValue
<
double
>
createAnimatedTurns
(
double
begin
,
double
end
,
Curve
curve
)
{
assert
(
end
.
floor
()
==
end
);
return
new
AnimatedValue
<
double
>(
begin
,
end:
end
,
curve:
curve
);
}
void
animate
(
Map
<
Object
,
HeroHandle
>
heroesFrom
,
Map
<
Object
,
HeroHandle
>
heroesTo
,
Rect
animationArea
,
Curve
curve
)
{
assert
(!
heroesFrom
.
containsKey
(
null
));
assert
(!
heroesTo
.
containsKey
(
null
));
// make a list of pairs of heroes, based on the from and to lists
Map
<
Object
,
_HeroMatch
>
heroes
=
<
Object
,
_HeroMatch
>{};
for
(
Object
tag
in
heroesFrom
.
keys
)
heroes
[
tag
]
=
new
_HeroMatch
(
heroesFrom
[
tag
],
heroesTo
[
tag
],
tag
);
for
(
Object
tag
in
heroesTo
.
keys
)
{
if
(!
heroes
.
containsKey
(
tag
))
heroes
[
tag
]
=
new
_HeroMatch
(
heroesFrom
[
tag
],
heroesTo
[
tag
],
tag
);
}
// create a heroating hero out of each pair
final
List
<
_HeroQuestState
>
_newHeroes
=
<
_HeroQuestState
>[];
for
(
_HeroMatch
heroPair
in
heroes
.
values
)
{
assert
(
heroPair
.
from
!=
null
||
heroPair
.
to
!=
null
);
_HeroManifest
from
=
heroPair
.
from
?.
_takeChild
(
animationArea
);
assert
(
heroPair
.
to
==
null
||
heroPair
.
to
is
HeroState
);
_HeroManifest
to
=
heroPair
.
to
?.
_takeChild
(
animationArea
);
assert
(
from
!=
null
||
to
!=
null
);
assert
(
to
==
null
||
to
.
sourceStates
.
length
==
1
);
assert
(
to
==
null
||
to
.
currentTurns
.
floor
()
==
to
.
currentTurns
);
HeroState
targetState
=
to
!=
null
?
to
.
sourceStates
.
elementAt
(
0
)
:
null
;
Set
<
HeroState
>
sourceStates
=
from
!=
null
?
from
.
sourceStates
:
new
Set
<
HeroState
>();
sourceStates
.
remove
(
targetState
);
RelativeRect
sourceRect
=
from
!=
null
?
from
.
currentRect
:
new
RelativeRect
.
fromRect
(
to
.
currentRect
.
toRect
(
animationArea
).
center
&
Size
.
zero
,
animationArea
);
RelativeRect
targetRect
=
to
!=
null
?
to
.
currentRect
:
new
RelativeRect
.
fromRect
(
from
.
currentRect
.
toRect
(
animationArea
).
center
&
Size
.
zero
,
animationArea
);
double
sourceTurns
=
from
!=
null
?
from
.
currentTurns
:
0.0
;
double
targetTurns
=
to
!=
null
?
to
.
currentTurns
:
0.0
;
_newHeroes
.
add
(
new
_HeroQuestState
(
tag:
heroPair
.
tag
,
key:
from
!=
null
?
from
.
key
:
to
.
key
,
child:
to
!=
null
?
to
.
config
:
from
.
config
,
sourceStates:
sourceStates
,
targetRect:
targetRect
,
targetTurns:
targetTurns
.
floor
(),
targetState:
targetState
,
currentRect:
createAnimatedRelativeRect
(
sourceRect
,
targetRect
,
curve
),
currentTurns:
createAnimatedTurns
(
sourceTurns
,
targetTurns
,
curve
)
));
}
assert
(!
_heroes
.
any
((
_HeroQuestState
hero
)
=>
!
hero
.
taken
));
_heroes
=
_newHeroes
;
}
PerformanceView
_currentPerformance
;
Iterable
<
Widget
>
getWidgets
(
BuildContext
context
,
PerformanceView
performance
)
sync
*
{
assert
(
performance
!=
null
||
_heroes
.
length
==
0
);
if
(
performance
!=
_currentPerformance
)
{
if
(
_currentPerformance
!=
null
)
_currentPerformance
.
removeStatusListener
(
_handleUpdate
);
_currentPerformance
=
performance
;
if
(
_currentPerformance
!=
null
)
_currentPerformance
.
addStatusListener
(
_handleUpdate
);
}
for
(
_HeroQuestState
hero
in
_heroes
)
yield
hero
.
build
(
context
,
performance
);
}
void
_handleUpdate
(
PerformanceStatus
status
)
{
if
(
status
==
PerformanceStatus
.
completed
||
status
==
PerformanceStatus
.
dismissed
)
{
for
(
_HeroQuestState
hero
in
_heroes
)
{
if
(
hero
.
targetState
!=
null
)
hero
.
targetState
.
_setChild
(
hero
.
key
);
for
(
HeroState
source
in
hero
.
sourceStates
)
source
.
_resetChild
();
if
(
onQuestFinished
!=
null
)
onQuestFinished
();
}
_heroes
.
clear
();
_currentPerformance
=
null
;
}
}
String
toString
()
=>
'
$_heroes
'
;
}
packages/flutter/lib/src/widgets/navigator.dart
View file @
fa8c4515
...
...
@@ -8,6 +8,7 @@ import 'package:flutter/rendering.dart';
import
'basic.dart'
;
import
'focus.dart'
;
import
'framework.dart'
;
import
'heroes.dart'
;
import
'transitions.dart'
;
import
'gridpaper.dart'
;
...
...
@@ -51,6 +52,40 @@ class Navigator extends StatefulComponent {
// The navigator tracks which "page" we are on.
// It also animates between these pages.
// Pages can have "heroes", which are UI elements that animate from point to point.
// These animations are called journeys.
//
// Journeys can start in two conditions:
// - Everything is calm, and we have no heroes in flight. In this case, we will
// have to collect the heroes from the route we're starting at and the route
// we're going to, and try to transition from one set to the other.
// - We already have heroes in flight. In that case, we just want to look at
// the heroes of our destination, and then try to transition to them from the
// in-flight heroes.
class
_HeroTransitionInstruction
{
Route
from
;
Route
to
;
void
update
(
Route
newFrom
,
Route
newTo
)
{
assert
(
newFrom
!=
null
);
assert
(
newTo
!=
null
);
if
(!
newFrom
.
canHaveHeroes
||
!
newTo
.
canHaveHeroes
)
return
;
assert
(
newFrom
.
performance
!=
null
);
assert
(
newTo
.
performance
!=
null
);
if
(
from
==
null
)
from
=
newFrom
;
to
=
newTo
;
if
(
from
==
to
)
reset
();
}
void
reset
()
{
assert
(
hasInstructions
);
from
=
null
;
to
=
null
;
}
bool
get
hasInstructions
=>
from
!=
null
||
to
!=
null
;
}
class
NavigatorState
extends
State
<
Navigator
>
{
...
...
@@ -62,6 +97,7 @@ class NavigatorState extends State<Navigator> {
void
initState
()
{
super
.
initState
();
_activeHeroes
=
new
HeroParty
(
onQuestFinished:
_handleHeroQuestFinished
);
PageRoute
route
=
new
PageRoute
(
config
.
routes
[
kDefaultRouteName
],
name:
kDefaultRouteName
);
assert
(
route
.
hasContent
);
assert
(!
route
.
ephemeral
);
...
...
@@ -76,14 +112,22 @@ class NavigatorState extends State<Navigator> {
));
}
void
pushNamed
(
String
name
)
{
void
pushNamed
(
String
name
,
{
Set
<
Key
>
mostValuableKeys
}
)
{
RouteBuilder
generateRoute
()
{
assert
(
config
.
onGenerateRoute
!=
null
);
return
config
.
onGenerateRoute
(
name
);
}
final
RouteBuilder
builder
=
config
.
routes
[
name
]
??
generateRoute
()
??
config
.
onUnknownRoute
;
assert
(
builder
!=
null
);
// 404 getting your 404!
push
(
new
PageRoute
(
builder
,
name:
name
));
push
(
new
PageRoute
(
builder
,
name:
name
,
mostValuableKeys:
mostValuableKeys
));
}
final
_HeroTransitionInstruction
_desiredHeroes
=
new
_HeroTransitionInstruction
();
HeroParty
_activeHeroes
;
void
_handleHeroQuestFinished
()
{
for
(
Route
route
in
_history
)
route
.
_hasActiveHeroes
=
false
;
}
void
push
(
Route
route
)
{
...
...
@@ -94,6 +138,14 @@ class NavigatorState extends State<Navigator> {
currentRoute
.
didPop
(
null
);
_currentPosition
-=
1
;
}
// find the most recent active route that might have heroes
if
(
route
.
hasContent
)
{
int
index
=
_currentPosition
;
while
(
index
>
0
&&
!
_history
[
index
].
hasContent
)
index
-=
1
;
assert
(
_history
[
index
].
hasContent
);
_desiredHeroes
.
update
(
_history
[
index
],
route
);
}
// add the new route
_currentPosition
+=
1
;
_insertRoute
(
route
);
...
...
@@ -118,6 +170,14 @@ class NavigatorState extends State<Navigator> {
void
pop
([
dynamic
result
])
{
setState
(()
{
assert
(
_currentPosition
>
0
);
// find the most recent previous route that might have heroes
if
(
currentRoute
.
hasContent
)
{
int
index
=
_currentPosition
-
1
;
while
(
index
>
0
&&
!
_history
[
index
].
hasContent
)
index
-=
1
;
assert
(
_history
[
index
].
hasContent
);
_desiredHeroes
.
update
(
currentRoute
,
_history
[
index
]);
}
// pop the route
currentRoute
.
didPop
(
result
);
_currentPosition
-=
1
;
...
...
@@ -153,11 +213,17 @@ class NavigatorState extends State<Navigator> {
void
_removeRoute
(
Route
route
)
{
assert
(
_history
.
contains
(
route
));
setState
(()
{
if
(
_desiredHeroes
.
hasInstructions
)
{
if
(
_desiredHeroes
.
from
==
route
||
_desiredHeroes
.
to
==
route
)
_desiredHeroes
.
reset
();
}
_history
.
remove
(
route
);
});
}
Widget
build
(
BuildContext
context
)
{
PerformanceView
_currentHeroPerformance
;
Widget
build
(
BuildContext
context
)
{
List
<
Widget
>
visibleRoutes
=
<
Widget
>[];
assert
(()
{
...
...
@@ -166,22 +232,35 @@ class NavigatorState extends State<Navigator> {
return
true
;
});
bool
alreadyInsertedHeroes
=
false
;
bool
alreadyInsertedModalBarrier
=
false
;
Route
nextContentRoute
;
PerformanceView
nextHeroPerformance
;
for
(
int
i
=
_history
.
length
-
1
;
i
>=
0
;
i
-=
1
)
{
Route
route
=
_history
[
i
];
final
Route
route
=
_history
[
i
];
if
(!
route
.
hasContent
)
{
assert
(!
route
.
modal
);
assert
(!
_desiredHeroes
.
hasInstructions
||
(
_desiredHeroes
.
from
!=
route
&&
_desiredHeroes
.
to
!=
route
));
assert
(!
route
.
_hasActiveHeroes
);
continue
;
}
visibleRoutes
.
add
(
new
KeyedSubtree
(
key:
new
ObjectKey
(
route
),
child:
route
.
_internalBuild
(
nextContentRoute
)
)
);
if
(
route
.
isActuallyOpaque
)
if
(
route
.
_hasActiveHeroes
&&
!
alreadyInsertedHeroes
)
{
visibleRoutes
.
addAll
(
_activeHeroes
.
getWidgets
(
context
,
_currentHeroPerformance
));
alreadyInsertedHeroes
=
true
;
}
if
(
_desiredHeroes
.
hasInstructions
)
{
if
((
_desiredHeroes
.
to
==
route
||
_desiredHeroes
.
from
==
route
)
&&
nextHeroPerformance
==
null
)
nextHeroPerformance
=
route
.
performance
;
visibleRoutes
.
add
(
route
.
_internalBuild
(
nextContentRoute
,
buildTargetHeroes:
_desiredHeroes
.
to
==
route
));
}
else
{
visibleRoutes
.
add
(
route
.
_internalBuild
(
nextContentRoute
));
}
if
(
route
.
isActuallyOpaque
)
{
assert
(!
_desiredHeroes
.
hasInstructions
||
(
_history
.
indexOf
(
_desiredHeroes
.
from
)
>=
i
&&
_history
.
indexOf
(
_desiredHeroes
.
to
)
>=
i
));
break
;
}
assert
(
route
.
modal
||
route
.
ephemeral
);
if
(
route
.
modal
&&
i
>
0
&&
!
alreadyInsertedModalBarrier
)
{
visibleRoutes
.
add
(
new
Listener
(
...
...
@@ -192,12 +271,54 @@ class NavigatorState extends State<Navigator> {
}
nextContentRoute
=
route
;
}
if
(
_desiredHeroes
.
hasInstructions
)
{
assert
(
nextHeroPerformance
!=
null
);
scheduler
.
requestPostFrameCallback
((
Duration
timestamp
)
{
Map
<
Object
,
HeroHandle
>
heroesFrom
;
Map
<
Object
,
HeroHandle
>
heroesTo
;
Set
<
Key
>
mostValuableKeys
=
new
Set
<
Key
>();
if
(
_desiredHeroes
.
from
.
mostValuableKeys
!=
null
)
mostValuableKeys
.
addAll
(
_desiredHeroes
.
from
.
mostValuableKeys
);
if
(
_desiredHeroes
.
to
.
mostValuableKeys
!=
null
)
mostValuableKeys
.
addAll
(
_desiredHeroes
.
to
.
mostValuableKeys
);
if
(
_activeHeroes
.
isEmpty
)
{
assert
(!
_desiredHeroes
.
from
.
_hasActiveHeroes
);
heroesFrom
=
_desiredHeroes
.
from
.
getHeroesToAnimate
(
mostValuableKeys
);
_desiredHeroes
.
from
.
_hasActiveHeroes
=
heroesFrom
.
length
>
0
;
}
else
{
assert
(
_desiredHeroes
.
from
.
_hasActiveHeroes
);
heroesFrom
=
_activeHeroes
.
getHeroesToAnimate
();
}
heroesTo
=
_desiredHeroes
.
to
.
getHeroesToAnimate
(
mostValuableKeys
);
_desiredHeroes
.
to
.
_hasActiveHeroes
=
heroesTo
.
length
>
0
;
_desiredHeroes
.
reset
();
setState
(()
{
final
RenderBox
renderObject
=
context
.
findRenderObject
();
final
Point
animationTopLeft
=
renderObject
.
localToGlobal
(
Point
.
origin
);
final
Point
animationBottomRight
=
renderObject
.
localToGlobal
(
renderObject
.
size
.
bottomRight
(
Point
.
origin
));
final
Rect
animationArea
=
new
Rect
.
fromLTRB
(
animationTopLeft
.
x
,
animationTopLeft
.
y
,
animationBottomRight
.
x
,
animationBottomRight
.
y
);
Curve
curve
=
Curves
.
ease
;
if
(
nextHeroPerformance
.
status
==
PerformanceStatus
.
reverse
)
{
nextHeroPerformance
=
new
ReversePerformance
(
nextHeroPerformance
);
curve
=
new
Interval
(
nextHeroPerformance
.
progress
,
1.0
,
curve:
curve
);
}
_activeHeroes
.
animate
(
heroesFrom
,
heroesTo
,
animationArea
,
curve
);
_currentHeroPerformance
=
nextHeroPerformance
;
});
});
}
return
new
Focus
(
child:
new
Stack
(
visibleRoutes
.
reversed
.
toList
()));
}
}
abstract
class
Route
{
Route
()
{
_subtreeKey
=
new
GlobalKey
(
label:
debugLabel
);
}
/// If hasContent is true, then the route represents some on-screen state.
///
/// If hasContent is false, then no performance will be created, and the values of
...
...
@@ -284,21 +405,56 @@ abstract class Route {
/// Called by the navigator.build() function if hasContent is true, to get the
/// subtree for this route.
Widget
_internalBuild
(
Route
nextRoute
)
{
///
/// If buildTargetHeroes is true, then getHeroesToAnimate() will be called
/// after this build, before the next build, and this build should render the
/// route off-screen, at the end of its animation. Next frame, the argument
/// will be false, and the tree should be built at the first frame of the
/// transition animation, whatever that is.
Widget
_internalBuild
(
Route
nextRoute
,
{
bool
buildTargetHeroes:
false
})
{
assert
(
navigator
!=
null
);
return
build
(
new
RouteArguments
(
return
keySubtree
(
build
(
new
RouteArguments
(
navigator
,
previousPerformance:
performance
,
nextPerformance:
nextRoute
?.
performance
));
)));
}
bool
get
canHaveHeroes
=>
hasContent
&&
modal
&&
opaque
;
Set
<
Key
>
get
mostValuableKeys
=>
null
;
/// Return a party of heroes (one per tag) to animate. This is called by the
/// navigator when hasContent is true just after this route, the previous
/// route, or the next route, has been pushed or popped, to figure out which
/// heroes it should be trying to animate.
Map
<
Object
,
HeroHandle
>
getHeroesToAnimate
([
Set
<
Key
>
mostValuableKeys
])
=>
const
<
Object
,
HeroHandle
>{};
bool
_hasActiveHeroes
=
false
;
/// Returns the BuildContext for the root of the subtree built for this route,
/// assuming that internalBuild used keySubtree to build that subtree.
/// This is only valid after a build phase.
BuildContext
get
context
=>
_subtreeKey
.
currentContext
;
GlobalKey
_subtreeKey
;
/// Wraps the given subtree in a route-specific GlobalKey.
Widget
keySubtree
(
Widget
child
)
{
return
new
KeyedSubtree
(
key:
_subtreeKey
,
child:
child
);
}
/// Called by internalBuild. This is the method to override if you want to
/// change what subtree is built for this route.
Widget
build
(
RouteArguments
args
);
String
get
debugLabel
=>
'
$runtimeType
'
;
String
toString
()
=>
'
$runtimeType
(performance:
$performance
)'
;
String
toString
()
=>
'
$runtimeType
(performance:
$performance
; key:
$_subtreeKey
)'
;
}
abstract
class
PerformanceRoute
extends
Route
{
PerformanceRoute
()
{
_performance
=
createPerformance
();
...
...
@@ -315,6 +471,22 @@ abstract class PerformanceRoute extends Route {
Duration
get
transitionDuration
;
Widget
_internalBuild
(
Route
nextRoute
,
{
bool
buildTargetHeroes:
false
})
{
assert
(
hasContent
);
assert
(
transitionDuration
>
Duration
.
ZERO
);
if
(
buildTargetHeroes
&&
performance
.
progress
!=
1.0
)
{
Performance
fakePerformance
=
createPerformance
();
assert
(
fakePerformance
!=
null
);
fakePerformance
.
progress
=
1.0
;
return
new
OffStage
(
child:
keySubtree
(
build
(
new
RouteArguments
(
navigator
,
previousPerformance:
fakePerformance
))
)
);
}
return
super
.
_internalBuild
(
nextRoute
,
buildTargetHeroes:
buildTargetHeroes
);
}
void
didPush
(
NavigatorState
navigator
)
{
super
.
didPush
(
navigator
);
_performance
?.
forward
();
...
...
@@ -330,27 +502,39 @@ const Duration _kTransitionDuration = const Duration(milliseconds: 150);
const
Point
_kTransitionStartPoint
=
const
Point
(
0.0
,
75.0
);
/// A route that represents a page in an application.
///
/// PageRoutes try to animate between themselves in a fashion that is aware of
/// any Heroes.
class
PageRoute
extends
PerformanceRoute
{
PageRoute
(
this
.
_builder
,
{
this
.
name
:
'<anonymous>'
})
{
this
.
name
:
'<anonymous>'
,
Set
<
Key
>
mostValuableKeys
})
:
_mostValuableKeys
=
mostValuableKeys
{
assert
(
_builder
!=
null
);
}
final
RouteBuilder
_builder
;
final
String
name
;
final
Set
<
Key
>
_mostValuableKeys
;
Set
<
Key
>
get
mostValuableKeys
=>
_mostValuableKeys
;
bool
get
opaque
=>
true
;
Duration
get
transitionDuration
=>
_kTransitionDuration
;
Map
<
Object
,
HeroHandle
>
getHeroesToAnimate
([
Set
<
Key
>
mostValuableKeys
])
{
return
Hero
.
of
(
context
,
mostValuableKeys
);
}
Widget
build
(
RouteArguments
args
)
{
// TODO(jackson): Hit testing should ignore transform
// TODO(jackson): Block input unless content is interactive
// TODO(ianh): Support having different transitions, e.g. when heroes are around.
return
new
SlideTransition
(
performance:
p
erformance
,
performance:
args
.
previousP
erformance
,
position:
new
AnimatedValue
<
Point
>(
_kTransitionStartPoint
,
end:
Point
.
origin
,
curve:
Curves
.
easeOut
),
child:
new
FadeTransition
(
performance:
p
erformance
,
performance:
args
.
previousP
erformance
,
opacity:
new
AnimatedValue
<
double
>(
0.0
,
end:
1.0
,
curve:
Curves
.
easeOut
),
child:
invokeBuilder
(
args
)
)
...
...
packages/flutter/lib/widgets.dart
View file @
fa8c4515
...
...
@@ -16,6 +16,7 @@ export 'src/widgets/focus.dart';
export
'src/widgets/framework.dart'
;
export
'src/widgets/gesture_detector.dart'
;
export
'src/widgets/gridpaper.dart'
;
export
'src/widgets/heroes.dart'
;
export
'src/widgets/homogeneous_viewport.dart'
;
export
'src/widgets/mimic.dart'
;
export
'src/widgets/mixed_viewport.dart'
;
...
...
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