Commit ea541955 authored by Matt Perry's avatar Matt Perry

Add transitions.dart for common animation transitions.

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