提交 cea3418b 编写于 作者: M Matt Perry

Scaffold: animate the FloatingActionButton with the SnackBar.

This introduces the concept of an Anchor, which you can use to link
transitions together. I've used this in the Fitness and Stocks apps to
link the FAB and SnackBar to animate together by sharing the
SlideTransition.

I also fixed the Scaffold hit testing code to apply sub-widget
transforms, so it works with Transformed nodes.
上级 3035e076
......@@ -80,6 +80,7 @@ class FeedFragment extends StatefulComponent {
onItemDeleted = source.onItemDeleted;
}
AnimationStatus _snackBarStatus = AnimationStatus.dismissed;
bool _isShowingSnackBar = false;
EventDisposition _handleFitnessModeChange(FitnessMode value) {
......@@ -168,6 +169,7 @@ class FeedFragment extends StatefulComponent {
setState(() {
_undoItem = item;
_isShowingSnackBar = true;
_snackBarStatus = AnimationStatus.forward;
});
}
......@@ -205,13 +207,16 @@ class FeedFragment extends StatefulComponent {
});
}
Anchor _snackBarAnchor = new Anchor();
Widget buildSnackBar() {
if (!_isShowingSnackBar)
if (_snackBarStatus == AnimationStatus.dismissed)
return null;
return new SnackBar(
showing: _isShowingSnackBar,
anchor: _snackBarAnchor,
content: new Text("Item deleted."),
actions: [new SnackBarAction(label: "UNDO", onPressed: _handleUndo)]
actions: [new SnackBarAction(label: "UNDO", onPressed: _handleUndo)],
onDismissed: () { setState(() { _snackBarStatus = AnimationStatus.dismissed; }); }
);
}
......@@ -225,10 +230,11 @@ class FeedFragment extends StatefulComponent {
Widget buildFloatingActionButton() {
switch (_fitnessMode) {
case FitnessMode.feed:
return new FloatingActionButton(
child: new Icon(type: 'content/add', size: 24),
onPressed: _handleActionButtonPressed
);
return _snackBarAnchor.build(
new FloatingActionButton(
child: new Icon(type: 'content/add', size: 24),
onPressed: _handleActionButtonPressed
));
case FitnessMode.chart:
return null;
}
......
......@@ -247,11 +247,13 @@ class StockHome extends StatefulComponent {
});
}
Anchor _snackBarAnchor = new Anchor();
Widget buildSnackBar() {
if (_snackBarStatus == AnimationStatus.dismissed)
return null;
return new SnackBar(
showing: _isSnackBarShowing,
anchor: _snackBarAnchor,
content: new Text("Stock purchased!"),
actions: [new SnackBarAction(label: "UNDO", onPressed: _handleUndo)],
onDismissed: () { setState(() { _snackBarStatus = AnimationStatus.dismissed; }); }
......@@ -266,11 +268,12 @@ class StockHome extends StatefulComponent {
}
Widget buildFloatingActionButton() {
return new FloatingActionButton(
child: new Icon(type: 'content/add', size: 24),
backgroundColor: colors.RedAccent[200],
onPressed: _handleStockPurchased
);
return _snackBarAnchor.build(
new FloatingActionButton(
child: new Icon(type: 'content/add', size: 24),
backgroundColor: colors.RedAccent[200],
onPressed: _handleStockPurchased
));
}
void addMenuToOverlays(List<Widget> overlays) {
......
......@@ -20,13 +20,22 @@ abstract class AnimatedComponent extends StatefulComponent {
});
}
bool isWatching(performance) => _watchedPerformances.contains(performance);
void watch(AnimationPerformance performance) {
assert(!_watchedPerformances.contains(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);
......
......@@ -85,7 +85,7 @@ class Transition extends TransitionBase {
super.syncFields(source);
}
Widget build() {
Widget buildWithChild(Widget child) {
// TODO(jackson): Hit testing should ignore transform
// TODO(jackson): Block input unless content is interactive
return new SlideTransition(
......
......@@ -9,6 +9,8 @@ import 'package:sky/rendering/object.dart';
import 'package:sky/theme/view_configuration.dart';
import 'package:sky/widgets/framework.dart';
import 'package:vector_math/vector_math.dart';
// Slots are painted in this order and hit tested in reverse of this order
enum ScaffoldSlots {
body,
......@@ -117,19 +119,18 @@ class RenderScaffold extends RenderBox {
assert(body.parentData is BoxParentData);
body.parentData.position = new Point(0.0, bodyPosition);
}
double snackBarHeight = 0.0;
if (_slots[ScaffoldSlots.snackBar] != null) {
RenderBox snackBar = _slots[ScaffoldSlots.snackBar];
// TODO(jackson): On tablet/desktop, minWidth = 288, maxWidth = 568
snackBar.layout(new BoxConstraints(minWidth: size.width, maxWidth: size.width, minHeight: 0.0, maxHeight: size.height),
parentUsesSize: true);
assert(snackBar.parentData is BoxParentData);
snackBar.parentData.position = new Point(0.0, size.height - snackBar.size.height);
snackBarHeight = snackBar.size.height;
// Position it off-screen. SnackBar slides in with an animation.
snackBar.parentData.position = new Point(0.0, size.height);
}
if (_slots[ScaffoldSlots.floatingActionButton] != null) {
RenderBox floatingActionButton = _slots[ScaffoldSlots.floatingActionButton];
Size area = new Size(size.width - kButtonX, size.height - kButtonY - snackBarHeight);
Size area = new Size(size.width - kButtonX, size.height - kButtonY);
floatingActionButton.layout(new BoxConstraints.loose(area), parentUsesSize: true);
assert(floatingActionButton.parentData is BoxParentData);
floatingActionButton.parentData.position = (area - floatingActionButton.size).toPoint();
......@@ -152,12 +153,32 @@ class RenderScaffold extends RenderBox {
}
}
static Point _transformPoint(Matrix4 transform, Point point) {
Vector3 position3 = new Vector3(point.x, point.y, 0.0);
Vector3 transformed3 = transform.transform3(position3);
return new Point(transformed3.x, transformed3.y);
}
Point parentToLocal(RenderBox box, Point point) {
assert(attached);
Matrix4 transform = new Matrix4.identity();
box.applyPaintTransform(transform);
/* double det = */ transform.invert();
// TODO(abarth): Check the determinant for degeneracy.
return _transformPoint(transform, point);
}
void hitTestChildren(HitTestResult result, { Point position }) {
for (ScaffoldSlots slot in ScaffoldSlots.values.reversed) {
RenderBox box = _slots[slot];
if (box != null) {
assert(box.parentData is BoxParentData);
if ((box.parentData.position & box.size).contains(position)) {
// TODO(abarth): Need to solve this problem in general.
// Apply the box's transform to check if it contains position.
// But when we pass the position to box.hitTest, we only want to apply
// the top-level transform (box will apply its own transforms).
Point local = parentToLocal(box, position);
if ((Point.origin & box.size).contains(local)) {
if (box.hitTest(result, position: (position - box.parentData.position).toPoint()))
return;
}
......
......@@ -6,6 +6,7 @@ import 'dart:async';
import 'package:sky/animation/animated_value.dart';
import 'package:sky/animation/animation_performance.dart';
import 'package:sky/animation/curves.dart';
import 'package:sky/painting/text_style.dart';
import 'package:sky/theme/typography.dart' as typography;
import 'package:sky/widgets/basic.dart';
......@@ -43,6 +44,7 @@ class SnackBar extends Component {
SnackBar({
Key key,
this.anchor,
this.content,
this.actions,
this.showing,
......@@ -51,6 +53,7 @@ class SnackBar extends Component {
assert(content != null);
}
Anchor anchor;
Widget content;
List<SnackBarAction> actions;
bool showing;
......@@ -79,9 +82,11 @@ class SnackBar extends Component {
return new SlideTransition(
duration: _kSlideInDuration,
direction: showing ? Direction.forward : Direction.reverse,
position: new AnimatedValue<Point>(const Point(0.0, 50.0),
end: Point.origin),
position: new AnimatedValue<Point>(Point.origin,
end: const Point(0.0, -52.0),
curve: easeIn, reverseCurve: easeOut),
onDismissed: _onDismissed,
anchor: anchor,
child: new Material(
level: 2,
color: const Color(0xFF323232),
......
......@@ -10,9 +10,57 @@ import 'package:vector_math/vector_math.dart';
dynamic _maybe(AnimatedValue x) => x != null ? x.value : null;
// 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();
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({
Key key,
this.anchoredTo,
this.child
}) : super(key: key);
Anchor anchoredTo;
Widget child;
TransitionBase get transition => anchoredTo.transition;
void initState() {
if (transition != null)
watch(transition.performance);
}
void syncFields(_AnchorTransition source) {
if (transition != null && isWatching(transition.performance))
unwatch(transition.performance);
anchoredTo = source.anchoredTo;
if (transition != null)
watch(transition.performance);
child = source.child;
super.syncFields(source);
}
Widget build() {
if (transition == null)
return child;
return transition.buildWithChild(child);
}
}
abstract class TransitionBase extends AnimatedComponent {
TransitionBase({
Key key,
this.anchor,
this.child,
this.direction,
this.duration,
......@@ -22,6 +70,7 @@ abstract class TransitionBase extends AnimatedComponent {
}) : super(key: key);
Widget child;
Anchor anchor;
Direction direction;
Duration duration;
AnimationPerformance performance;
......@@ -29,6 +78,9 @@ abstract class TransitionBase extends AnimatedComponent {
Function onCompleted;
void initState() {
if (anchor != null)
anchor.transition = this;
if (performance == null) {
assert(duration != null);
performance = new AnimationPerformance(duration: duration);
......@@ -67,7 +119,11 @@ abstract class TransitionBase extends AnimatedComponent {
}
}
Widget build();
Widget build() {
return buildWithChild(child);
}
Widget buildWithChild(Widget child);
}
class SlideTransition extends TransitionBase {
......@@ -75,6 +131,7 @@ class SlideTransition extends TransitionBase {
// to super. Is there a simpler way?
SlideTransition({
Key key,
Anchor anchor,
this.position,
Duration duration,
AnimationPerformance performance,
......@@ -83,6 +140,7 @@ class SlideTransition extends TransitionBase {
Function onCompleted,
Widget child
}) : super(key: key,
anchor: anchor,
duration: duration,
performance: performance,
direction: direction,
......@@ -97,7 +155,7 @@ class SlideTransition extends TransitionBase {
super.syncFields(source);
}
Widget build() {
Widget buildWithChild(Widget child) {
performance.updateVariable(position);
Matrix4 transform = new Matrix4.identity()
..translate(position.value.x, position.value.y);
......@@ -108,6 +166,7 @@ class SlideTransition extends TransitionBase {
class FadeTransition extends TransitionBase {
FadeTransition({
Key key,
Anchor anchor,
this.opacity,
Duration duration,
AnimationPerformance performance,
......@@ -116,6 +175,7 @@ class FadeTransition extends TransitionBase {
Function onCompleted,
Widget child
}) : super(key: key,
anchor: anchor,
duration: duration,
performance: performance,
direction: direction,
......@@ -130,7 +190,7 @@ class FadeTransition extends TransitionBase {
super.syncFields(source);
}
Widget build() {
Widget buildWithChild(Widget child) {
performance.updateVariable(opacity);
return new Opacity(opacity: opacity.value, child: child);
}
......@@ -139,6 +199,7 @@ class FadeTransition extends TransitionBase {
class ColorTransition extends TransitionBase {
ColorTransition({
Key key,
Anchor anchor,
this.color,
Duration duration,
AnimationPerformance performance,
......@@ -147,6 +208,7 @@ class ColorTransition extends TransitionBase {
Function onCompleted,
Widget child
}) : super(key: key,
anchor: anchor,
duration: duration,
performance: performance,
direction: direction,
......@@ -161,7 +223,7 @@ class ColorTransition extends TransitionBase {
super.syncFields(source);
}
Widget build() {
Widget buildWithChild(Widget child) {
performance.updateVariable(color);
return new DecoratedBox(
decoration: new BoxDecoration(backgroundColor: color.value),
......@@ -173,6 +235,7 @@ class ColorTransition extends TransitionBase {
class SquashTransition extends TransitionBase {
SquashTransition({
Key key,
Anchor anchor,
this.width,
this.height,
Duration duration,
......@@ -182,6 +245,7 @@ class SquashTransition extends TransitionBase {
Function onCompleted,
Widget child
}) : super(key: key,
anchor: anchor,
duration: duration,
performance: performance,
direction: direction,
......@@ -198,7 +262,7 @@ class SquashTransition extends TransitionBase {
super.syncFields(source);
}
Widget build() {
Widget buildWithChild(Widget child) {
if (width != null)
performance.updateVariable(width);
if (height != null)
......@@ -212,6 +276,7 @@ typedef Widget BuilderFunction();
class BuilderTransition extends TransitionBase {
BuilderTransition({
Key key,
Anchor anchor,
this.variables,
this.builder,
Duration duration,
......@@ -221,6 +286,7 @@ class BuilderTransition extends TransitionBase {
Function onCompleted,
Widget child
}) : super(key: key,
anchor: anchor,
duration: duration,
performance: performance,
direction: direction,
......@@ -237,7 +303,7 @@ class BuilderTransition extends TransitionBase {
super.syncFields(source);
}
Widget build() {
Widget buildWithChild(Widget child) {
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.
先完成此消息的编辑!
想要评论请 注册