Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
sxychenjing
engine
提交
e2b78f7b
E
engine
项目概览
sxychenjing
/
engine
与 Fork 源项目一致
从无法访问的项目Fork
通知
3
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
E
engine
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
提交
e2b78f7b
编写于
10月 21, 2015
作者:
I
Ian Hickson
浏览文件
操作
浏览文件
下载
差异文件
Merge pull request #1720 from Hixie/heroes
Heroes
上级
322219cd
197bd295
变更
6
隐藏空白更改
内联
并排
Showing
6 changed file
with
615 addition
and
31 deletion
+615
-31
examples/stocks/lib/stock_home.dart
examples/stocks/lib/stock_home.dart
+5
-3
examples/stocks/lib/stock_row.dart
examples/stocks/lib/stock_row.dart
+10
-7
examples/stocks/lib/stock_symbol_viewer.dart
examples/stocks/lib/stock_symbol_viewer.dart
+7
-2
sky/packages/sky/lib/src/widgets/heroes.dart
sky/packages/sky/lib/src/widgets/heroes.dart
+389
-0
sky/packages/sky/lib/src/widgets/navigator.dart
sky/packages/sky/lib/src/widgets/navigator.dart
+203
-19
sky/packages/sky/lib/widgets.dart
sky/packages/sky/lib/widgets.dart
+1
-0
未找到文件。
examples/stocks/lib/stock_home.dart
浏览文件 @
e2b78f7b
...
...
@@ -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
浏览文件 @
e2b78f7b
...
...
@@ -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
浏览文件 @
e2b78f7b
...
...
@@ -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 {
)
)
)
])
]
)
);
}
...
...
sky/packages/sky/lib/src/widgets/heroes.dart
0 → 100644
浏览文件 @
e2b78f7b
// 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
'
;
}
sky/packages/sky/lib/src/widgets/navigator.dart
浏览文件 @
e2b78f7b
...
...
@@ -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
)
)
...
...
sky/packages/sky/lib/widgets.dart
浏览文件 @
e2b78f7b
...
...
@@ -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'
;
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录