Add transformAlignment and clipBehavior to AnimatedContainer (#67046)
* Add transformAlignment and clipBehavior to AnimatedContainer, make Container.transformAlignment an AlignmentGeometry.
* Add clipBehavior test
* Fix comment
* Clarify clipBehavior animation
diff --git a/packages/flutter/lib/src/widgets/container.dart b/packages/flutter/lib/src/widgets/container.dart
index 14ad931..ce9cb02 100644
--- a/packages/flutter/lib/src/widgets/container.dart
+++ b/packages/flutter/lib/src/widgets/container.dart
@@ -352,14 +352,14 @@
/// The transformation matrix to apply before painting the container.
final Matrix4? transform;
- /// The alignment of the origin, relative to the size of the container, if [transform] is specified.
- ///
- /// When [transform] is null, the value of this property is ignored.
- ///
- /// See also:
- ///
- /// * [Transform.alignment], which is set by this property.
- final Alignment? transformAlignment;
+ /// The alignment of the origin, relative to the size of the container, if [transform] is specified.
+ ///
+ /// When [transform] is null, the value of this property is ignored.
+ ///
+ /// See also:
+ ///
+ /// * [Transform.alignment], which is set by this property.
+ final AlignmentGeometry? transformAlignment;
/// The clip behavior when [Container.decoration] is not null.
///
diff --git a/packages/flutter/lib/src/widgets/implicit_animations.dart b/packages/flutter/lib/src/widgets/implicit_animations.dart
index 806d6b1..b5e1042 100644
--- a/packages/flutter/lib/src/widgets/implicit_animations.dart
+++ b/packages/flutter/lib/src/widgets/implicit_animations.dart
@@ -635,7 +635,9 @@
BoxConstraints? constraints,
this.margin,
this.transform,
+ this.transformAlignment,
this.child,
+ this.clipBehavior = Clip.none,
Curve curve = Curves.linear,
required Duration duration,
VoidCallback? onEnd,
@@ -709,6 +711,28 @@
/// The transformation matrix to apply before painting the container.
final Matrix4? transform;
+ /// The alignment of the origin, relative to the size of the container, if [transform] is specified.
+ ///
+ /// When [transform] is null, the value of this property is ignored.
+ ///
+ /// See also:
+ ///
+ /// * [Transform.alignment], which is set by this property.
+ final AlignmentGeometry? transformAlignment;
+
+ /// The clip behavior when [AnimatedContainer.decoration] is not null.
+ ///
+ /// Defaults to [Clip.none]. Must be [Clip.none] if [decoration] is null.
+ ///
+ /// Unlike other properties of [AnimatedContainer], changes to this property
+ /// apply immediately and have no animation.
+ ///
+ /// If a clip is to be applied, the [Decoration.getClipPath] method
+ /// for the provided decoration must return a clip path. (This is not
+ /// supported by all decorations; the default implementation of that
+ /// method throws an [UnsupportedError].)
+ final Clip clipBehavior;
+
@override
_AnimatedContainerState createState() => _AnimatedContainerState();
@@ -722,6 +746,8 @@
properties.add(DiagnosticsProperty<BoxConstraints>('constraints', constraints, defaultValue: null, showName: false));
properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('margin', margin, defaultValue: null));
properties.add(ObjectFlagProperty<Matrix4>.has('transform', transform));
+ properties.add(DiagnosticsProperty<AlignmentGeometry>('transformAlignment', transformAlignment, defaultValue: null));
+ properties.add(DiagnosticsProperty<Clip>('clipBehavior', clipBehavior));
}
}
@@ -733,6 +759,7 @@
BoxConstraintsTween? _constraints;
EdgeInsetsGeometryTween? _margin;
Matrix4Tween? _transform;
+ AlignmentGeometryTween? _transformAlignment;
@override
void forEachTween(TweenVisitor<dynamic> visitor) {
@@ -743,19 +770,23 @@
_constraints = visitor(_constraints, widget.constraints, (dynamic value) => BoxConstraintsTween(begin: value as BoxConstraints)) as BoxConstraintsTween?;
_margin = visitor(_margin, widget.margin, (dynamic value) => EdgeInsetsGeometryTween(begin: value as EdgeInsetsGeometry)) as EdgeInsetsGeometryTween?;
_transform = visitor(_transform, widget.transform, (dynamic value) => Matrix4Tween(begin: value as Matrix4)) as Matrix4Tween?;
+ _transformAlignment = visitor(_transformAlignment, widget.transformAlignment, (dynamic value) => AlignmentGeometryTween(begin: value as AlignmentGeometry)) as AlignmentGeometryTween?;
}
@override
Widget build(BuildContext context) {
+ final Animation<double> animation = this.animation!;
return Container(
child: widget.child,
- alignment: _alignment?.evaluate(animation!),
- padding: _padding?.evaluate(animation!),
- decoration: _decoration?.evaluate(animation!),
- foregroundDecoration: _foregroundDecoration?.evaluate(animation!),
- constraints: _constraints?.evaluate(animation!),
- margin: _margin?.evaluate(animation!),
- transform: _transform?.evaluate(animation!),
+ alignment: _alignment?.evaluate(animation),
+ padding: _padding?.evaluate(animation),
+ decoration: _decoration?.evaluate(animation),
+ foregroundDecoration: _foregroundDecoration?.evaluate(animation),
+ constraints: _constraints?.evaluate(animation),
+ margin: _margin?.evaluate(animation),
+ transform: _transform?.evaluate(animation),
+ transformAlignment: _transformAlignment?.evaluate(animation),
+ clipBehavior: widget.clipBehavior,
);
}
@@ -769,6 +800,7 @@
description.add(DiagnosticsProperty<BoxConstraintsTween>('constraints', _constraints, showName: false, defaultValue: null));
description.add(DiagnosticsProperty<EdgeInsetsGeometryTween>('margin', _margin, defaultValue: null));
description.add(ObjectFlagProperty<Matrix4Tween>.has('transform', _transform));
+ description.add(DiagnosticsProperty<AlignmentGeometryTween>('transformAlignment', _transformAlignment, defaultValue: null));
}
}
diff --git a/packages/flutter/test/widgets/animated_container_test.dart b/packages/flutter/test/widgets/animated_container_test.dart
index d2df68f..4d06ca5 100644
--- a/packages/flutter/test/widgets/animated_container_test.dart
+++ b/packages/flutter/test/widgets/animated_container_test.dart
@@ -286,4 +286,74 @@
expect(text.size.width, equals(200.0));
expect(text.size.height, equals(100.0));
});
+
+ testWidgets('AnimatedContainer sets transformAlignment', (WidgetTester tester) async {
+ final Key target = UniqueKey();
+
+ await tester.pumpWidget(
+ Center(
+ child: Directionality(
+ textDirection: TextDirection.ltr,
+ child: AnimatedContainer(
+ duration: const Duration(milliseconds: 200),
+ child: SizedBox(key: target, width: 100.0, height: 200.0),
+ transform: Matrix4.diagonal3Values(0.5, 0.5, 1),
+ transformAlignment: Alignment.topLeft,
+ ),
+ ),
+ ),
+ );
+
+ expect(tester.getSize(find.byKey(target)), const Size(100.0, 200.0));
+ expect(tester.getTopLeft(find.byKey(target)), const Offset(350.0, 200.0));
+
+ await tester.pumpWidget(
+ Center(
+ child: Directionality(
+ textDirection: TextDirection.ltr,
+ child: AnimatedContainer(
+ duration: const Duration(milliseconds: 200),
+ child: SizedBox(key: target, width: 100.0, height: 200.0),
+ transform: Matrix4.diagonal3Values(0.5, 0.5, 1),
+ transformAlignment: Alignment.bottomRight,
+ ),
+ ),
+ ),
+ );
+
+ expect(tester.getSize(find.byKey(target)), const Size(100.0, 200.0));
+ expect(tester.getTopLeft(find.byKey(target)), const Offset(350.0, 200.0));
+
+ await tester.pump(const Duration(milliseconds: 100));
+
+ expect(tester.getSize(find.byKey(target)), const Size(100.0, 200.0));
+ expect(tester.getTopLeft(find.byKey(target)), const Offset(375.0, 250.0));
+
+ await tester.pump(const Duration(milliseconds: 500));
+
+ expect(tester.getSize(find.byKey(target)), const Size(100.0, 200.0));
+ expect(tester.getTopLeft(find.byKey(target)), const Offset(400.0, 300.0));
+ });
+
+ testWidgets('AnimatedContainer sets clipBehavior', (WidgetTester tester) async {
+ await tester.pumpWidget(
+ AnimatedContainer(
+ decoration: const BoxDecoration(
+ color: Color(0xFFED1D7F),
+ ),
+ duration: const Duration(milliseconds: 200),
+ )
+ );
+ expect(tester.firstWidget<Container>(find.byType(Container)).clipBehavior, Clip.none);
+ await tester.pumpWidget(
+ AnimatedContainer(
+ decoration: const BoxDecoration(
+ color: Color(0xFFED1D7F),
+ ),
+ duration: const Duration(milliseconds: 200),
+ clipBehavior: Clip.antiAlias,
+ )
+ );
+ expect(tester.firstWidget<Container>(find.byType(Container)).clipBehavior, Clip.antiAlias);
+ });
}