提交 c07f8aaa 编写于 作者: I Ian Hickson

Merge pull request #1175 from Hixie/performance-view

Require that you pass transitions a performance.
......@@ -251,13 +251,13 @@ class StockHome extends StatefulComponent {
});
}
Anchor _snackBarAnchor = new Anchor();
GlobalKey snackBarKey = new GlobalKey(label: 'snackbar');
Widget buildSnackBar() {
if (_snackBarStatus == AnimationStatus.dismissed)
return null;
return new SnackBar(
transitionKey: snackBarKey,
showing: _isSnackBarShowing,
anchor: _snackBarAnchor,
content: new Text("Stock purchased!"),
actions: [new SnackBarAction(label: "UNDO", onPressed: _handleUndo)],
onDismissed: () { setState(() { _snackBarStatus = AnimationStatus.dismissed; }); }
......@@ -272,12 +272,14 @@ class StockHome extends StatefulComponent {
}
Widget buildFloatingActionButton() {
return _snackBarAnchor.build(
new FloatingActionButton(
return new TransitionProxy(
transitionKey: snackBarKey,
child: new FloatingActionButton(
child: new Icon(type: 'content/add', size: 24),
backgroundColor: Colors.redAccent[200],
onPressed: _handleStockPurchased
));
)
);
}
void addMenuToOverlays(List<Widget> overlays) {
......
......@@ -15,7 +15,7 @@ class Stocklist extends Component {
child: new ScrollableList<Stock>(
items: stocks,
itemExtent: StockRow.kHeight,
itemBuilder: (stock) => new StockRow(stock: stock)
itemBuilder: (Stock stock) => new StockRow(stock: stock)
)
);
}
......
......@@ -22,21 +22,28 @@ class ProgressIndicatorApp extends App {
reverseCurve: ease,
interval: new Interval(0.0, 0.9)
);
valueAnimation.addStatusListener((AnimationStatus status) {
if (status == AnimationStatus.dismissed || status == AnimationStatus.completed)
reverseValueAnimationDirection();
});
valueAnimation.play(valueAnimationDirection);
}
void handleTap() {
if (valueAnimation.isAnimating)
valueAnimation.stop();
else
valueAnimation.resume();
setState(() {
// valueAnimation.isAnimating is part of our build state
if (valueAnimation.isAnimating)
valueAnimation.stop();
else
valueAnimation.resume();
});
}
void reverseValueAnimationDirection() {
setState(() {
valueAnimationDirection = (valueAnimationDirection == Direction.forward)
? Direction.reverse
: Direction.forward;
});
valueAnimationDirection = (valueAnimationDirection == Direction.forward)
? Direction.reverse
: Direction.forward;
valueAnimation.play(valueAnimationDirection);
}
Widget buildIndicators() {
......@@ -58,11 +65,12 @@ class ProgressIndicatorApp extends App {
width: 50.0,
height: 30.0,
child: new CircularProgressIndicator(value: valueAnimation.value)
)
),
new Text("${(valueAnimation.value * 100.0).toStringAsFixed(1)}%" + (valueAnimation.isAnimating ? '' : ' (paused)'))
];
return new Column(
indicators
.map((c) => new Container(child: c, margin: const EdgeDims.symmetric(vertical: 20.0)))
.map((c) => new Container(child: c, margin: const EdgeDims.symmetric(vertical: 15.0, horizontal: 20.0)))
.toList(),
justifyContent: FlexJustifyContent.center
);
......@@ -76,10 +84,7 @@ class ProgressIndicatorApp extends App {
decoration: new BoxDecoration(backgroundColor: Theme.of(this).cardColor),
child: new BuilderTransition(
variables: [valueAnimation.variable],
direction: valueAnimationDirection,
performance: valueAnimation,
onDismissed: reverseValueAnimationDirection,
onCompleted: reverseValueAnimationDirection,
performance: valueAnimation.view,
builder: buildIndicators
)
)
......@@ -94,10 +99,13 @@ class ProgressIndicatorApp extends App {
accentColor: Colors.redAccent[200]
),
child: new Title(
title: 'Cards',
title: 'Progress Indicators',
child: new Scaffold(
toolbar: new ToolBar(center: new Text('Progress Indicators')),
body: body
body: new DefaultTextStyle(
style: Theme.of(this).text.title,
child: body
)
)
)
)
......
......@@ -15,7 +15,10 @@ enum Direction {
reverse
}
/// A variable that changes as an animation progresses
/// An interface describing a variable that changes as an animation progresses.
///
/// AnimatedVariables, by convention, must be cheap to create. This allows them to be used in
/// build functions in Widgets.
abstract class AnimatedVariable {
/// Update the variable to a given time in an animation that is running in the given direction
void setProgress(double t, Direction direction);
......
......@@ -23,6 +23,26 @@ enum AnimationStatus {
completed,
}
typedef void AnimationPerformanceListener();
typedef void AnimationPerformanceStatusListener(AnimationStatus status);
/// An interface that is implemented by [AnimationPerformance] that exposes a
/// read-only view of the underlying performance. This is used by classes that
/// want to watch a performance but should not be able to change the
/// performance's state.
abstract class WatchableAnimationPerformance {
/// Update the given variable according to the current progress of the performance
void updateVariable(AnimatedVariable variable);
/// Calls the listener every time the progress of the performance changes
void addListener(AnimationPerformanceListener listener);
/// Stop calling the listener every time the progress of the performance changes
void removeListener(AnimationPerformanceListener listener);
/// Calls listener every time the status of the performance changes
void addStatusListener(AnimationPerformanceStatusListener listener);
/// Stops calling the listener every time the status of the performance changes
void removeStatusListener(AnimationPerformanceStatusListener listener);
}
/// A collection of values that animated based on a timeline
///
/// For example, a performance may handle an animation of a menu opening by
......@@ -31,12 +51,17 @@ enum AnimationStatus {
/// may also take direct control of the timeline by manipulating [progress], or
/// [fling] the timeline causing a physics-based simulation to take over the
/// progression.
class AnimationPerformance {
class AnimationPerformance implements WatchableAnimationPerformance {
AnimationPerformance({ AnimatedVariable variable, this.duration }) :
_variable = variable {
_timeline = new Timeline(_tick);
}
/// Returns a [WatchableAnimationPerformance] for this performance,
/// so that a pointer to this object can be passed around without
/// allowing users of that pointer to mutate the AnimationPerformance state.
WatchableAnimationPerformance get view => this;
/// The length of time this performance should last
Duration duration;
......@@ -155,33 +180,33 @@ class AnimationPerformance {
return _timeline.fling(force.release(progress, velocity));
}
final List<Function> _listeners = new List<Function>();
final List<AnimationPerformanceListener> _listeners = new List<AnimationPerformanceListener>();
/// Calls the listener every time the progress of this performance changes
void addListener(Function listener) {
void addListener(AnimationPerformanceListener listener) {
_listeners.add(listener);
}
/// Stop calling the listener every time the progress of this performance changes
void removeListener(Function listener) {
void removeListener(AnimationPerformanceListener listener) {
_listeners.remove(listener);
}
void _notifyListeners() {
List<Function> localListeners = new List<Function>.from(_listeners);
for (Function listener in localListeners)
List<AnimationPerformanceListener> localListeners = new List<AnimationPerformanceListener>.from(_listeners);
for (AnimationPerformanceListener listener in localListeners)
listener();
}
final List<Function> _statusListeners = new List<Function>();
final List<AnimationPerformanceStatusListener> _statusListeners = new List<AnimationPerformanceStatusListener>();
/// Calls listener every time the status of this performance changes
void addStatusListener(Function listener) {
void addStatusListener(AnimationPerformanceStatusListener listener) {
_statusListeners.add(listener);
}
/// Stops calling the listener every time the status of this performance changes
void removeStatusListener(Function listener) {
void removeStatusListener(AnimationPerformanceStatusListener listener) {
_statusListeners.remove(listener);
}
......@@ -189,8 +214,8 @@ class AnimationPerformance {
void _checkStatusChanged() {
AnimationStatus currentStatus = status;
if (currentStatus != _lastStatus) {
List<Function> localListeners = new List<Function>.from(_statusListeners);
for (Function listener in localListeners)
List<AnimationPerformanceStatusListener> localListeners = new List<AnimationPerformanceStatusListener>.from(_statusListeners);
for (AnimationPerformanceStatusListener listener in localListeners)
listener(currentStatus);
}
_lastStatus = currentStatus;
......
......@@ -7,47 +7,49 @@ import 'package:sky/src/widgets/framework.dart';
abstract class AnimatedComponent extends StatefulComponent {
AnimatedComponent({ Key key }) : super(key: key);
void syncConstructorArguments(AnimatedComponent source) { }
final List<AnimationPerformance> _watchedPerformances = new List<AnimationPerformance>();
void _performanceChanged() {
setState(() {
// We don't actually have any state to change, per se,
// we just know that we have in fact changed state.
});
AnimatedComponent({ Key key, this.direction, this.duration }) : super(key: key);
Duration duration;
Direction direction;
void syncConstructorArguments(AnimatedComponent source) {
bool resumePerformance = false;
if (duration != source.duration) {
duration = source.duration;
resumePerformance = true;
}
if (direction != source.direction) {
direction = source.direction;
resumePerformance = true;
}
if (resumePerformance)
performance.play(direction);
}
bool isWatching(AnimationPerformance performance) {
return _watchedPerformances.contains(performance);
}
AnimationPerformance get performance => _performance;
AnimationPerformance _performance;
void watch(AnimationPerformance performance) {
assert(!isWatching(performance));
_watchedPerformances.add(performance);
if (mounted)
performance.addListener(_performanceChanged);
}
void unwatch(AnimationPerformance performance) {
assert(isWatching(performance));
_watchedPerformances.remove(performance);
if (mounted)
performance.removeListener(_performanceChanged);
}
void didMount() {
for (AnimationPerformance performance in _watchedPerformances)
performance.addListener(_performanceChanged);
super.didMount();
void initState() {
_performance = new AnimationPerformance(duration: duration);
performance.addStatusListener((AnimationStatus status) {
if (status == AnimationStatus.completed)
handleCompleted();
else if (status == AnimationStatus.dismissed)
handleDismissed();
});
if (buildDependsOnPerformance) {
performance.addListener(() {
setState(() {
// We don't actually have any state to change, per se,
// we just know that we have in fact changed state.
});
});
}
performance.play(direction);
}
void didUnmount() {
for (AnimationPerformance performance in _watchedPerformances)
performance.removeListener(_performanceChanged);
super.didUnmount();
}
bool get buildDependsOnPerformance => false;
void handleCompleted() { }
void handleDismissed() { }
}
......@@ -131,6 +131,8 @@ class Dialog extends Component {
}
}
const Duration _kTransitionDuration = const Duration(milliseconds: 150);
class DialogRoute extends RouteBase {
DialogRoute({ this.completer, this.builder });
......@@ -144,28 +146,10 @@ class DialogRoute extends RouteBase {
completer.complete(result);
}
TransitionBase buildTransition({ Key key }) => new DialogTransition(key: key);
}
const Duration _kTransitionDuration = const Duration(milliseconds: 150);
class DialogTransition extends TransitionBase {
DialogTransition({
Key key,
Widget child,
Direction direction,
Function onDismissed,
Function onCompleted
}): super(key: key,
child: child,
duration: _kTransitionDuration,
direction: direction,
onDismissed: onDismissed,
onCompleted: onCompleted);
Widget buildWithChild(Widget child) {
Duration get transitionDuration => _kTransitionDuration;
TransitionBase buildTransition({ Key key, Widget child, WatchableAnimationPerformance performance }) {
return new FadeTransition(
performance: performance,
direction: direction,
opacity: new AnimatedValue<double>(0.0, end: 1.0, curve: easeOut),
child: child
);
......
......@@ -54,6 +54,10 @@ class Dismissable extends StatefulComponent {
void initState() {
_fadePerformance = new AnimationPerformance(duration: _kCardDismissFadeout);
_fadePerformance.addStatusListener((AnimationStatus status) {
if (status == AnimationStatus.completed)
_handleFadeCompleted();
});
}
void syncConstructorArguments(Dismissable source) {
......@@ -99,6 +103,7 @@ class Dismissable extends StatefulComponent {
_resizePerformance = new AnimationPerformance()
..duration = _kCardDismissResize
..addListener(_handleResizeProgressChanged);
_resizePerformance.play();
});
}
......@@ -226,8 +231,7 @@ class Dismissable extends StatefulComponent {
);
return new SquashTransition(
performance: _resizePerformance,
direction: Direction.forward,
performance: _resizePerformance.view,
width: _directionIsYAxis ? squashAxisExtent : null,
height: !_directionIsYAxis ? squashAxisExtent : null
);
......@@ -243,11 +247,10 @@ class Dismissable extends StatefulComponent {
child: new SizeObserver(
callback: _handleSizeChanged,
child: new FadeTransition(
performance: _fadePerformance,
onCompleted: _handleFadeCompleted,
performance: _fadePerformance.view,
opacity: new AnimatedValue<double>(1.0, end: 0.0),
child: new SlideTransition(
performance: _fadePerformance,
performance: _fadePerformance.view,
position: new AnimatedValue<Point>(Point.origin, end: _activeCardDragEndPoint),
child: child
)
......
......@@ -58,32 +58,41 @@ class Drawer extends StatefulComponent {
void initState() {
_performance = new AnimationPerformance(duration: _kBaseSettleDuration);
_performance.addStatusListener((AnimationStatus status) {
if (status == AnimationStatus.dismissed)
_handleDismissed();
});
// 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.
_performance.attachedForce = kDefaultSpringForce;
if (navigator != null) {
// TODO(ianh): This is crazy. We should convert drawer to use a pattern like openDialog().
// https://github.com/domokit/sky_engine/pull/1186
scheduleMicrotask(() {
navigator.pushState(this, (_) => _performance.reverse());
});
}
_performance.play(_direction);
}
Direction get _direction => showing ? Direction.forward : Direction.reverse;
void syncConstructorArguments(Drawer source) {
children = source.children;
if (showing != source.showing) {
showing = source.showing;
_performance.play(_direction);
}
level = source.level;
navigator = source.navigator;
showing = source.showing;
onDismissed = source.onDismissed;
navigator = source.navigator;
}
Widget build() {
var mask = new GestureDetector(
child: new ColorTransition(
performance: _performance,
direction: showing ? Direction.forward : Direction.reverse,
performance: _performance.view,
color: new AnimatedColorValue(Colors.transparent, end: const Color(0x7F000000)),
child: new Container()
),
......@@ -93,10 +102,8 @@ class Drawer extends StatefulComponent {
);
Widget content = new SlideTransition(
performance: _performance,
direction: showing ? Direction.forward : Direction.reverse,
performance: _performance.view,
position: new AnimatedValue<Point>(_kClosedPosition, end: _kOpenPosition),
onDismissed: _onDismissed,
child: new AnimatedContainer(
behavior: implicitlyAnimate(const Duration(milliseconds: 200)),
decoration: new BoxDecoration(
......@@ -115,7 +122,7 @@ class Drawer extends StatefulComponent {
);
}
void _onDismissed() {
void _handleDismissed() {
if (navigator != null &&
navigator.currentRoute is RouteState &&
(navigator.currentRoute as RouteState).owner == this) // TODO(ianh): remove cast once analyzer is cleverer
......
......@@ -13,12 +13,14 @@ abstract class GlobalKeyWatcher extends StatefulComponent {
GlobalKey watchedKey;
void syncConstructorArguments(GlobalKeyWatcher source) {
if (source != source.watchedKey) {
_removeListeners();
if (watchedKey != source.watchedKey) {
if (watchedKey != null)
_removeListeners();
watchedKey = source.watchedKey;
if (mounted)
if (mounted && watchedKey != null) {
_setWatchedWidget(GlobalKey.getWidget(watchedKey));
_addListeners();
_addListeners();
}
}
}
......@@ -29,7 +31,7 @@ abstract class GlobalKeyWatcher extends StatefulComponent {
if (watchedWidget != value) {
if (watchedWidget != null)
stopWatching();
assert(debugValidateWatchedWidget(value));
assert(value == null || debugValidateWatchedWidget(value));
setState(() {
_watchedWidget = value;
});
......@@ -42,13 +44,16 @@ abstract class GlobalKeyWatcher extends StatefulComponent {
void didMount() {
super.didMount();
_setWatchedWidget(GlobalKey.getWidget(watchedKey));
_addListeners();
if (watchedKey != null) {
_setWatchedWidget(GlobalKey.getWidget(watchedKey));
_addListeners();
}
}
void didUnmount() {
super.didUnmount();
_removeListeners();
if (watchedKey != null)
_removeListeners();
_setWatchedWidget(null);
}
......
......@@ -10,13 +10,49 @@ import 'package:sky/src/widgets/transitions.dart';
typedef Widget RouteBuilder(Navigator navigator, RouteBase route);
typedef void NotificationCallback();
abstract class RouteBase {
Widget build(Navigator navigator, RouteBase route);
bool get isOpaque;
void popState([dynamic result]) { assert(result == null); }
TransitionBase buildTransition({ Key key });
AnimationPerformance _performance;
NotificationCallback onDismissed;
NotificationCallback onCompleted;
WatchableAnimationPerformance ensurePerformance({ Direction direction }) {
assert(direction != null);
if (_performance == null) {
_performance = new AnimationPerformance(duration: transitionDuration);
_performance.addStatusListener((AnimationStatus status) {
switch (status) {
case AnimationStatus.dismissed:
if (onDismissed != null)
onDismissed();
break;
case AnimationStatus.completed:
if (onCompleted != null)
onCompleted();
break;
default:
;
}
});
}
AnimationStatus desiredStatus = direction == Direction.forward ? AnimationStatus.forward : AnimationStatus.reverse;
if (_performance.status != desiredStatus)
_performance.play(direction);
return _performance.view;
}
Duration get transitionDuration;
TransitionBase buildTransition({ Key key, Widget child, WatchableAnimationPerformance performance });
String toString() => '$runtimeType()';
}
const Duration _kTransitionDuration = const Duration(milliseconds: 150);
const Point _kTransitionStartPoint = const Point(0.0, 75.0);
class Route extends RouteBase {
Route({ this.name, this.builder });
......@@ -25,7 +61,24 @@ class Route extends RouteBase {
Widget build(Navigator navigator, RouteBase route) => builder(navigator, route);
bool get isOpaque => true;
TransitionBase buildTransition({ Key key }) => new SlideUpFadeTransition(key: key);
Duration get transitionDuration => _kTransitionDuration;
TransitionBase buildTransition({ Key key, Widget child, WatchableAnimationPerformance performance }) {
// TODO(jackson): Hit testing should ignore transform
// TODO(jackson): Block input unless content is interactive
return new SlideTransition(
key: key,
performance: performance,
position: new AnimatedValue<Point>(_kTransitionStartPoint, end: Point.origin, curve: easeOut),
child: new FadeTransition(
performance: performance,
opacity: new AnimatedValue<double>(0.0, end: 1.0, curve: easeOut),
child: child
)
);
}
String toString() => '$runtimeType(name="$name")';
}
class RouteState extends RouteBase {
......@@ -44,44 +97,14 @@ class RouteState extends RouteBase {
callback(this);
}
TransitionBase buildTransition({ Key key }) {
// Custom state routes shouldn't be asked to construct a transition
// Custom state routes shouldn't be asked to construct a transition
Duration get transitionDuration {
assert(false);
return null;
return const Duration();
}
}
// TODO(jackson): Refactor this into its own file
const Duration _kTransitionDuration = const Duration(milliseconds: 150);
const Point _kTransitionStartPoint = const Point(0.0, 75.0);
class SlideUpFadeTransition extends TransitionBase {
SlideUpFadeTransition({
Key key,
Widget child,
Direction direction,
Function onDismissed,
Function onCompleted
}): super(key: key,
child: child,
duration: _kTransitionDuration,
direction: direction,
onDismissed: onDismissed,
onCompleted: onCompleted);
Widget buildWithChild(Widget child) {
// TODO(jackson): Hit testing should ignore transform
// TODO(jackson): Block input unless content is interactive
return new SlideTransition(
performance: performance,
direction: direction,
position: new AnimatedValue<Point>(_kTransitionStartPoint, end: Point.origin, curve: easeOut),
child: new FadeTransition(
performance: performance,
direction: direction,
opacity: new AnimatedValue<double>(0.0, end: 1.0, curve: easeOut),
child: child
)
);
TransitionBase buildTransition({ Key key, Widget child, WatchableAnimationPerformance performance }) {
assert(false);
return null;
}
}
......@@ -90,6 +113,7 @@ class HistoryEntry {
final RouteBase route;
bool fullyOpaque = false;
// TODO(jackson): Keep track of the requested transition
String toString() => "HistoryEntry($route, hashCode=$hashCode)";
}
class NavigationState {
......@@ -116,6 +140,7 @@ class NavigationState {
}
void push(RouteBase route) {
assert(!_debugCurrentlyHaveRoute(route));
HistoryEntry historyEntry = new HistoryEntry(route: route);
history.insert(historyIndex + 1, historyEntry);
historyIndex++;
......@@ -129,6 +154,10 @@ class NavigationState {
historyIndex--;
}
}
bool _debugCurrentlyHaveRoute(RouteBase route) {
return history.any((entry) => entry.route == route);
}
}
class Navigator extends StatefulComponent {
......@@ -184,19 +213,25 @@ class Navigator extends StatefulComponent {
}
if (child == null)
continue;
TransitionBase transition = historyEntry.route.buildTransition(key: new ObjectKey(historyEntry))
..child = child
..direction = (i <= state.historyIndex) ? Direction.forward : Direction.reverse
..onDismissed = () {
setState(() {
state.history.remove(historyEntry);
});
}
..onCompleted = () {
setState(() {
historyEntry.fullyOpaque = historyEntry.route.isOpaque;
});
};
WatchableAnimationPerformance performance = historyEntry.route.ensurePerformance(
direction: (i <= state.historyIndex) ? Direction.forward : Direction.reverse
);
historyEntry.route.onDismissed = () {
setState(() {
assert(state.history.contains(historyEntry));
state.history.remove(historyEntry);
});
};
historyEntry.route.onCompleted = () {
setState(() {
historyEntry.fullyOpaque = historyEntry.route.isOpaque;
});
};
TransitionBase transition = historyEntry.route.buildTransition(
key: new ObjectKey(historyEntry),
child: child,
performance: performance
);
visibleRoutes.add(transition);
}
return new Focus(child: new Stack(visibleRoutes));
......
......@@ -45,10 +45,13 @@ class PopupMenu extends StatefulComponent {
AnimationPerformance _performance;
void initState() {
_performance = new AnimationPerformance()
..duration = _kMenuDuration;
_performance = new AnimationPerformance(duration: _kMenuDuration);
_performance.timing = new AnimationTiming()
..reverseInterval = new Interval(0.0, _kMenuCloseIntervalEnd);
..reverseInterval = new Interval(0.0, _kMenuCloseIntervalEnd);
_performance.addStatusListener((AnimationStatus status) {
if (status == AnimationStatus.dismissed)
_handleDismissed();
});
_updateBoxPainter();
if (showing)
......@@ -69,6 +72,7 @@ class PopupMenu extends StatefulComponent {
void _open() {
navigator.pushState(this, (_) => _close());
_performance.play();
}
void _close() {
......@@ -82,7 +86,7 @@ class PopupMenu extends StatefulComponent {
boxShadow: shadows[level]));
}
void _onDismissed() {
void _handleDismissed() {
if (navigator != null &&
navigator.currentRoute is RouteState &&
(navigator.currentRoute as RouteState).owner == this) // TODO(ianh): remove cast once analyzer is cleverer
......@@ -100,24 +104,21 @@ class PopupMenu extends StatefulComponent {
double start = (i + 1) * unit;
double end = (start + 1.5 * unit).clamp(0.0, 1.0);
children.add(new FadeTransition(
direction: showing ? Direction.forward : Direction.reverse,
performance: _performance,
performance: _performance.view,
opacity: new AnimatedValue<double>(0.0, end: 1.0, interval: new Interval(start, end)),
child: items[i]));
child: items[i])
);
}
final width = new AnimatedValue<double>(0.0, end: 1.0, interval: new Interval(0.0, unit));
final height = new AnimatedValue<double>(0.0, end: 1.0, interval: new Interval(0.0, unit * items.length));
return new FadeTransition(
direction: showing ? Direction.forward : Direction.reverse,
performance: _performance,
onDismissed: _onDismissed,
performance: _performance.view,
opacity: new AnimatedValue<double>(0.0, end: 1.0, interval: new Interval(0.0, 1.0 / 3.0)),
child: new Container(
margin: new EdgeDims.all(_kMenuMargin),
child: new BuilderTransition(
direction: showing ? Direction.forward : Direction.reverse,
performance: _performance,
performance: _performance.view,
variables: [width, height],
builder: () {
return new CustomPaint(
......
......@@ -25,16 +25,21 @@ abstract class ProgressIndicator extends StatefulComponent {
double value; // Null for non-determinate progress indicator.
double bufferValue; // TODO(hansmuller) implement the support for this.
AnimationPerformance _animation;
double get _animationValue => (_animation.variable as AnimatedValue<double>).value;
AnimationPerformance _performance;
double get _performanceValue => (_performance.variable as AnimatedValue<double>).value;
Color get _backgroundColor => Theme.of(this).primarySwatch[200];
Color get _valueColor => Theme.of(this).primaryColor;
Object get _customPaintToken => value != null ? value : _animationValue;
Object get _customPaintToken => value != null ? value : _performanceValue;
void initState() {
_animation = new AnimationPerformance()
_performance = new AnimationPerformance()
..duration = const Duration(milliseconds: 1500)
..variable = new AnimatedValue<double>(0.0, end: 1.0, curve: ease);
_performance.addStatusListener((AnimationStatus status) {
if (status == AnimationStatus.completed)
_restartAnimation();
});
_performance.play();
}
void syncConstructorArguments(ProgressIndicator source) {
......@@ -43,8 +48,8 @@ abstract class ProgressIndicator extends StatefulComponent {
}
void _restartAnimation() {
_animation.progress = 0.0;
_animation.play();
_performance.progress = 0.0;
_performance.play();
}
Widget build() {
......@@ -52,10 +57,8 @@ abstract class ProgressIndicator extends StatefulComponent {
return _buildIndicator();
return new BuilderTransition(
variables: [_animation.variable],
direction: Direction.forward,
performance: _animation,
onCompleted: _restartAnimation,
variables: [_performance.variable],
performance: _performance.view,
builder: _buildIndicator
);
}
......@@ -81,7 +84,7 @@ class LinearProgressIndicator extends ProgressIndicator {
double width = value.clamp(0.0, 1.0) * size.width;
canvas.drawRect(Point.origin & new Size(width, size.height), paint);
} else {
double startX = size.width * (1.5 * _animationValue - 0.5);
double startX = size.width * (1.5 * _performanceValue - 0.5);
double endX = startX + 0.5 * size.width;
double x = startX.clamp(0.0, size.width);
double width = endX.clamp(0.0, size.width) - x;
......@@ -125,7 +128,7 @@ class CircularProgressIndicator extends ProgressIndicator {
..arcTo(Point.origin & size, _kStartAngle, angle, false);
canvas.drawPath(path, paint);
} else {
double startAngle = _kTwoPI * (1.75 * _animationValue - 0.75);
double startAngle = _kTwoPI * (1.75 * _performanceValue - 0.75);
double endAngle = startAngle + _kTwoPI * 0.75;
double arcAngle = startAngle.clamp(0.0, _kTwoPI);
double arcSweep = endAngle.clamp(0.0, _kTwoPI) - arcAngle;
......
......@@ -6,6 +6,7 @@
import 'package:sky/animation.dart';
import 'package:sky/painting.dart';
import 'package:sky/material.dart';
import 'package:sky/src/widgets/animated_component.dart';
import 'package:sky/src/widgets/basic.dart';
import 'package:sky/src/widgets/default_text_style.dart';
import 'package:sky/src/widgets/framework.dart';
......@@ -17,6 +18,7 @@ import 'package:sky/src/widgets/transitions.dart';
typedef void SnackBarDismissedCallback();
const Duration _kSlideInDuration = const Duration(milliseconds: 200);
// TODO(ianh): factor out some of the constants below
class SnackBarAction extends Component {
SnackBarAction({Key key, this.label, this.onPressed }) : super(key: key) {
......@@ -38,25 +40,37 @@ class SnackBarAction extends Component {
}
}
class SnackBar extends Component {
class SnackBar extends AnimatedComponent {
SnackBar({
Key key,
this.anchor,
this.transitionKey,
this.content,
this.actions,
this.showing,
bool showing,
this.onDismissed
}) : super(key: key) {
}) : super(key: key, direction: showing ? Direction.forward : Direction.reverse, duration: _kSlideInDuration) {
assert(content != null);
}
Anchor anchor;
Key transitionKey;
Widget content;
List<SnackBarAction> actions;
bool showing;
SnackBarDismissedCallback onDismissed;
void syncConstructorArguments(SnackBar source) {
transitionKey = source.transitionKey;
content = source.content;
actions = source.actions;
onDismissed = source.onDismissed;
super.syncConstructorArguments(source);
}
void handleDismissed() {
if (onDismissed != null)
onDismissed();
}
Widget build() {
List<Widget> children = [
new Flexible(
......@@ -71,15 +85,15 @@ class SnackBar extends Component {
];
if (actions != null)
children.addAll(actions);
return new SlideTransition(
duration: _kSlideInDuration,
direction: showing ? Direction.forward : Direction.reverse,
position: new AnimatedValue<Point>(Point.origin,
end: const Point(0.0, -52.0),
curve: easeIn, reverseCurve: easeOut),
onDismissed: onDismissed,
anchor: anchor,
key: transitionKey,
performance: performance.view,
position: new AnimatedValue<Point>(
Point.origin,
end: const Point(0.0, -52.0),
curve: easeIn,
reverseCurve: easeOut
),
child: new Material(
level: 2,
color: const Color(0xFF323232),
......
......@@ -542,8 +542,7 @@ class TabBar extends Scrollable {
style: textStyle,
child: new BuilderTransition(
variables: [_indicatorRect],
direction: Direction.forward,
performance: _indicatorAnimation,
performance: _indicatorAnimation.view,
builder: () {
return new TabBarWrapper(
children: tabs,
......
......@@ -6,117 +6,107 @@ import 'package:sky/animation.dart';
import 'package:sky/src/widgets/animated_component.dart';
import 'package:sky/src/widgets/basic.dart';
import 'package:sky/src/widgets/framework.dart';
import 'package:sky/src/widgets/global_key_watcher.dart';
import 'package:vector_math/vector_math.dart';
export 'package:sky/animation.dart' show Direction;
// A helper class to anchor widgets to one another. Pass an instance of this to
// a Transition, then use the build() method to create a child with the same
// transition applied.
class Anchor {
Anchor();
class TransitionProxy extends GlobalKeyWatcher {
TransitionBase transition;
Widget build(Widget child) {
return new _AnchorTransition(anchoredTo: this, child: child);
}
}
// Used with the Anchor class to apply a transition to multiple children.
class _AnchorTransition extends AnimatedComponent {
_AnchorTransition({
TransitionProxy({
Key key,
this.anchoredTo,
GlobalKey transitionKey,
this.child
}) : super(key: key);
}) : super(key: key, watchedKey: transitionKey);
Anchor anchoredTo;
Widget child;
TransitionBase get transition => anchoredTo.transition;
void initState() {
if (transition != null)
watch(transition.performance);
}
void syncConstructorArguments(_AnchorTransition source) {
if (transition != null && isWatching(transition.performance))
unwatch(transition.performance);
anchoredTo = source.anchoredTo;
if (transition != null)
watch(transition.performance);
void syncConstructorArguments(TransitionProxy source) {
child = source.child;
super.syncConstructorArguments(source);
}
bool debugValidateWatchedWidget(Widget candidate) {
return candidate is TransitionBaseWithChild;
}
TransitionBaseWithChild get transition => this.watchedWidget;
void startWatching() {
transition.performance.addListener(_performanceChanged);
}
void stopWatching() {
transition.performance.removeListener(_performanceChanged);
}
void _performanceChanged() {
setState(() {
// The performance changed, so we probably need to ask the transition
// we're watching for a rebuild.
});
}
Widget build() {
if (transition == null)
return child;
return transition.buildWithChild(child);
if (transition != null)
return transition.buildWithChild(child);
return child;
}
}
abstract class TransitionBase extends AnimatedComponent {
abstract class TransitionBase extends StatefulComponent {
TransitionBase({
Key key,
this.child,
this.anchor,
this.direction,
this.duration,
this.performance,
this.onDismissed,
this.onCompleted
}) : super(key: key);
this.performance
}) : super(key: key) {
assert(performance != null);
}
Widget child;
Anchor anchor;
Direction direction;
Duration duration;
AnimationPerformance performance;
Function onDismissed;
Function onCompleted;
void initState() {
if (anchor != null)
anchor.transition = this;
if (performance == null) {
assert(duration != null);
performance = new AnimationPerformance(duration: duration);
if (direction == Direction.reverse)
performance.progress = 1.0;
WatchableAnimationPerformance performance;
void syncConstructorArguments(TransitionBase source) {
if (performance != source.performance) {
if (mounted)
performance.removeListener(_performanceChanged);
performance = source.performance;
if (mounted)
performance.addListener(_performanceChanged);
}
performance.addStatusListener(_checkStatusChanged);
}
watch(performance);
_start();
void _performanceChanged() {
setState(() {
// The performance's state is our build state, and it changed already.
});
}
void syncConstructorArguments(TransitionBase source) {
child = source.child;
onCompleted = source.onCompleted;
onDismissed = source.onDismissed;
duration = source.duration;
if (direction != source.direction) {
direction = source.direction;
_start();
}
super.syncConstructorArguments(source);
void didMount() {
performance.addListener(_performanceChanged);
super.didMount();
}
void _start() {
performance.play(direction);
void didUnmount() {
performance.removeListener(_performanceChanged);
super.didUnmount();
}
void _checkStatusChanged(AnimationStatus status) {
if (performance.isDismissed) {
if (onDismissed != null)
onDismissed();
} else if (performance.isCompleted) {
if (onCompleted != null)
onCompleted();
}
}
abstract class TransitionBaseWithChild extends TransitionBase {
TransitionBaseWithChild({
Key key,
this.child,
WatchableAnimationPerformance performance
}) : super(key: key, performance: performance);
Widget child;
void syncConstructorArguments(TransitionBaseWithChild source) {
child = source.child;
super.syncConstructorArguments(source);
}
Widget build() {
......@@ -124,26 +114,17 @@ abstract class TransitionBase extends AnimatedComponent {
}
Widget buildWithChild(Widget child);
}
class SlideTransition extends TransitionBase {
class SlideTransition extends TransitionBaseWithChild {
SlideTransition({
Key key,
Anchor anchor,
this.position,
Duration duration,
AnimationPerformance performance,
Direction direction,
Function onDismissed,
Function onCompleted,
WatchableAnimationPerformance performance,
Widget child
}) : super(key: key,
anchor: anchor,
duration: duration,
performance: performance,
direction: direction,
onDismissed: onDismissed,
onCompleted: onCompleted,
child: child);
AnimatedValue<Point> position;
......@@ -161,24 +142,14 @@ class SlideTransition extends TransitionBase {
}
}
class FadeTransition extends TransitionBase {
class FadeTransition extends TransitionBaseWithChild {
FadeTransition({
Key key,
Anchor anchor,
this.opacity,
Duration duration,
AnimationPerformance performance,
Direction direction,
Function onDismissed,
Function onCompleted,
WatchableAnimationPerformance performance,
Widget child
}) : super(key: key,
anchor: anchor,
duration: duration,
performance: performance,
direction: direction,
onDismissed: onDismissed,
onCompleted: onCompleted,
child: child);
AnimatedValue<double> opacity;
......@@ -194,24 +165,14 @@ class FadeTransition extends TransitionBase {
}
}
class ColorTransition extends TransitionBase {
class ColorTransition extends TransitionBaseWithChild {
ColorTransition({
Key key,
Anchor anchor,
this.color,
Duration duration,
AnimationPerformance performance,
Direction direction,
Function onDismissed,
Function onCompleted,
WatchableAnimationPerformance performance,
Widget child
}) : super(key: key,
anchor: anchor,
duration: duration,
performance: performance,
direction: direction,
onDismissed: onDismissed,
onCompleted: onCompleted,
child: child);
AnimatedColorValue color;
......@@ -230,25 +191,15 @@ class ColorTransition extends TransitionBase {
}
}
class SquashTransition extends TransitionBase {
class SquashTransition extends TransitionBaseWithChild {
SquashTransition({
Key key,
Anchor anchor,
this.width,
this.height,
Duration duration,
AnimationPerformance performance,
Direction direction,
Function onDismissed,
Function onCompleted,
WatchableAnimationPerformance performance,
Widget child
}) : super(key: key,
anchor: anchor,
duration: duration,
performance: performance,
direction: direction,
onDismissed: onDismissed,
onCompleted: onCompleted,
child: child);
AnimatedValue<double> width;
......@@ -274,23 +225,11 @@ typedef Widget BuilderFunction();
class BuilderTransition extends TransitionBase {
BuilderTransition({
Key key,
Anchor anchor,
this.variables,
this.builder,
Duration duration,
AnimationPerformance performance,
Direction direction,
Function onDismissed,
Function onCompleted,
Widget child
WatchableAnimationPerformance performance
}) : super(key: key,
anchor: anchor,
duration: duration,
performance: performance,
direction: direction,
onDismissed: onDismissed,
onCompleted: onCompleted,
child: child);
performance: performance);
List<AnimatedValue> variables;
BuilderFunction builder;
......@@ -301,7 +240,7 @@ class BuilderTransition extends TransitionBase {
super.syncConstructorArguments(source);
}
Widget buildWithChild(Widget child) {
Widget build() {
for (int i = 0; i < variables.length; ++i)
performance.updateVariable(variables[i]);
return builder();
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册