diff --git a/sky/packages/sky/lib/animation/animation_performance.dart b/sky/packages/sky/lib/animation/animation_performance.dart index d95c11e8bee2d27b5430af92806c29acf7f48748..7c2e5626770f0a267f9743275450d341ebd26cde 100644 --- a/sky/packages/sky/lib/animation/animation_performance.dart +++ b/sky/packages/sky/lib/animation/animation_performance.dart @@ -145,7 +145,8 @@ class AnimationPerformance { } void _tick(double t) { - variable.setProgress(t); + if (variable != null) + variable.setProgress(t); _notifyListeners(); _checkStatusChanged(); } diff --git a/sky/packages/sky/lib/widgets/drawer.dart b/sky/packages/sky/lib/widgets/drawer.dart index 398926216e620f6d83305dfe061ab7963d89d34a..d075d1acea58e67c325130e114ea63ff5fdda2cd 100644 --- a/sky/packages/sky/lib/widgets/drawer.dart +++ b/sky/packages/sky/lib/widgets/drawer.dart @@ -5,6 +5,7 @@ import 'dart:async'; import 'dart:sky' as sky; +import 'package:sky/animation/animated_value.dart'; import 'package:sky/animation/animation_performance.dart'; import 'package:sky/animation/forces.dart'; import 'package:sky/theme/shadows.dart'; @@ -15,6 +16,7 @@ import 'package:sky/widgets/basic.dart'; import 'package:sky/widgets/navigator.dart'; import 'package:sky/widgets/scrollable.dart'; import 'package:sky/widgets/theme.dart'; +import 'package:sky/widgets/transitions.dart'; export 'package:sky/animation/animation_performance.dart' show AnimationStatus; @@ -57,17 +59,11 @@ class Drawer extends StatefulComponent { DrawerStatusChangedCallback onStatusChanged; Navigator navigator; - SlideInIntention _intention; - ColorTransitionIntention _maskColorIntention; - AnimationPerformance get _performance => _intention.performance; + AnimationPerformance _performance; void initState() { - _intention = new SlideInIntention( - duration: _kBaseSettleDuration, start: _kClosedPosition, end: _kOpenPosition); - _maskColorIntention = new ColorTransitionIntention( - performance: _intention.performance, start: colors.transparent, end: const Color(0x7F000000)); + _performance = new AnimationPerformance(duration: _kBaseSettleDuration); - _performance.addStatusListener(_onStatusChanged); // Use a spring force for animating the drawer. We can't use curves for // this because we need a linear curve in order to track the user's finger // while dragging. @@ -87,27 +83,27 @@ class Drawer extends StatefulComponent { Widget build() { var mask = new Listener( - child: new AnimatedContainer( - intentions: [_maskColorIntention], - tag: showing + child: new ColorTransition( + performance: _performance, + direction: showing ? Direction.forward : Direction.reverse, + color: new AnimatedColorValue(colors.transparent, end: const Color(0x7F000000)) ), onGestureTap: handleMaskTap ); - Widget content = new AnimatedContainer( - intentions: [ - _intention, - // TODO(mpcomplete): it should be easier to override some intentions, - // and have those you don't care about revert to a sensible default. - new ImplicitlySyncDecorationIntention(_kThemeChangeDuration), - new ImplicitlySyncWidthIntention(_kThemeChangeDuration), - ], - tag: showing, - decoration: new BoxDecoration( - backgroundColor: Theme.of(this).canvasColor, - boxShadow: shadows[level]), - width: _kWidth, - child: new ScrollableBlock(children) + Widget content = new SlideIn( + performance: _performance, + direction: showing ? Direction.forward : Direction.reverse, + position: new AnimatedValue(_kClosedPosition, end: _kOpenPosition), + onDismissed: _onDismissed, + child: new AnimatedContainer( + intentions: implicitlySyncFieldsIntention(const Duration(milliseconds: 200)), + decoration: new BoxDecoration( + backgroundColor: Theme.of(this).canvasColor, + boxShadow: shadows[level]), + width: _kWidth, + child: new ScrollableBlock(children) + ) ); return new Listener( @@ -120,6 +116,10 @@ class Drawer extends StatefulComponent { ); } + void _onDismissed() { + _onStatusChanged(AnimationStatus.dismissed); + } + void _onStatusChanged(AnimationStatus status) { scheduleMicrotask(() { if (status == AnimationStatus.dismissed && diff --git a/sky/packages/sky/lib/widgets/navigator.dart b/sky/packages/sky/lib/widgets/navigator.dart index 6a1b9cce0912ef31ecf88b00f3a6de0035f24b0b..0fd913eecd1504bd405a90929c93bc9321f5b2d1 100644 --- a/sky/packages/sky/lib/widgets/navigator.dart +++ b/sky/packages/sky/lib/widgets/navigator.dart @@ -11,6 +11,7 @@ import 'package:sky/animation/forces.dart'; import 'package:sky/widgets/animated_component.dart'; import 'package:sky/widgets/basic.dart'; import 'package:sky/widgets/focus.dart'; +import 'package:sky/widgets/transitions.dart'; import 'package:vector_math/vector_math.dart'; typedef Widget RouteBuilder(Navigator navigator, RouteBase route); @@ -66,76 +67,39 @@ class RouteState extends RouteBase { // and support multiple transition types const Duration _kTransitionDuration = const Duration(milliseconds: 150); const Point _kTransitionStartPoint = const Point(0.0, 75.0); -class Transition extends AnimatedComponent { +class Transition extends TransitionBase { Transition({ Key key, - this.content, - this.direction, - this.onDismissed, - this.onCompleted, + Widget child, + Direction direction, + Function onDismissed, + Function onCompleted, this.interactive - }): super(key: key); - Widget content; - Direction direction; + }): super(key: key, + child: child, + duration: _kTransitionDuration, + direction: direction, + onDismissed: onDismissed, + onCompleted: onCompleted); bool interactive; - Function onDismissed; - Function onCompleted; - - AnimatedValue _position; - AnimatedValue _opacity; - AnimationPerformance _performance; - - void initState() { - _position = new AnimatedValue( - _kTransitionStartPoint, - end: Point.origin, - curve: easeOut - ); - _opacity = new AnimatedValue(0.0, end: 1.0, curve: easeOut); - _performance = new AnimationPerformance(duration: _kTransitionDuration) - ..variable = new AnimatedList([_position, _opacity]); - if (direction == Direction.reverse) - _performance.progress = 1.0; - _performance.addStatusListener(_checkStatusChanged); - watch(_performance); - _start(); - } - - void _start() { - _performance.play(direction); - } void syncFields(Transition source) { - content = source.content; interactive = source.interactive; - onDismissed = source.onDismissed; - if (direction != source.direction) { - direction = source.direction; - _start(); - } super.syncFields(source); } - void _checkStatusChanged(AnimationStatus status) { - if (_performance.isDismissed) { - if (onDismissed != null) - onDismissed(); - } else if (_performance.isCompleted) { - if (onCompleted != null) - onCompleted(); - } - } - 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 + return new SlideIn( + performance: performance, + direction: direction, + position: new AnimatedValue(_kTransitionStartPoint, end: Point.origin, curve: easeOut), + child: new FadeIn( + performance: performance, + direction: direction, + opacity: new AnimatedValue(0.0, end: 1.0, curve: easeOut), + child: child ) ); } @@ -233,16 +197,16 @@ class Navigator extends StatefulComponent { if (i + 1 < state.history.length && state.history[i + 1].fullyOpaque) continue; HistoryEntry historyEntry = state.history[i]; - Widget content = historyEntry.route.build(this, historyEntry.route); + Widget child = historyEntry.route.build(this, historyEntry.route); if (i == 0) { - visibleRoutes.add(content); + visibleRoutes.add(child); continue; } - if (content == null) + if (child == null) continue; Transition transition = new Transition( key: new Key.fromObjectIdentity(historyEntry), - content: content, + child: child, direction: (i <= state.historyIndex) ? Direction.forward : Direction.reverse, interactive: (i == state.historyIndex), onDismissed: () { diff --git a/sky/packages/sky/lib/widgets/snack_bar.dart b/sky/packages/sky/lib/widgets/snack_bar.dart index 9496ead5de33fabd84300f2d60c4936c960bc14f..44784219cf4d8ff5696eb44ebb007d33cfc5b7bd 100644 --- a/sky/packages/sky/lib/widgets/snack_bar.dart +++ b/sky/packages/sky/lib/widgets/snack_bar.dart @@ -4,15 +4,16 @@ import 'dart:async'; +import 'package:sky/animation/animated_value.dart'; import 'package:sky/animation/animation_performance.dart'; import 'package:sky/painting/text_style.dart'; import 'package:sky/theme/typography.dart' as typography; import 'package:sky/widgets/animated_container.dart'; -import 'package:sky/widgets/animation_intentions.dart'; import 'package:sky/widgets/basic.dart'; import 'package:sky/widgets/default_text_style.dart'; import 'package:sky/widgets/material.dart'; import 'package:sky/widgets/theme.dart'; +import 'package:sky/widgets/transitions.dart'; export 'package:sky/animation/animation_performance.dart' show AnimationStatus; @@ -39,8 +40,7 @@ class SnackBarAction extends Component { ); } } - -class SnackBar extends StatefulComponent { +class SnackBar extends Component { SnackBar({ Key key, @@ -57,25 +57,9 @@ class SnackBar extends StatefulComponent { bool showing; SnackBarStatusChangedCallback onStatusChanged; - SlideInIntention _intention; - - void initState() { - _intention = new SlideInIntention(duration: _kSlideInDuration, - start: const Point(0.0, 50.0), - end: Point.origin); - _intention.performance.addStatusListener(_onStatusChanged); - } - - void syncFields(SnackBar source) { - content = source.content; - actions = source.actions; - onStatusChanged = source.onStatusChanged; - showing = source.showing; - } - - void _onStatusChanged(AnimationStatus status) { + void _onDismissed() { if (onStatusChanged != null) - scheduleMicrotask(() { onStatusChanged(status); }); + scheduleMicrotask(() { onStatusChanged(AnimationStatus.dismissed); }); } Widget build() { @@ -91,9 +75,12 @@ class SnackBar extends StatefulComponent { ) ]..addAll(actions); - return new AnimatedContainer( - intentions: [_intention], - tag: showing, + return new SlideIn( + duration: _kSlideInDuration, + direction: showing ? Direction.forward : Direction.reverse, + position: new AnimatedValue(const Point(0.0, 50.0), + end: Point.origin), + onDismissed: _onDismissed, child: new Material( level: 2, color: const Color(0xFF323232), diff --git a/sky/packages/sky/lib/widgets/transitions.dart b/sky/packages/sky/lib/widgets/transitions.dart new file mode 100644 index 0000000000000000000000000000000000000000..63b015644cec263edbbe27ed74f4632ccaeaa35c --- /dev/null +++ b/sky/packages/sky/lib/widgets/transitions.dart @@ -0,0 +1,170 @@ +// 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:sky/widgets/animated_component.dart'; +import 'package:sky/animation/animation_performance.dart'; +import 'package:sky/animation/animated_value.dart'; +import 'package:sky/widgets/animated_component.dart'; +import 'package:sky/widgets/basic.dart'; +import 'package:vector_math/vector_math.dart'; + +abstract class TransitionBase extends AnimatedComponent { + TransitionBase({ + Key key, + this.child, + this.direction, + this.duration, + this.performance, + this.onDismissed, + this.onCompleted + }) : super(key: key); + + Widget child; + Direction direction; + Duration duration; + AnimationPerformance performance; + Function onDismissed; + Function onCompleted; + + void initState() { + if (performance == null) { + assert(duration != null); + performance = new AnimationPerformance(duration: duration); + } + if (direction == Direction.reverse) + performance.progress = 1.0; + performance.addStatusListener(_checkStatusChanged); + + watch(performance); + _start(); + } + + void syncFields(TransitionBase source) { + child = source.child; + onCompleted = source.onCompleted; + onDismissed = source.onDismissed; + duration = source.duration; + if (direction != source.direction) { + direction = source.direction; + _start(); + } + super.syncFields(source); + } + + void _start() { + performance.play(direction); + } + + void _checkStatusChanged(AnimationStatus status) { + if (performance.isDismissed) { + if (onDismissed != null) + onDismissed(); + } else if (performance.isCompleted) { + if (onCompleted != null) + onCompleted(); + } + } + + Widget build(); +} + +class SlideIn extends TransitionBase { + // TODO(mpcomplete): this constructor is mostly boilerplate, passing values + // to super. Is there a simpler way? + SlideIn({ + Key key, + this.position, + Duration duration, + AnimationPerformance performance, + Direction direction, + Function onDismissed, + Function onCompleted, + Widget child + }) : super(key: key, + duration: duration, + performance: performance, + direction: direction, + onDismissed: onDismissed, + onCompleted: onCompleted, + child: child); + + AnimatedValue position; + + void syncFields(SlideIn updated) { + position = updated.position; + super.syncFields(updated); + } + + Widget build() { + position.setProgress(performance.progress); + Matrix4 transform = new Matrix4.identity() + ..translate(position.value.x, position.value.y); + return new Transform(transform: transform, child: child); + } +} + +class FadeIn extends TransitionBase { + FadeIn({ + Key key, + this.opacity, + Duration duration, + AnimationPerformance performance, + Direction direction, + Function onDismissed, + Function onCompleted, + Widget child + }) : super(key: key, + duration: duration, + performance: performance, + direction: direction, + onDismissed: onDismissed, + onCompleted: onCompleted, + child: child); + + AnimatedValue opacity; + + void syncFields(FadeIn updated) { + opacity = updated.opacity; + super.syncFields(updated); + } + + Widget build() { + opacity.setProgress(performance.progress); + return new Opacity(opacity: opacity.value, child: child); + } +} + +class ColorTransition extends TransitionBase { + ColorTransition({ + Key key, + this.color, + Duration duration, + AnimationPerformance performance, + Direction direction, + Function onDismissed, + Function onCompleted, + Widget child + }) : super(key: key, + duration: duration, + performance: performance, + direction: direction, + onDismissed: onDismissed, + onCompleted: onCompleted, + child: child); + + AnimatedColorValue color; + + void syncFields(ColorTransition updated) { + color = updated.color; + super.syncFields(updated); + } + + Widget build() { + color.setProgress(performance.progress); + return new DecoratedBox( + decoration: new BoxDecoration(backgroundColor: color.value), + child: child + ); + } +}