From 79ccfe6b876a2d36a565c2f3e7bd1643d11671a7 Mon Sep 17 00:00:00 2001 From: Collin Jackson Date: Thu, 16 Jul 2015 17:22:20 -0700 Subject: [PATCH] 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 82a9b35bbc7..ded7c421208 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 483325f4747..4aabc0fc91d 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); } }