mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Merge pull request #564 from mpcomplete/anchor
Scaffold: animate the FloatingActionButton with the SnackBar.
This commit is contained in:
commit
dee68441e9
@ -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();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user