From 79ccfe6b876a2d36a565c2f3e7bd1643d11671a7 Mon Sep 17 00:00:00 2001 From: Collin Jackson Date: Thu, 16 Jul 2015 17:22:20 -0700 Subject: [PATCH 1/4] Support for settings fly-in animation --- sky/sdk/example/widgets/navigation.dart | 14 +-- sky/sdk/lib/widgets/navigator.dart | 133 +++++++++++++++++++++--- 2 files changed, 128 insertions(+), 19 deletions(-) diff --git a/sky/sdk/example/widgets/navigation.dart b/sky/sdk/example/widgets/navigation.dart index 82a9b35bbc7c0..ded7c4212087e 100644 --- a/sky/sdk/example/widgets/navigation.dart +++ b/sky/sdk/example/widgets/navigation.dart @@ -10,9 +10,9 @@ List routes = [ new Route( name: 'home', builder: (navigator, route) => new Container( - padding: const EdgeDims.all(20.0), + padding: const EdgeDims.all(30.0), decoration: new BoxDecoration(backgroundColor: const Color(0xFFCCCCCC)), - child: new Block([ + child: new Flex([ new Text("You are at home"), new RaisedButton( child: new Text('GO SHOPPING'), @@ -22,7 +22,7 @@ List routes = [ child: new Text('START ADVENTURE'), onPressed: () => navigator.pushNamed('adventure') ) - ]) + ], direction: FlexDirection.vertical, alignItems: FlexAlignItems.center) ) ), new Route( @@ -30,7 +30,7 @@ List routes = [ builder: (navigator, route) => new Container( padding: const EdgeDims.all(20.0), decoration: new BoxDecoration(backgroundColor: const Color(0xFFBF5FFF)), - child: new Block([ + child: new Flex([ new Text("Village Shop"), new RaisedButton( child: new Text('RETURN HOME'), @@ -40,7 +40,7 @@ List routes = [ child: new Text('GO TO DUNGEON'), onPressed: () => navigator.push(routes[2]) ) - ]) + ], direction: FlexDirection.vertical) ) ), new Route( @@ -48,13 +48,13 @@ List routes = [ builder: (navigator, route) => new Container( padding: const EdgeDims.all(20.0), decoration: new BoxDecoration(backgroundColor: const Color(0xFFDC143C)), - child: new Block([ + child: new Flex([ new Text("Monster's Lair"), new RaisedButton( child: new Text('NO WAIT! GO BACK!'), onPressed: () => navigator.pop() ) - ]) + ], direction: FlexDirection.vertical) ) ) ]; diff --git a/sky/sdk/lib/widgets/navigator.dart b/sky/sdk/lib/widgets/navigator.dart index 483325f4747b2..4aabc0fc91d0d 100644 --- a/sky/sdk/lib/widgets/navigator.dart +++ b/sky/sdk/lib/widgets/navigator.dart @@ -2,7 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:sky/animation/animation_performance.dart'; +import 'package:sky/animation/curves.dart'; +import 'package:sky/widgets/animated_component.dart'; +import 'package:sky/widgets/animation_builder.dart'; import 'package:sky/widgets/basic.dart'; +import 'package:vector_math/vector_math.dart'; typedef Widget Builder(Navigator navigator, RouteBase route); @@ -27,7 +32,7 @@ class RouteState extends RouteBase { RouteBase route; Function callback; - Widget build(Navigator navigator, _) => route.build(navigator, this); + Widget build(Navigator navigator, RouteBase route) => null; void popState() { if (callback != null) @@ -35,6 +40,92 @@ class RouteState extends RouteBase { } } +// TODO(jackson): Refactor this into its own file +// and support multiple transition types +const Duration _kTransitionDuration = const Duration(milliseconds: 200); +const Point _kTransitionStartPoint = const Point(0.0, 100.0); +enum TransitionDirection { forward, reverse } +class Transition extends AnimatedComponent { + Transition({ this.content, this.direction, this.onDismissed, this.interactive }); + Widget content; + TransitionDirection direction; + bool interactive; + Function onDismissed; + + AnimatedType _position; + AnimatedType _opacity; + AnimationPerformance _performance; + + void initState() { + _position = new AnimatedType(_kTransitionStartPoint) + ..end = Point.origin + ..curve = easeOut; + _opacity = new AnimatedType(0.0, end: 1.0) + ..curve = easeOut; + _performance = new AnimationPerformance() + ..duration = _kTransitionDuration + ..variable = new AnimatedList([_position, _opacity]) + ..addListener(_checkDismissed); + if (direction == TransitionDirection.reverse) + _performance.progress = 1.0; + watch(_performance); + _start(); + } + + void _start() { + _dismissed = false; + switch (direction) { + case TransitionDirection.forward: + _performance.play(); + break; + case TransitionDirection.reverse: + _performance.reverse(); + break; + } + } + + void syncFields(Transition source) { + content = source.content; + if (direction != source.direction) { + direction = source.direction; + _start(); + } + onDismissed = source.onDismissed; + super.syncFields(source); + } + + bool _dismissed = false; + void _checkDismissed() { + if (!_dismissed && + direction == TransitionDirection.reverse && + _performance.isDismissed) { + if (onDismissed != null) + onDismissed(); + _dismissed = true; + } + } + + Widget build() { + Matrix4 transform = new Matrix4.identity() + ..translate(_position.value.x, _position.value.y); + // TODO(jackson): Hit testing should ignore transform + // TODO(jackson): Block input unless content is interactive + return new Transform( + transform: transform, + child: new Opacity( + opacity: _opacity.value, + child: content + ) + ); + } +} + +class HistoryEntry { + HistoryEntry(this.route); + final RouteBase route; + // TODO(jackson): Keep track of the requested transition +} + class NavigationState { NavigationState(List routes) { @@ -42,16 +133,15 @@ class NavigationState { if (route.name != null) namedRoutes[route.name] = route; } - history.add(routes[0]); + history.add(new HistoryEntry(routes[0])); } - List history = new List(); + List history = new List(); int historyIndex = 0; Map namedRoutes = new Map(); - RouteBase get currentRoute => history[historyIndex]; + RouteBase get currentRoute => history[historyIndex].route; bool hasPrevious() => historyIndex > 0; - bool hasNext() => history.length > historyIndex + 1; void pushNamed(String name) { Route route = namedRoutes[name]; @@ -60,16 +150,15 @@ class NavigationState { } void push(RouteBase route) { - // Discard future history - history.removeRange(historyIndex + 1, history.length); - historyIndex = history.length; - history.add(route); + HistoryEntry historyEntry = new HistoryEntry(route); + history.insert(historyIndex + 1, historyEntry); + historyIndex++; } void pop() { if (historyIndex > 0) { - history[historyIndex].popState(); - history.removeLast(); + HistoryEntry entry = history[historyIndex]; + entry.route.popState(); historyIndex--; } } @@ -115,6 +204,26 @@ class Navigator extends StatefulComponent { } Widget build() { - return state.currentRoute.build(this, state.currentRoute); + List visibleRoutes = new List(); + for (int i = 0; i < state.history.length; i++) { + HistoryEntry historyEntry = state.history[i]; + Widget content = historyEntry.route.build(this, historyEntry.route); + if (i == 0) { + visibleRoutes.add(content); + continue; + } + if (content == null) + continue; + Transition transition = new Transition(content: content) + ..direction = (i <= state.historyIndex) ? TransitionDirection.forward : TransitionDirection.reverse + ..interactive = (i == state.historyIndex) + ..onDismissed = () { + setState(() { + state.history.remove(historyEntry); + }); + }; + visibleRoutes.add(transition); + } + return new Stack(visibleRoutes); } } From e3244086fe87d23a66ef075ddb2ffd8d2a8137cf Mon Sep 17 00:00:00 2001 From: Collin Jackson Date: Thu, 16 Jul 2015 17:36:25 -0700 Subject: [PATCH 2/4] Make example look more beautiful --- sky/sdk/example/widgets/navigation.dart | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/sky/sdk/example/widgets/navigation.dart b/sky/sdk/example/widgets/navigation.dart index ded7c4212087e..c6168314cf506 100644 --- a/sky/sdk/example/widgets/navigation.dart +++ b/sky/sdk/example/widgets/navigation.dart @@ -21,8 +21,10 @@ List routes = [ new RaisedButton( child: new Text('START ADVENTURE'), onPressed: () => navigator.pushNamed('adventure') - ) - ], direction: FlexDirection.vertical, alignItems: FlexAlignItems.center) + )], + direction: FlexDirection.vertical, + justifyContent: FlexJustifyContent.center + ) ) ), new Route( @@ -39,8 +41,10 @@ List routes = [ new RaisedButton( child: new Text('GO TO DUNGEON'), onPressed: () => navigator.push(routes[2]) - ) - ], direction: FlexDirection.vertical) + )], + direction: FlexDirection.vertical, + justifyContent: FlexJustifyContent.center + ) ) ), new Route( @@ -51,10 +55,12 @@ List routes = [ child: new Flex([ new Text("Monster's Lair"), new RaisedButton( - child: new Text('NO WAIT! GO BACK!'), + child: new Text('RUN!!!'), onPressed: () => navigator.pop() - ) - ], direction: FlexDirection.vertical) + )], + direction: FlexDirection.vertical, + justifyContent: FlexJustifyContent.center + ) ) ) ]; From ffe129927fda6c69ae60deee44abe1930d3e17b3 Mon Sep 17 00:00:00 2001 From: Collin Jackson Date: Thu, 16 Jul 2015 17:36:45 -0700 Subject: [PATCH 3/4] Fix bug when having more than 2 routes --- sky/sdk/lib/widgets/navigator.dart | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/sky/sdk/lib/widgets/navigator.dart b/sky/sdk/lib/widgets/navigator.dart index 4aabc0fc91d0d..4096c7d05dc22 100644 --- a/sky/sdk/lib/widgets/navigator.dart +++ b/sky/sdk/lib/widgets/navigator.dart @@ -46,7 +46,13 @@ const Duration _kTransitionDuration = const Duration(milliseconds: 200); const Point _kTransitionStartPoint = const Point(0.0, 100.0); enum TransitionDirection { forward, reverse } class Transition extends AnimatedComponent { - Transition({ this.content, this.direction, this.onDismissed, this.interactive }); + Transition({ + String key, + this.content, + this.direction, + this.onDismissed, + this.interactive + }) : super(key: key); Widget content; TransitionDirection direction; bool interactive; @@ -206,6 +212,7 @@ class Navigator extends StatefulComponent { Widget build() { List visibleRoutes = new List(); for (int i = 0; i < state.history.length; i++) { + // TODO(jackson): Avoid building routes that are not visible HistoryEntry historyEntry = state.history[i]; Widget content = historyEntry.route.build(this, historyEntry.route); if (i == 0) { @@ -214,7 +221,8 @@ class Navigator extends StatefulComponent { } if (content == null) continue; - Transition transition = new Transition(content: content) + String key = historyEntry.route.key; + Transition transition = new Transition(content: content, key: key) ..direction = (i <= state.historyIndex) ? TransitionDirection.forward : TransitionDirection.reverse ..interactive = (i == state.historyIndex) ..onDismissed = () { From 10d51926168f598be8b99ed413238856eac3917f Mon Sep 17 00:00:00 2001 From: Collin Jackson Date: Thu, 16 Jul 2015 17:39:36 -0700 Subject: [PATCH 4/4] abarth feedback --- sky/sdk/lib/widgets/navigator.dart | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/sky/sdk/lib/widgets/navigator.dart b/sky/sdk/lib/widgets/navigator.dart index 4096c7d05dc22..03d8c3bf915da 100644 --- a/sky/sdk/lib/widgets/navigator.dart +++ b/sky/sdk/lib/widgets/navigator.dart @@ -63,9 +63,11 @@ class Transition extends AnimatedComponent { AnimationPerformance _performance; void initState() { - _position = new AnimatedType(_kTransitionStartPoint) - ..end = Point.origin - ..curve = easeOut; + _position = new AnimatedType( + _kTransitionStartPoint, + end: Point.origin, + curve: easeOut + ); _opacity = new AnimatedType(0.0, end: 1.0) ..curve = easeOut; _performance = new AnimationPerformance() @@ -96,6 +98,7 @@ class Transition extends AnimatedComponent { direction = source.direction; _start(); } + interactive = source.interactive; onDismissed = source.onDismissed; super.syncFields(source); } @@ -222,14 +225,17 @@ class Navigator extends StatefulComponent { if (content == null) continue; String key = historyEntry.route.key; - Transition transition = new Transition(content: content, key: key) - ..direction = (i <= state.historyIndex) ? TransitionDirection.forward : TransitionDirection.reverse - ..interactive = (i == state.historyIndex) - ..onDismissed = () { - setState(() { - state.history.remove(historyEntry); - }); - }; + Transition transition = new Transition( + key: key, + content: content, + direction: (i <= state.historyIndex) ? TransitionDirection.forward : TransitionDirection.reverse, + interactive: (i == state.historyIndex), + onDismissed: () { + setState(() { + state.history.remove(historyEntry); + }); + } + ); visibleRoutes.add(transition); } return new Stack(visibleRoutes);