From fb8fe97a9b15c8418b88134fa75c3a363de2f9ad Mon Sep 17 00:00:00 2001 From: Hixie Date: Sat, 17 Oct 2015 18:28:33 -0700 Subject: [PATCH] Enable Positioned to be animated. Add a AnimatedRelativeRectValue class for animating RelativeRects. Add a PositionedTransition class for animating Positioned using AnimatedRelativeRectValues. Add a test for PositionedTransition. Fix a math bug a RelativeRect found by the test. Fix a logic bug in the two ParentDataWidget classes found by the test. Specifically, they were marking the child dirty, rather than the parent. The parentData is for the parent's layout, not the child's, so they have to mark the parent dirty. (I didn't hoist this up to the superclass because ParentData could be used for painting, hit testing, accessibility, or any number of other things, and I didn't want to bake in the assumption that it needed markNeedsLayout.) --- packages/flutter/lib/src/rendering/stack.dart | 4 +- packages/flutter/lib/src/widgets/basic.dart | 11 ++- .../flutter/lib/src/widgets/transitions.dart | 43 +++++++++++ .../unit/test/widget/positioned_test.dart | 74 +++++++++++++++++++ 4 files changed, 127 insertions(+), 5 deletions(-) create mode 100644 packages/unit/test/widget/positioned_test.dart 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)])); + + }); + }); + +}