mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
parent
01778c4895
commit
e73bbd949e
@ -251,13 +251,13 @@ class StockHome extends StatefulComponent {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Anchor _snackBarAnchor = new Anchor();
|
GlobalKey snackBarKey = new GlobalKey(label: 'snackbar');
|
||||||
Widget buildSnackBar() {
|
Widget buildSnackBar() {
|
||||||
if (_snackBarStatus == AnimationStatus.dismissed)
|
if (_snackBarStatus == AnimationStatus.dismissed)
|
||||||
return null;
|
return null;
|
||||||
return new SnackBar(
|
return new SnackBar(
|
||||||
|
transitionKey: snackBarKey,
|
||||||
showing: _isSnackBarShowing,
|
showing: _isSnackBarShowing,
|
||||||
anchor: _snackBarAnchor,
|
|
||||||
content: new Text("Stock purchased!"),
|
content: new Text("Stock purchased!"),
|
||||||
actions: [new SnackBarAction(label: "UNDO", onPressed: _handleUndo)],
|
actions: [new SnackBarAction(label: "UNDO", onPressed: _handleUndo)],
|
||||||
onDismissed: () { setState(() { _snackBarStatus = AnimationStatus.dismissed; }); }
|
onDismissed: () { setState(() { _snackBarStatus = AnimationStatus.dismissed; }); }
|
||||||
@ -272,12 +272,14 @@ class StockHome extends StatefulComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget buildFloatingActionButton() {
|
Widget buildFloatingActionButton() {
|
||||||
return _snackBarAnchor.build(
|
return new TransitionProxy(
|
||||||
new FloatingActionButton(
|
transitionKey: snackBarKey,
|
||||||
|
child: new FloatingActionButton(
|
||||||
child: new Icon(type: 'content/add', size: 24),
|
child: new Icon(type: 'content/add', size: 24),
|
||||||
backgroundColor: Colors.redAccent[200],
|
backgroundColor: Colors.redAccent[200],
|
||||||
onPressed: _handleStockPurchased
|
onPressed: _handleStockPurchased
|
||||||
));
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void addMenuToOverlays(List<Widget> overlays) {
|
void addMenuToOverlays(List<Widget> overlays) {
|
||||||
|
@ -15,7 +15,7 @@ class Stocklist extends Component {
|
|||||||
child: new ScrollableList<Stock>(
|
child: new ScrollableList<Stock>(
|
||||||
items: stocks,
|
items: stocks,
|
||||||
itemExtent: StockRow.kHeight,
|
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,
|
reverseCurve: ease,
|
||||||
interval: new Interval(0.0, 0.9)
|
interval: new Interval(0.0, 0.9)
|
||||||
);
|
);
|
||||||
|
valueAnimation.addStatusListener((AnimationStatus status) {
|
||||||
|
if (status == AnimationStatus.dismissed || status == AnimationStatus.completed)
|
||||||
|
reverseValueAnimationDirection();
|
||||||
|
});
|
||||||
|
valueAnimation.play(valueAnimationDirection);
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleTap() {
|
void handleTap() {
|
||||||
if (valueAnimation.isAnimating)
|
setState(() {
|
||||||
valueAnimation.stop();
|
// valueAnimation.isAnimating is part of our build state
|
||||||
else
|
if (valueAnimation.isAnimating)
|
||||||
valueAnimation.resume();
|
valueAnimation.stop();
|
||||||
|
else
|
||||||
|
valueAnimation.resume();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void reverseValueAnimationDirection() {
|
void reverseValueAnimationDirection() {
|
||||||
setState(() {
|
valueAnimationDirection = (valueAnimationDirection == Direction.forward)
|
||||||
valueAnimationDirection = (valueAnimationDirection == Direction.forward)
|
? Direction.reverse
|
||||||
? Direction.reverse
|
: Direction.forward;
|
||||||
: Direction.forward;
|
valueAnimation.play(valueAnimationDirection);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildIndicators() {
|
Widget buildIndicators() {
|
||||||
@ -58,11 +65,12 @@ class ProgressIndicatorApp extends App {
|
|||||||
width: 50.0,
|
width: 50.0,
|
||||||
height: 30.0,
|
height: 30.0,
|
||||||
child: new CircularProgressIndicator(value: valueAnimation.value)
|
child: new CircularProgressIndicator(value: valueAnimation.value)
|
||||||
)
|
),
|
||||||
|
new Text("${(valueAnimation.value * 100.0).toStringAsFixed(1)}%" + (valueAnimation.isAnimating ? '' : ' (paused)'))
|
||||||
];
|
];
|
||||||
return new Column(
|
return new Column(
|
||||||
indicators
|
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(),
|
.toList(),
|
||||||
justifyContent: FlexJustifyContent.center
|
justifyContent: FlexJustifyContent.center
|
||||||
);
|
);
|
||||||
@ -76,10 +84,7 @@ class ProgressIndicatorApp extends App {
|
|||||||
decoration: new BoxDecoration(backgroundColor: Theme.of(this).cardColor),
|
decoration: new BoxDecoration(backgroundColor: Theme.of(this).cardColor),
|
||||||
child: new BuilderTransition(
|
child: new BuilderTransition(
|
||||||
variables: [valueAnimation.variable],
|
variables: [valueAnimation.variable],
|
||||||
direction: valueAnimationDirection,
|
performance: valueAnimation.view,
|
||||||
performance: valueAnimation,
|
|
||||||
onDismissed: reverseValueAnimationDirection,
|
|
||||||
onCompleted: reverseValueAnimationDirection,
|
|
||||||
builder: buildIndicators
|
builder: buildIndicators
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -94,10 +99,13 @@ class ProgressIndicatorApp extends App {
|
|||||||
accentColor: Colors.redAccent[200]
|
accentColor: Colors.redAccent[200]
|
||||||
),
|
),
|
||||||
child: new Title(
|
child: new Title(
|
||||||
title: 'Cards',
|
title: 'Progress Indicators',
|
||||||
child: new Scaffold(
|
child: new Scaffold(
|
||||||
toolbar: new ToolBar(center: new Text('Progress Indicators')),
|
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
|
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 {
|
abstract class AnimatedVariable {
|
||||||
/// Update the variable to a given time in an animation that is running in the given direction
|
/// Update the variable to a given time in an animation that is running in the given direction
|
||||||
void setProgress(double t, Direction direction);
|
void setProgress(double t, Direction direction);
|
||||||
|
@ -23,6 +23,26 @@ enum AnimationStatus {
|
|||||||
completed,
|
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
|
/// A collection of values that animated based on a timeline
|
||||||
///
|
///
|
||||||
/// For example, a performance may handle an animation of a menu opening by
|
/// 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
|
/// may also take direct control of the timeline by manipulating [progress], or
|
||||||
/// [fling] the timeline causing a physics-based simulation to take over the
|
/// [fling] the timeline causing a physics-based simulation to take over the
|
||||||
/// progression.
|
/// progression.
|
||||||
class AnimationPerformance {
|
class AnimationPerformance implements WatchableAnimationPerformance {
|
||||||
AnimationPerformance({ AnimatedVariable variable, this.duration }) :
|
AnimationPerformance({ AnimatedVariable variable, this.duration }) :
|
||||||
_variable = variable {
|
_variable = variable {
|
||||||
_timeline = new Timeline(_tick);
|
_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
|
/// The length of time this performance should last
|
||||||
Duration duration;
|
Duration duration;
|
||||||
|
|
||||||
@ -155,33 +180,33 @@ class AnimationPerformance {
|
|||||||
return _timeline.fling(force.release(progress, velocity));
|
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
|
/// Calls the listener every time the progress of this performance changes
|
||||||
void addListener(Function listener) {
|
void addListener(AnimationPerformanceListener listener) {
|
||||||
_listeners.add(listener);
|
_listeners.add(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stop calling the listener every time the progress of this performance changes
|
/// Stop calling the listener every time the progress of this performance changes
|
||||||
void removeListener(Function listener) {
|
void removeListener(AnimationPerformanceListener listener) {
|
||||||
_listeners.remove(listener);
|
_listeners.remove(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _notifyListeners() {
|
void _notifyListeners() {
|
||||||
List<Function> localListeners = new List<Function>.from(_listeners);
|
List<AnimationPerformanceListener> localListeners = new List<AnimationPerformanceListener>.from(_listeners);
|
||||||
for (Function listener in localListeners)
|
for (AnimationPerformanceListener listener in localListeners)
|
||||||
listener();
|
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
|
/// Calls listener every time the status of this performance changes
|
||||||
void addStatusListener(Function listener) {
|
void addStatusListener(AnimationPerformanceStatusListener listener) {
|
||||||
_statusListeners.add(listener);
|
_statusListeners.add(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stops calling the listener every time the status of this performance changes
|
/// Stops calling the listener every time the status of this performance changes
|
||||||
void removeStatusListener(Function listener) {
|
void removeStatusListener(AnimationPerformanceStatusListener listener) {
|
||||||
_statusListeners.remove(listener);
|
_statusListeners.remove(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,8 +214,8 @@ class AnimationPerformance {
|
|||||||
void _checkStatusChanged() {
|
void _checkStatusChanged() {
|
||||||
AnimationStatus currentStatus = status;
|
AnimationStatus currentStatus = status;
|
||||||
if (currentStatus != _lastStatus) {
|
if (currentStatus != _lastStatus) {
|
||||||
List<Function> localListeners = new List<Function>.from(_statusListeners);
|
List<AnimationPerformanceStatusListener> localListeners = new List<AnimationPerformanceStatusListener>.from(_statusListeners);
|
||||||
for (Function listener in localListeners)
|
for (AnimationPerformanceStatusListener listener in localListeners)
|
||||||
listener(currentStatus);
|
listener(currentStatus);
|
||||||
}
|
}
|
||||||
_lastStatus = currentStatus;
|
_lastStatus = currentStatus;
|
||||||
|
@ -7,47 +7,49 @@ import 'package:sky/src/widgets/framework.dart';
|
|||||||
|
|
||||||
abstract class AnimatedComponent extends StatefulComponent {
|
abstract class AnimatedComponent extends StatefulComponent {
|
||||||
|
|
||||||
AnimatedComponent({ Key key }) : super(key: key);
|
AnimatedComponent({ Key key, this.direction, this.duration }) : super(key: key);
|
||||||
|
|
||||||
void syncConstructorArguments(AnimatedComponent source) { }
|
Duration duration;
|
||||||
|
Direction direction;
|
||||||
|
|
||||||
final List<AnimationPerformance> _watchedPerformances = new List<AnimationPerformance>();
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
void _performanceChanged() {
|
AnimationPerformance get performance => _performance;
|
||||||
setState(() {
|
AnimationPerformance _performance;
|
||||||
// We don't actually have any state to change, per se,
|
|
||||||
// we just know that we have in fact changed state.
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isWatching(AnimationPerformance performance) {
|
bool get buildDependsOnPerformance => false;
|
||||||
return _watchedPerformances.contains(performance);
|
void handleCompleted() { }
|
||||||
}
|
void handleDismissed() { }
|
||||||
|
|
||||||
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 didUnmount() {
|
|
||||||
for (AnimationPerformance performance in _watchedPerformances)
|
|
||||||
performance.removeListener(_performanceChanged);
|
|
||||||
super.didUnmount();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -131,6 +131,8 @@ class Dialog extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Duration _kTransitionDuration = const Duration(milliseconds: 150);
|
||||||
|
|
||||||
class DialogRoute extends RouteBase {
|
class DialogRoute extends RouteBase {
|
||||||
DialogRoute({ this.completer, this.builder });
|
DialogRoute({ this.completer, this.builder });
|
||||||
|
|
||||||
@ -144,28 +146,10 @@ class DialogRoute extends RouteBase {
|
|||||||
completer.complete(result);
|
completer.complete(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
TransitionBase buildTransition({ Key key }) => new DialogTransition(key: key);
|
Duration get transitionDuration => _kTransitionDuration;
|
||||||
}
|
TransitionBase buildTransition({ Key key, Widget child, WatchableAnimationPerformance performance }) {
|
||||||
|
|
||||||
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) {
|
|
||||||
return new FadeTransition(
|
return new FadeTransition(
|
||||||
performance: performance,
|
performance: performance,
|
||||||
direction: direction,
|
|
||||||
opacity: new AnimatedValue<double>(0.0, end: 1.0, curve: easeOut),
|
opacity: new AnimatedValue<double>(0.0, end: 1.0, curve: easeOut),
|
||||||
child: child
|
child: child
|
||||||
);
|
);
|
||||||
|
@ -54,6 +54,10 @@ class Dismissable extends StatefulComponent {
|
|||||||
|
|
||||||
void initState() {
|
void initState() {
|
||||||
_fadePerformance = new AnimationPerformance(duration: _kCardDismissFadeout);
|
_fadePerformance = new AnimationPerformance(duration: _kCardDismissFadeout);
|
||||||
|
_fadePerformance.addStatusListener((AnimationStatus status) {
|
||||||
|
if (status == AnimationStatus.completed)
|
||||||
|
_handleFadeCompleted();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void syncConstructorArguments(Dismissable source) {
|
void syncConstructorArguments(Dismissable source) {
|
||||||
@ -99,6 +103,7 @@ class Dismissable extends StatefulComponent {
|
|||||||
_resizePerformance = new AnimationPerformance()
|
_resizePerformance = new AnimationPerformance()
|
||||||
..duration = _kCardDismissResize
|
..duration = _kCardDismissResize
|
||||||
..addListener(_handleResizeProgressChanged);
|
..addListener(_handleResizeProgressChanged);
|
||||||
|
_resizePerformance.play();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -226,8 +231,7 @@ class Dismissable extends StatefulComponent {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return new SquashTransition(
|
return new SquashTransition(
|
||||||
performance: _resizePerformance,
|
performance: _resizePerformance.view,
|
||||||
direction: Direction.forward,
|
|
||||||
width: _directionIsYAxis ? squashAxisExtent : null,
|
width: _directionIsYAxis ? squashAxisExtent : null,
|
||||||
height: !_directionIsYAxis ? squashAxisExtent : null
|
height: !_directionIsYAxis ? squashAxisExtent : null
|
||||||
);
|
);
|
||||||
@ -243,11 +247,10 @@ class Dismissable extends StatefulComponent {
|
|||||||
child: new SizeObserver(
|
child: new SizeObserver(
|
||||||
callback: _handleSizeChanged,
|
callback: _handleSizeChanged,
|
||||||
child: new FadeTransition(
|
child: new FadeTransition(
|
||||||
performance: _fadePerformance,
|
performance: _fadePerformance.view,
|
||||||
onCompleted: _handleFadeCompleted,
|
|
||||||
opacity: new AnimatedValue<double>(1.0, end: 0.0),
|
opacity: new AnimatedValue<double>(1.0, end: 0.0),
|
||||||
child: new SlideTransition(
|
child: new SlideTransition(
|
||||||
performance: _fadePerformance,
|
performance: _fadePerformance.view,
|
||||||
position: new AnimatedValue<Point>(Point.origin, end: _activeCardDragEndPoint),
|
position: new AnimatedValue<Point>(Point.origin, end: _activeCardDragEndPoint),
|
||||||
child: child
|
child: child
|
||||||
)
|
)
|
||||||
|
@ -58,32 +58,41 @@ class Drawer extends StatefulComponent {
|
|||||||
|
|
||||||
void initState() {
|
void initState() {
|
||||||
_performance = new AnimationPerformance(duration: _kBaseSettleDuration);
|
_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
|
// 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.
|
||||||
_performance.attachedForce = kDefaultSpringForce;
|
_performance.attachedForce = kDefaultSpringForce;
|
||||||
|
|
||||||
if (navigator != null) {
|
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(() {
|
scheduleMicrotask(() {
|
||||||
navigator.pushState(this, (_) => _performance.reverse());
|
navigator.pushState(this, (_) => _performance.reverse());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
_performance.play(_direction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Direction get _direction => showing ? Direction.forward : Direction.reverse;
|
||||||
|
|
||||||
void syncConstructorArguments(Drawer source) {
|
void syncConstructorArguments(Drawer source) {
|
||||||
children = source.children;
|
children = source.children;
|
||||||
|
if (showing != source.showing) {
|
||||||
|
showing = source.showing;
|
||||||
|
_performance.play(_direction);
|
||||||
|
}
|
||||||
level = source.level;
|
level = source.level;
|
||||||
navigator = source.navigator;
|
|
||||||
showing = source.showing;
|
|
||||||
onDismissed = source.onDismissed;
|
onDismissed = source.onDismissed;
|
||||||
|
navigator = source.navigator;
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget build() {
|
Widget build() {
|
||||||
var mask = new GestureDetector(
|
var mask = new GestureDetector(
|
||||||
child: new ColorTransition(
|
child: new ColorTransition(
|
||||||
performance: _performance,
|
performance: _performance.view,
|
||||||
direction: showing ? Direction.forward : Direction.reverse,
|
|
||||||
color: new AnimatedColorValue(Colors.transparent, end: const Color(0x7F000000)),
|
color: new AnimatedColorValue(Colors.transparent, end: const Color(0x7F000000)),
|
||||||
child: new Container()
|
child: new Container()
|
||||||
),
|
),
|
||||||
@ -93,10 +102,8 @@ class Drawer extends StatefulComponent {
|
|||||||
);
|
);
|
||||||
|
|
||||||
Widget content = new SlideTransition(
|
Widget content = new SlideTransition(
|
||||||
performance: _performance,
|
performance: _performance.view,
|
||||||
direction: showing ? Direction.forward : Direction.reverse,
|
|
||||||
position: new AnimatedValue<Point>(_kClosedPosition, end: _kOpenPosition),
|
position: new AnimatedValue<Point>(_kClosedPosition, end: _kOpenPosition),
|
||||||
onDismissed: _onDismissed,
|
|
||||||
child: new AnimatedContainer(
|
child: new AnimatedContainer(
|
||||||
behavior: implicitlyAnimate(const Duration(milliseconds: 200)),
|
behavior: implicitlyAnimate(const Duration(milliseconds: 200)),
|
||||||
decoration: new BoxDecoration(
|
decoration: new BoxDecoration(
|
||||||
@ -115,7 +122,7 @@ class Drawer extends StatefulComponent {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onDismissed() {
|
void _handleDismissed() {
|
||||||
if (navigator != null &&
|
if (navigator != null &&
|
||||||
navigator.currentRoute is RouteState &&
|
navigator.currentRoute is RouteState &&
|
||||||
(navigator.currentRoute as RouteState).owner == this) // TODO(ianh): remove cast once analyzer is cleverer
|
(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;
|
GlobalKey watchedKey;
|
||||||
|
|
||||||
void syncConstructorArguments(GlobalKeyWatcher source) {
|
void syncConstructorArguments(GlobalKeyWatcher source) {
|
||||||
if (source != source.watchedKey) {
|
if (watchedKey != source.watchedKey) {
|
||||||
_removeListeners();
|
if (watchedKey != null)
|
||||||
|
_removeListeners();
|
||||||
watchedKey = source.watchedKey;
|
watchedKey = source.watchedKey;
|
||||||
if (mounted)
|
if (mounted && watchedKey != null) {
|
||||||
_setWatchedWidget(GlobalKey.getWidget(watchedKey));
|
_setWatchedWidget(GlobalKey.getWidget(watchedKey));
|
||||||
_addListeners();
|
_addListeners();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,7 +31,7 @@ abstract class GlobalKeyWatcher extends StatefulComponent {
|
|||||||
if (watchedWidget != value) {
|
if (watchedWidget != value) {
|
||||||
if (watchedWidget != null)
|
if (watchedWidget != null)
|
||||||
stopWatching();
|
stopWatching();
|
||||||
assert(debugValidateWatchedWidget(value));
|
assert(value == null || debugValidateWatchedWidget(value));
|
||||||
setState(() {
|
setState(() {
|
||||||
_watchedWidget = value;
|
_watchedWidget = value;
|
||||||
});
|
});
|
||||||
@ -42,13 +44,16 @@ abstract class GlobalKeyWatcher extends StatefulComponent {
|
|||||||
|
|
||||||
void didMount() {
|
void didMount() {
|
||||||
super.didMount();
|
super.didMount();
|
||||||
_setWatchedWidget(GlobalKey.getWidget(watchedKey));
|
if (watchedKey != null) {
|
||||||
_addListeners();
|
_setWatchedWidget(GlobalKey.getWidget(watchedKey));
|
||||||
|
_addListeners();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void didUnmount() {
|
void didUnmount() {
|
||||||
super.didUnmount();
|
super.didUnmount();
|
||||||
_removeListeners();
|
if (watchedKey != null)
|
||||||
|
_removeListeners();
|
||||||
_setWatchedWidget(null);
|
_setWatchedWidget(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,13 +10,49 @@ import 'package:sky/src/widgets/transitions.dart';
|
|||||||
|
|
||||||
typedef Widget RouteBuilder(Navigator navigator, RouteBase route);
|
typedef Widget RouteBuilder(Navigator navigator, RouteBase route);
|
||||||
|
|
||||||
|
typedef void NotificationCallback();
|
||||||
|
|
||||||
abstract class RouteBase {
|
abstract class RouteBase {
|
||||||
Widget build(Navigator navigator, RouteBase route);
|
Widget build(Navigator navigator, RouteBase route);
|
||||||
bool get isOpaque;
|
bool get isOpaque;
|
||||||
void popState([dynamic result]) { assert(result == null); }
|
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 {
|
class Route extends RouteBase {
|
||||||
Route({ this.name, this.builder });
|
Route({ this.name, this.builder });
|
||||||
|
|
||||||
@ -25,7 +61,24 @@ class Route extends RouteBase {
|
|||||||
|
|
||||||
Widget build(Navigator navigator, RouteBase route) => builder(navigator, route);
|
Widget build(Navigator navigator, RouteBase route) => builder(navigator, route);
|
||||||
bool get isOpaque => true;
|
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 {
|
class RouteState extends RouteBase {
|
||||||
@ -44,52 +97,23 @@ class RouteState extends RouteBase {
|
|||||||
callback(this);
|
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 const Duration();
|
||||||
|
}
|
||||||
|
TransitionBase buildTransition({ Key key, Widget child, WatchableAnimationPerformance performance }) {
|
||||||
assert(false);
|
assert(false);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class HistoryEntry {
|
class HistoryEntry {
|
||||||
HistoryEntry({ this.route });
|
HistoryEntry({ this.route });
|
||||||
final RouteBase route;
|
final RouteBase route;
|
||||||
bool fullyOpaque = false;
|
bool fullyOpaque = false;
|
||||||
// TODO(jackson): Keep track of the requested transition
|
// TODO(jackson): Keep track of the requested transition
|
||||||
|
String toString() => "HistoryEntry($route, hashCode=$hashCode)";
|
||||||
}
|
}
|
||||||
|
|
||||||
class NavigationState {
|
class NavigationState {
|
||||||
@ -116,6 +140,7 @@ class NavigationState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void push(RouteBase route) {
|
void push(RouteBase route) {
|
||||||
|
assert(!_debugCurrentlyHaveRoute(route));
|
||||||
HistoryEntry historyEntry = new HistoryEntry(route: route);
|
HistoryEntry historyEntry = new HistoryEntry(route: route);
|
||||||
history.insert(historyIndex + 1, historyEntry);
|
history.insert(historyIndex + 1, historyEntry);
|
||||||
historyIndex++;
|
historyIndex++;
|
||||||
@ -129,6 +154,10 @@ class NavigationState {
|
|||||||
historyIndex--;
|
historyIndex--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _debugCurrentlyHaveRoute(RouteBase route) {
|
||||||
|
return history.any((entry) => entry.route == route);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Navigator extends StatefulComponent {
|
class Navigator extends StatefulComponent {
|
||||||
@ -184,19 +213,25 @@ class Navigator extends StatefulComponent {
|
|||||||
}
|
}
|
||||||
if (child == null)
|
if (child == null)
|
||||||
continue;
|
continue;
|
||||||
TransitionBase transition = historyEntry.route.buildTransition(key: new ObjectKey(historyEntry))
|
WatchableAnimationPerformance performance = historyEntry.route.ensurePerformance(
|
||||||
..child = child
|
direction: (i <= state.historyIndex) ? Direction.forward : Direction.reverse
|
||||||
..direction = (i <= state.historyIndex) ? Direction.forward : Direction.reverse
|
);
|
||||||
..onDismissed = () {
|
historyEntry.route.onDismissed = () {
|
||||||
setState(() {
|
setState(() {
|
||||||
state.history.remove(historyEntry);
|
assert(state.history.contains(historyEntry));
|
||||||
});
|
state.history.remove(historyEntry);
|
||||||
}
|
});
|
||||||
..onCompleted = () {
|
};
|
||||||
setState(() {
|
historyEntry.route.onCompleted = () {
|
||||||
historyEntry.fullyOpaque = historyEntry.route.isOpaque;
|
setState(() {
|
||||||
});
|
historyEntry.fullyOpaque = historyEntry.route.isOpaque;
|
||||||
};
|
});
|
||||||
|
};
|
||||||
|
TransitionBase transition = historyEntry.route.buildTransition(
|
||||||
|
key: new ObjectKey(historyEntry),
|
||||||
|
child: child,
|
||||||
|
performance: performance
|
||||||
|
);
|
||||||
visibleRoutes.add(transition);
|
visibleRoutes.add(transition);
|
||||||
}
|
}
|
||||||
return new Focus(child: new Stack(visibleRoutes));
|
return new Focus(child: new Stack(visibleRoutes));
|
||||||
|
@ -45,10 +45,13 @@ class PopupMenu extends StatefulComponent {
|
|||||||
AnimationPerformance _performance;
|
AnimationPerformance _performance;
|
||||||
|
|
||||||
void initState() {
|
void initState() {
|
||||||
_performance = new AnimationPerformance()
|
_performance = new AnimationPerformance(duration: _kMenuDuration);
|
||||||
..duration = _kMenuDuration;
|
|
||||||
_performance.timing = new AnimationTiming()
|
_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();
|
_updateBoxPainter();
|
||||||
|
|
||||||
if (showing)
|
if (showing)
|
||||||
@ -69,6 +72,7 @@ class PopupMenu extends StatefulComponent {
|
|||||||
|
|
||||||
void _open() {
|
void _open() {
|
||||||
navigator.pushState(this, (_) => _close());
|
navigator.pushState(this, (_) => _close());
|
||||||
|
_performance.play();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _close() {
|
void _close() {
|
||||||
@ -82,7 +86,7 @@ class PopupMenu extends StatefulComponent {
|
|||||||
boxShadow: shadows[level]));
|
boxShadow: shadows[level]));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onDismissed() {
|
void _handleDismissed() {
|
||||||
if (navigator != null &&
|
if (navigator != null &&
|
||||||
navigator.currentRoute is RouteState &&
|
navigator.currentRoute is RouteState &&
|
||||||
(navigator.currentRoute as RouteState).owner == this) // TODO(ianh): remove cast once analyzer is cleverer
|
(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 start = (i + 1) * unit;
|
||||||
double end = (start + 1.5 * unit).clamp(0.0, 1.0);
|
double end = (start + 1.5 * unit).clamp(0.0, 1.0);
|
||||||
children.add(new FadeTransition(
|
children.add(new FadeTransition(
|
||||||
direction: showing ? Direction.forward : Direction.reverse,
|
performance: _performance.view,
|
||||||
performance: _performance,
|
|
||||||
opacity: new AnimatedValue<double>(0.0, end: 1.0, interval: new Interval(start, end)),
|
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 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));
|
final height = new AnimatedValue<double>(0.0, end: 1.0, interval: new Interval(0.0, unit * items.length));
|
||||||
return new FadeTransition(
|
return new FadeTransition(
|
||||||
direction: showing ? Direction.forward : Direction.reverse,
|
performance: _performance.view,
|
||||||
performance: _performance,
|
|
||||||
onDismissed: _onDismissed,
|
|
||||||
opacity: new AnimatedValue<double>(0.0, end: 1.0, interval: new Interval(0.0, 1.0 / 3.0)),
|
opacity: new AnimatedValue<double>(0.0, end: 1.0, interval: new Interval(0.0, 1.0 / 3.0)),
|
||||||
child: new Container(
|
child: new Container(
|
||||||
margin: new EdgeDims.all(_kMenuMargin),
|
margin: new EdgeDims.all(_kMenuMargin),
|
||||||
child: new BuilderTransition(
|
child: new BuilderTransition(
|
||||||
direction: showing ? Direction.forward : Direction.reverse,
|
performance: _performance.view,
|
||||||
performance: _performance,
|
|
||||||
variables: [width, height],
|
variables: [width, height],
|
||||||
builder: () {
|
builder: () {
|
||||||
return new CustomPaint(
|
return new CustomPaint(
|
||||||
|
@ -25,16 +25,21 @@ abstract class ProgressIndicator extends StatefulComponent {
|
|||||||
double value; // Null for non-determinate progress indicator.
|
double value; // Null for non-determinate progress indicator.
|
||||||
double bufferValue; // TODO(hansmuller) implement the support for this.
|
double bufferValue; // TODO(hansmuller) implement the support for this.
|
||||||
|
|
||||||
AnimationPerformance _animation;
|
AnimationPerformance _performance;
|
||||||
double get _animationValue => (_animation.variable as AnimatedValue<double>).value;
|
double get _performanceValue => (_performance.variable as AnimatedValue<double>).value;
|
||||||
Color get _backgroundColor => Theme.of(this).primarySwatch[200];
|
Color get _backgroundColor => Theme.of(this).primarySwatch[200];
|
||||||
Color get _valueColor => Theme.of(this).primaryColor;
|
Color get _valueColor => Theme.of(this).primaryColor;
|
||||||
Object get _customPaintToken => value != null ? value : _animationValue;
|
Object get _customPaintToken => value != null ? value : _performanceValue;
|
||||||
|
|
||||||
void initState() {
|
void initState() {
|
||||||
_animation = new AnimationPerformance()
|
_performance = new AnimationPerformance()
|
||||||
..duration = const Duration(milliseconds: 1500)
|
..duration = const Duration(milliseconds: 1500)
|
||||||
..variable = new AnimatedValue<double>(0.0, end: 1.0, curve: ease);
|
..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) {
|
void syncConstructorArguments(ProgressIndicator source) {
|
||||||
@ -43,8 +48,8 @@ abstract class ProgressIndicator extends StatefulComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _restartAnimation() {
|
void _restartAnimation() {
|
||||||
_animation.progress = 0.0;
|
_performance.progress = 0.0;
|
||||||
_animation.play();
|
_performance.play();
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget build() {
|
Widget build() {
|
||||||
@ -52,10 +57,8 @@ abstract class ProgressIndicator extends StatefulComponent {
|
|||||||
return _buildIndicator();
|
return _buildIndicator();
|
||||||
|
|
||||||
return new BuilderTransition(
|
return new BuilderTransition(
|
||||||
variables: [_animation.variable],
|
variables: [_performance.variable],
|
||||||
direction: Direction.forward,
|
performance: _performance.view,
|
||||||
performance: _animation,
|
|
||||||
onCompleted: _restartAnimation,
|
|
||||||
builder: _buildIndicator
|
builder: _buildIndicator
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -81,7 +84,7 @@ class LinearProgressIndicator extends ProgressIndicator {
|
|||||||
double width = value.clamp(0.0, 1.0) * size.width;
|
double width = value.clamp(0.0, 1.0) * size.width;
|
||||||
canvas.drawRect(Point.origin & new Size(width, size.height), paint);
|
canvas.drawRect(Point.origin & new Size(width, size.height), paint);
|
||||||
} else {
|
} 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 endX = startX + 0.5 * size.width;
|
||||||
double x = startX.clamp(0.0, size.width);
|
double x = startX.clamp(0.0, size.width);
|
||||||
double width = endX.clamp(0.0, size.width) - x;
|
double width = endX.clamp(0.0, size.width) - x;
|
||||||
@ -125,7 +128,7 @@ class CircularProgressIndicator extends ProgressIndicator {
|
|||||||
..arcTo(Point.origin & size, _kStartAngle, angle, false);
|
..arcTo(Point.origin & size, _kStartAngle, angle, false);
|
||||||
canvas.drawPath(path, paint);
|
canvas.drawPath(path, paint);
|
||||||
} else {
|
} else {
|
||||||
double startAngle = _kTwoPI * (1.75 * _animationValue - 0.75);
|
double startAngle = _kTwoPI * (1.75 * _performanceValue - 0.75);
|
||||||
double endAngle = startAngle + _kTwoPI * 0.75;
|
double endAngle = startAngle + _kTwoPI * 0.75;
|
||||||
double arcAngle = startAngle.clamp(0.0, _kTwoPI);
|
double arcAngle = startAngle.clamp(0.0, _kTwoPI);
|
||||||
double arcSweep = endAngle.clamp(0.0, _kTwoPI) - arcAngle;
|
double arcSweep = endAngle.clamp(0.0, _kTwoPI) - arcAngle;
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
import 'package:sky/animation.dart';
|
import 'package:sky/animation.dart';
|
||||||
import 'package:sky/painting.dart';
|
import 'package:sky/painting.dart';
|
||||||
import 'package:sky/material.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/basic.dart';
|
||||||
import 'package:sky/src/widgets/default_text_style.dart';
|
import 'package:sky/src/widgets/default_text_style.dart';
|
||||||
import 'package:sky/src/widgets/framework.dart';
|
import 'package:sky/src/widgets/framework.dart';
|
||||||
@ -17,6 +18,7 @@ import 'package:sky/src/widgets/transitions.dart';
|
|||||||
typedef void SnackBarDismissedCallback();
|
typedef void SnackBarDismissedCallback();
|
||||||
|
|
||||||
const Duration _kSlideInDuration = const Duration(milliseconds: 200);
|
const Duration _kSlideInDuration = const Duration(milliseconds: 200);
|
||||||
|
// TODO(ianh): factor out some of the constants below
|
||||||
|
|
||||||
class SnackBarAction extends Component {
|
class SnackBarAction extends Component {
|
||||||
SnackBarAction({Key key, this.label, this.onPressed }) : super(key: key) {
|
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({
|
SnackBar({
|
||||||
Key key,
|
Key key,
|
||||||
this.anchor,
|
this.transitionKey,
|
||||||
this.content,
|
this.content,
|
||||||
this.actions,
|
this.actions,
|
||||||
this.showing,
|
bool showing,
|
||||||
this.onDismissed
|
this.onDismissed
|
||||||
}) : super(key: key) {
|
}) : super(key: key, direction: showing ? Direction.forward : Direction.reverse, duration: _kSlideInDuration) {
|
||||||
assert(content != null);
|
assert(content != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
Anchor anchor;
|
Key transitionKey;
|
||||||
Widget content;
|
Widget content;
|
||||||
List<SnackBarAction> actions;
|
List<SnackBarAction> actions;
|
||||||
bool showing;
|
|
||||||
SnackBarDismissedCallback onDismissed;
|
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() {
|
Widget build() {
|
||||||
List<Widget> children = [
|
List<Widget> children = [
|
||||||
new Flexible(
|
new Flexible(
|
||||||
@ -71,15 +85,15 @@ class SnackBar extends Component {
|
|||||||
];
|
];
|
||||||
if (actions != null)
|
if (actions != null)
|
||||||
children.addAll(actions);
|
children.addAll(actions);
|
||||||
|
|
||||||
return new SlideTransition(
|
return new SlideTransition(
|
||||||
duration: _kSlideInDuration,
|
key: transitionKey,
|
||||||
direction: showing ? Direction.forward : Direction.reverse,
|
performance: performance.view,
|
||||||
position: new AnimatedValue<Point>(Point.origin,
|
position: new AnimatedValue<Point>(
|
||||||
end: const Point(0.0, -52.0),
|
Point.origin,
|
||||||
curve: easeIn, reverseCurve: easeOut),
|
end: const Point(0.0, -52.0),
|
||||||
onDismissed: onDismissed,
|
curve: easeIn,
|
||||||
anchor: anchor,
|
reverseCurve: easeOut
|
||||||
|
),
|
||||||
child: new Material(
|
child: new Material(
|
||||||
level: 2,
|
level: 2,
|
||||||
color: const Color(0xFF323232),
|
color: const Color(0xFF323232),
|
||||||
|
@ -542,8 +542,7 @@ class TabBar extends Scrollable {
|
|||||||
style: textStyle,
|
style: textStyle,
|
||||||
child: new BuilderTransition(
|
child: new BuilderTransition(
|
||||||
variables: [_indicatorRect],
|
variables: [_indicatorRect],
|
||||||
direction: Direction.forward,
|
performance: _indicatorAnimation.view,
|
||||||
performance: _indicatorAnimation,
|
|
||||||
builder: () {
|
builder: () {
|
||||||
return new TabBarWrapper(
|
return new TabBarWrapper(
|
||||||
children: tabs,
|
children: tabs,
|
||||||
|
@ -6,144 +6,125 @@ import 'package:sky/animation.dart';
|
|||||||
import 'package:sky/src/widgets/animated_component.dart';
|
import 'package:sky/src/widgets/animated_component.dart';
|
||||||
import 'package:sky/src/widgets/basic.dart';
|
import 'package:sky/src/widgets/basic.dart';
|
||||||
import 'package:sky/src/widgets/framework.dart';
|
import 'package:sky/src/widgets/framework.dart';
|
||||||
|
import 'package:sky/src/widgets/global_key_watcher.dart';
|
||||||
import 'package:vector_math/vector_math.dart';
|
import 'package:vector_math/vector_math.dart';
|
||||||
|
|
||||||
export 'package:sky/animation.dart' show Direction;
|
export 'package:sky/animation.dart' show Direction;
|
||||||
|
|
||||||
// A helper class to anchor widgets to one another. Pass an instance of this to
|
class TransitionProxy extends GlobalKeyWatcher {
|
||||||
// a Transition, then use the build() method to create a child with the same
|
|
||||||
// transition applied.
|
|
||||||
class Anchor {
|
|
||||||
Anchor();
|
|
||||||
|
|
||||||
TransitionBase transition;
|
TransitionProxy({
|
||||||
|
|
||||||
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({
|
|
||||||
Key key,
|
Key key,
|
||||||
this.anchoredTo,
|
GlobalKey transitionKey,
|
||||||
this.child
|
this.child
|
||||||
}) : super(key: key);
|
}) : super(key: key, watchedKey: transitionKey);
|
||||||
|
|
||||||
Anchor anchoredTo;
|
|
||||||
Widget child;
|
Widget child;
|
||||||
TransitionBase get transition => anchoredTo.transition;
|
|
||||||
|
|
||||||
void initState() {
|
void syncConstructorArguments(TransitionProxy source) {
|
||||||
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);
|
|
||||||
child = source.child;
|
child = source.child;
|
||||||
super.syncConstructorArguments(source);
|
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() {
|
Widget build() {
|
||||||
if (transition == null)
|
if (transition != null)
|
||||||
return child;
|
return transition.buildWithChild(child);
|
||||||
return transition.buildWithChild(child);
|
return child;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class TransitionBase extends AnimatedComponent {
|
abstract class TransitionBase extends StatefulComponent {
|
||||||
|
|
||||||
TransitionBase({
|
TransitionBase({
|
||||||
Key key,
|
Key key,
|
||||||
this.child,
|
this.performance
|
||||||
this.anchor,
|
}) : super(key: key) {
|
||||||
this.direction,
|
assert(performance != null);
|
||||||
this.duration,
|
|
||||||
this.performance,
|
|
||||||
this.onDismissed,
|
|
||||||
this.onCompleted
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
performance.addStatusListener(_checkStatusChanged);
|
|
||||||
|
|
||||||
watch(performance);
|
|
||||||
_start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WatchableAnimationPerformance performance;
|
||||||
|
|
||||||
void syncConstructorArguments(TransitionBase source) {
|
void syncConstructorArguments(TransitionBase source) {
|
||||||
|
if (performance != source.performance) {
|
||||||
|
if (mounted)
|
||||||
|
performance.removeListener(_performanceChanged);
|
||||||
|
performance = source.performance;
|
||||||
|
if (mounted)
|
||||||
|
performance.addListener(_performanceChanged);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _performanceChanged() {
|
||||||
|
setState(() {
|
||||||
|
// The performance's state is our build state, and it changed already.
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void didMount() {
|
||||||
|
performance.addListener(_performanceChanged);
|
||||||
|
super.didMount();
|
||||||
|
}
|
||||||
|
|
||||||
|
void didUnmount() {
|
||||||
|
performance.removeListener(_performanceChanged);
|
||||||
|
super.didUnmount();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
child = source.child;
|
||||||
onCompleted = source.onCompleted;
|
|
||||||
onDismissed = source.onDismissed;
|
|
||||||
duration = source.duration;
|
|
||||||
if (direction != source.direction) {
|
|
||||||
direction = source.direction;
|
|
||||||
_start();
|
|
||||||
}
|
|
||||||
super.syncConstructorArguments(source);
|
super.syncConstructorArguments(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() {
|
Widget build() {
|
||||||
return buildWithChild(child);
|
return buildWithChild(child);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildWithChild(Widget child);
|
Widget buildWithChild(Widget child);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class SlideTransition extends TransitionBase {
|
class SlideTransition extends TransitionBaseWithChild {
|
||||||
SlideTransition({
|
SlideTransition({
|
||||||
Key key,
|
Key key,
|
||||||
Anchor anchor,
|
|
||||||
this.position,
|
this.position,
|
||||||
Duration duration,
|
WatchableAnimationPerformance performance,
|
||||||
AnimationPerformance performance,
|
|
||||||
Direction direction,
|
|
||||||
Function onDismissed,
|
|
||||||
Function onCompleted,
|
|
||||||
Widget child
|
Widget child
|
||||||
}) : super(key: key,
|
}) : super(key: key,
|
||||||
anchor: anchor,
|
|
||||||
duration: duration,
|
|
||||||
performance: performance,
|
performance: performance,
|
||||||
direction: direction,
|
|
||||||
onDismissed: onDismissed,
|
|
||||||
onCompleted: onCompleted,
|
|
||||||
child: child);
|
child: child);
|
||||||
|
|
||||||
AnimatedValue<Point> position;
|
AnimatedValue<Point> position;
|
||||||
@ -161,24 +142,14 @@ class SlideTransition extends TransitionBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class FadeTransition extends TransitionBase {
|
class FadeTransition extends TransitionBaseWithChild {
|
||||||
FadeTransition({
|
FadeTransition({
|
||||||
Key key,
|
Key key,
|
||||||
Anchor anchor,
|
|
||||||
this.opacity,
|
this.opacity,
|
||||||
Duration duration,
|
WatchableAnimationPerformance performance,
|
||||||
AnimationPerformance performance,
|
|
||||||
Direction direction,
|
|
||||||
Function onDismissed,
|
|
||||||
Function onCompleted,
|
|
||||||
Widget child
|
Widget child
|
||||||
}) : super(key: key,
|
}) : super(key: key,
|
||||||
anchor: anchor,
|
|
||||||
duration: duration,
|
|
||||||
performance: performance,
|
performance: performance,
|
||||||
direction: direction,
|
|
||||||
onDismissed: onDismissed,
|
|
||||||
onCompleted: onCompleted,
|
|
||||||
child: child);
|
child: child);
|
||||||
|
|
||||||
AnimatedValue<double> opacity;
|
AnimatedValue<double> opacity;
|
||||||
@ -194,24 +165,14 @@ class FadeTransition extends TransitionBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ColorTransition extends TransitionBase {
|
class ColorTransition extends TransitionBaseWithChild {
|
||||||
ColorTransition({
|
ColorTransition({
|
||||||
Key key,
|
Key key,
|
||||||
Anchor anchor,
|
|
||||||
this.color,
|
this.color,
|
||||||
Duration duration,
|
WatchableAnimationPerformance performance,
|
||||||
AnimationPerformance performance,
|
|
||||||
Direction direction,
|
|
||||||
Function onDismissed,
|
|
||||||
Function onCompleted,
|
|
||||||
Widget child
|
Widget child
|
||||||
}) : super(key: key,
|
}) : super(key: key,
|
||||||
anchor: anchor,
|
|
||||||
duration: duration,
|
|
||||||
performance: performance,
|
performance: performance,
|
||||||
direction: direction,
|
|
||||||
onDismissed: onDismissed,
|
|
||||||
onCompleted: onCompleted,
|
|
||||||
child: child);
|
child: child);
|
||||||
|
|
||||||
AnimatedColorValue color;
|
AnimatedColorValue color;
|
||||||
@ -230,25 +191,15 @@ class ColorTransition extends TransitionBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SquashTransition extends TransitionBase {
|
class SquashTransition extends TransitionBaseWithChild {
|
||||||
SquashTransition({
|
SquashTransition({
|
||||||
Key key,
|
Key key,
|
||||||
Anchor anchor,
|
|
||||||
this.width,
|
this.width,
|
||||||
this.height,
|
this.height,
|
||||||
Duration duration,
|
WatchableAnimationPerformance performance,
|
||||||
AnimationPerformance performance,
|
|
||||||
Direction direction,
|
|
||||||
Function onDismissed,
|
|
||||||
Function onCompleted,
|
|
||||||
Widget child
|
Widget child
|
||||||
}) : super(key: key,
|
}) : super(key: key,
|
||||||
anchor: anchor,
|
|
||||||
duration: duration,
|
|
||||||
performance: performance,
|
performance: performance,
|
||||||
direction: direction,
|
|
||||||
onDismissed: onDismissed,
|
|
||||||
onCompleted: onCompleted,
|
|
||||||
child: child);
|
child: child);
|
||||||
|
|
||||||
AnimatedValue<double> width;
|
AnimatedValue<double> width;
|
||||||
@ -274,23 +225,11 @@ typedef Widget BuilderFunction();
|
|||||||
class BuilderTransition extends TransitionBase {
|
class BuilderTransition extends TransitionBase {
|
||||||
BuilderTransition({
|
BuilderTransition({
|
||||||
Key key,
|
Key key,
|
||||||
Anchor anchor,
|
|
||||||
this.variables,
|
this.variables,
|
||||||
this.builder,
|
this.builder,
|
||||||
Duration duration,
|
WatchableAnimationPerformance performance
|
||||||
AnimationPerformance performance,
|
|
||||||
Direction direction,
|
|
||||||
Function onDismissed,
|
|
||||||
Function onCompleted,
|
|
||||||
Widget child
|
|
||||||
}) : super(key: key,
|
}) : super(key: key,
|
||||||
anchor: anchor,
|
performance: performance);
|
||||||
duration: duration,
|
|
||||||
performance: performance,
|
|
||||||
direction: direction,
|
|
||||||
onDismissed: onDismissed,
|
|
||||||
onCompleted: onCompleted,
|
|
||||||
child: child);
|
|
||||||
|
|
||||||
List<AnimatedValue> variables;
|
List<AnimatedValue> variables;
|
||||||
BuilderFunction builder;
|
BuilderFunction builder;
|
||||||
@ -301,7 +240,7 @@ class BuilderTransition extends TransitionBase {
|
|||||||
super.syncConstructorArguments(source);
|
super.syncConstructorArguments(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildWithChild(Widget child) {
|
Widget build() {
|
||||||
for (int i = 0; i < variables.length; ++i)
|
for (int i = 0; i < variables.length; ++i)
|
||||||
performance.updateVariable(variables[i]);
|
performance.updateVariable(variables[i]);
|
||||||
return builder();
|
return builder();
|
||||||
|
Loading…
Reference in New Issue
Block a user