diff --git a/packages/flutter/lib/src/rendering/stack.dart b/packages/flutter/lib/src/rendering/stack.dart index 85af118fe61..54b329b41bf 100644 --- a/packages/flutter/lib/src/rendering/stack.dart +++ b/packages/flutter/lib/src/rendering/stack.dart @@ -42,8 +42,8 @@ class RelativeRect { return new RelativeRect.fromLTRB( rect.left - container.left, rect.top - container.top, - container.right - rect.left + rect.width, - container.bottom - rect.top + rect.height + container.right - rect.right, + container.bottom - rect.bottom ); } diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart index 248f343a70d..ac64a03bb9b 100644 --- a/packages/flutter/lib/src/widgets/basic.dart +++ b/packages/flutter/lib/src/widgets/basic.dart @@ -692,8 +692,11 @@ class Positioned extends ParentDataWidget { needsLayout = true; } - if (needsLayout) - renderObject.markNeedsLayout(); + if (needsLayout) { + AbstractNode targetParent = renderObject.parent; + if (targetParent is RenderObject) + targetParent.markNeedsLayout(); + } } void debugFillDescription(List description) { @@ -788,7 +791,9 @@ class Flexible extends ParentDataWidget { final FlexParentData parentData = renderObject.parentData; if (parentData.flex != flex) { parentData.flex = flex; - renderObject.markNeedsLayout(); + AbstractNode targetParent = renderObject.parent; + if (targetParent is RenderObject) + targetParent.markNeedsLayout(); } } diff --git a/packages/flutter/lib/src/widgets/transitions.dart b/packages/flutter/lib/src/widgets/transitions.dart index bd2f7dab4a5..c696131a408 100644 --- a/packages/flutter/lib/src/widgets/transitions.dart +++ b/packages/flutter/lib/src/widgets/transitions.dart @@ -4,11 +4,13 @@ import 'package:flutter/animation.dart'; import 'package:vector_math/vector_math_64.dart' show Matrix4; +import 'package:flutter/rendering.dart'; import 'basic.dart'; import 'framework.dart'; export 'package:flutter/animation.dart' show AnimationDirection; +export 'package:flutter/rendering.dart' show RelativeRect; abstract class TransitionComponent extends StatefulComponent { TransitionComponent({ @@ -150,6 +152,47 @@ class SquashTransition extends TransitionWithChild { } } +/// An animated variable containing a RelativeRectangle +/// +/// This class specializes the interpolation of AnimatedValue to +/// be appropriate for rectangles that are described in terms of offsets from +/// other rectangles. +class AnimatedRelativeRectValue extends AnimatedValue { + AnimatedRelativeRectValue(RelativeRect begin, { RelativeRect end, Curve curve, Curve reverseCurve }) + : super(begin, end: end, curve: curve, reverseCurve: reverseCurve); + + RelativeRect lerp(double t) => RelativeRect.lerp(begin, end, t); +} + +/// Animated version of [Positioned]. +/// Only works if it's the child of a [Stack]. +class PositionedTransition extends TransitionWithChild { + PositionedTransition({ + Key key, + this.rect, + PerformanceView performance, + Widget child + }) : super(key: key, + performance: performance, + child: child) { + assert(rect != null); + } + + final AnimatedRelativeRectValue rect; + + Widget buildWithChild(BuildContext context, Widget child) { + performance.updateVariable(rect); + return new Positioned( + top: rect.value.top, + right: rect.value.right, + bottom: rect.value.bottom, + left: rect.value.left, + child: child + ); + } +} + + typedef Widget BuilderFunction(BuildContext context); class BuilderTransition extends TransitionComponent { diff --git a/packages/unit/test/widget/positioned_test.dart b/packages/unit/test/widget/positioned_test.dart new file mode 100644 index 00000000000..7adecc8e58f --- /dev/null +++ b/packages/unit/test/widget/positioned_test.dart @@ -0,0 +1,74 @@ +import 'package:flutter/animation.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/widgets.dart'; +import 'package:test/test.dart'; + +import 'widget_tester.dart'; + +void main() { + + test('Can animate position data', () { + testWidgets((WidgetTester tester) { + + final AnimatedRelativeRectValue rect = new AnimatedRelativeRectValue( + new RelativeRect.fromRect( + new Rect.fromLTRB(10.0, 20.0, 20.0, 30.0), + new Rect.fromLTRB(0.0, 10.0, 100.0, 110.0) + ), + end: new RelativeRect.fromRect( + new Rect.fromLTRB(80.0, 90.0, 90.0, 100.0), + new Rect.fromLTRB(0.0, 10.0, 100.0, 110.0) + ), + curve: linear + ); + final Performance performance = new Performance( + duration: const Duration(seconds: 10) + ); + final List sizes = []; + final List positions = []; + final GlobalKey key = new GlobalKey(); + + void recordMetrics() { + RenderBox box = key.currentContext.findRenderObject(); + BoxParentData boxParentData = box.parentData; + sizes.add(box.size); + positions.add(boxParentData.position); + } + + tester.pumpWidget( + new Center( + child: new Container( + height: 100.0, + width: 100.0, + child: new Stack([ + new PositionedTransition( + rect: rect, + performance: performance, + child: new Container( + key: key + ) + ) + ]) + ) + ) + ); // t=0 + recordMetrics(); + performance.play(); + tester.pump(); // t=0 again + recordMetrics(); + tester.pump(const Duration(seconds: 1)); // t=1 + recordMetrics(); + tester.pump(const Duration(seconds: 1)); // t=2 + recordMetrics(); + tester.pump(const Duration(seconds: 3)); // t=5 + recordMetrics(); + tester.pump(const Duration(seconds: 5)); // t=10 + recordMetrics(); + + expect(sizes, equals([const Size(10.0, 10.0), const Size(10.0, 10.0), const Size(10.0, 10.0), const Size(10.0, 10.0), const Size(10.0, 10.0), const Size(10.0, 10.0)])); + expect(positions, equals([const Point(10.0, 10.0), const Point(10.0, 10.0), const Point(17.0, 17.0), const Point(24.0, 24.0), const Point(45.0, 45.0), const Point(80.0, 80.0)])); + + }); + }); + +}