Unverified Commit c73b8a7c authored by Greg Spencer's avatar Greg Spencer Committed by GitHub

Fix a bug in the AnimatedChildSwitcher, add builders. (#16250)

This fixes a rendering problem in the AnimatedChildSwitcher where it would add a new "previous" child each time it rebuilt, and if you did it fast enough, all of them would disappear from the page.

It also expands the API for AnimatedChildSwitcher to allow you to specify your own transition and/or layout builder for the transition.

Fixes #16226
parent 7a787419
......@@ -480,6 +480,11 @@ class RenderStack extends RenderBox
assert(_resolvedAlignment != null);
_hasVisualOverflow = false;
bool hasNonPositionedChildren = false;
if (childCount == 0) {
size = constraints.biggest;
assert(size.isFinite);
return;
}
double width = constraints.minWidth;
double height = constraints.minHeight;
......
......@@ -100,6 +100,8 @@ typedef Widget AnimatedCrossFadeBuilder(Widget topChild, Key topChildKey, Widget
///
/// * [AnimatedSize], the lower-level widget which [AnimatedCrossFade] uses to
/// automatically change size.
/// * [AnimatedChildSwitcher], which switches out a child for a new one with a
/// customizable transition.
class AnimatedCrossFade extends StatefulWidget {
/// Creates a cross-fade animation widget.
///
......
......@@ -49,6 +49,17 @@ void main() {
expect(green.size.height, equals(100.0));
});
test('Stack can layout with no children', () {
final RenderBox stack = new RenderStack(
textDirection: TextDirection.ltr,
children: <RenderBox>[],
);
layout(stack, constraints: new BoxConstraints.tight(const Size(100.0, 100.0)));
expect(stack.size.width, equals(100.0));
expect(stack.size.height, equals(100.0));
});
group('RenderIndexedStack', () {
test('visitChildrenForSemantics only visits displayed child', () {
......
......@@ -7,14 +7,63 @@ import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('AnimatedChildSwitcher fades in a new child.', (WidgetTester tester) async {
final UniqueKey containerOne = new UniqueKey();
final UniqueKey containerTwo = new UniqueKey();
final UniqueKey containerThree = new UniqueKey();
await tester.pumpWidget(
new AnimatedChildSwitcher(
duration: const Duration(milliseconds: 100),
child: new Container(key: containerOne, color: const Color(0x00000000)),
switchInCurve: Curves.linear,
switchOutCurve: Curves.linear,
),
);
FadeTransition transition = tester.firstWidget(find.byType(FadeTransition));
expect(transition.opacity.value, equals(1.0));
await tester.pumpWidget(
new AnimatedChildSwitcher(
duration: const Duration(milliseconds: 100),
child: new Container(key: containerTwo, color: const Color(0xff000000)),
switchInCurve: Curves.linear,
switchOutCurve: Curves.linear,
),
);
await tester.pump(const Duration(milliseconds: 50));
transition = tester.firstWidget(find.byType(FadeTransition));
expect(transition.opacity.value, equals(0.5));
await tester.pumpWidget(
new AnimatedChildSwitcher(
duration: const Duration(milliseconds: 100),
child: new Container(key: containerThree, color: const Color(0xffff0000)),
switchInCurve: Curves.linear,
switchOutCurve: Curves.linear,
),
);
await tester.pump(const Duration(milliseconds: 10));
transition = tester.widget(find.byType(FadeTransition).at(0));
expect(transition.opacity.value, closeTo(0.4, 0.01));
transition = tester.widget(find.byType(FadeTransition).at(1));
expect(transition.opacity.value, closeTo(0.4, 0.01));
transition = tester.widget(find.byType(FadeTransition).at(2));
expect(transition.opacity.value, closeTo(0.1, 0.01));
await tester.pumpAndSettle();
});
testWidgets("AnimatedChildSwitcher doesn't transition in a new child of the same type.", (WidgetTester tester) async {
await tester.pumpWidget(
new AnimatedChildSwitcher(
duration: const Duration(milliseconds: 100),
child: new Container(color: const Color(0x00000000)),
switchInCurve: Curves.linear,
switchOutCurve: Curves.linear,
),
);
// First one just appears.
FadeTransition transition = tester.firstWidget(find.byType(FadeTransition));
expect(transition.opacity.value, equals(1.0));
......@@ -23,12 +72,80 @@ void main() {
duration: const Duration(milliseconds: 100),
child: new Container(color: const Color(0xff000000)),
switchInCurve: Curves.linear,
switchOutCurve: Curves.linear,
),
);
await tester.pump(const Duration(milliseconds: 50));
transition = tester.widget(find.byType(FadeTransition));
expect(transition.opacity.value, equals(1.0));
await tester.pumpAndSettle();
});
testWidgets('AnimatedChildSwitcher handles null children.', (WidgetTester tester) async {
await tester.pumpWidget(
const AnimatedChildSwitcher(
duration: const Duration(milliseconds: 100),
child: null,
switchInCurve: Curves.linear,
switchOutCurve: Curves.linear,
),
);
expect(find.byType(FadeTransition), findsNothing);
await tester.pumpWidget(
new AnimatedChildSwitcher(
duration: const Duration(milliseconds: 100),
child: new Container(color: const Color(0xff000000)),
switchInCurve: Curves.linear,
switchOutCurve: Curves.linear,
),
);
// Second one cross-fades with the first.
await tester.pump(const Duration(milliseconds: 50));
FadeTransition transition = tester.firstWidget(find.byType(FadeTransition));
expect(transition.opacity.value, equals(0.5));
await tester.pumpAndSettle();
await tester.pumpWidget(
new AnimatedChildSwitcher(
duration: const Duration(milliseconds: 100),
child: new Container(color: const Color(0x00000000)),
switchInCurve: Curves.linear,
switchOutCurve: Curves.linear,
),
);
transition = tester.firstWidget(find.byType(FadeTransition));
expect(transition.opacity.value, equals(1.0));
await tester.pumpWidget(
const AnimatedChildSwitcher(
duration: const Duration(milliseconds: 100),
child: null,
switchInCurve: Curves.linear,
switchOutCurve: Curves.linear,
),
);
await tester.pump(const Duration(milliseconds: 50));
transition = tester.firstWidget(find.byType(FadeTransition));
expect(transition.opacity.value, equals(0.5));
await tester.pumpWidget(
const AnimatedChildSwitcher(
duration: const Duration(milliseconds: 100),
child: null,
switchInCurve: Curves.linear,
switchOutCurve: Curves.linear,
),
);
await tester.pump(const Duration(milliseconds: 50));
transition = tester.firstWidget(find.byType(FadeTransition));
expect(transition.opacity.value, equals(0.0));
await tester.pumpAndSettle();
});
......@@ -44,4 +161,63 @@ void main() {
await tester.pumpWidget(new Container(color: const Color(0xffff0000)));
expect(await tester.pumpAndSettle(const Duration(milliseconds: 100)), equals(1));
});
testWidgets('AnimatedChildSwitcher uses custom layout.', (WidgetTester tester) async {
Widget newLayoutBuilder(List<Widget> children) {
return new Column(
children: children,
);
}
await tester.pumpWidget(
new AnimatedChildSwitcher(
duration: const Duration(milliseconds: 100),
child: new Container(color: const Color(0x00000000)),
switchInCurve: Curves.linear,
layoutBuilder: newLayoutBuilder,
),
);
expect(find.byType(Column), findsOneWidget);
});
testWidgets('AnimatedChildSwitcher uses custom transitions.', (WidgetTester tester) async {
final List<Widget> transitions = <Widget>[];
Widget newLayoutBuilder(List<Widget> children) {
transitions.clear();
transitions.addAll(children);
return new Column(
children: children,
);
}
Widget newTransitionBuilder(Widget child, Animation<double> animation) {
return new SizeTransition(
sizeFactor: animation,
child: child,
);
}
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.rtl,
child: new AnimatedChildSwitcher(
duration: const Duration(milliseconds: 100),
child: new Container(color: const Color(0x00000000)),
switchInCurve: Curves.linear,
layoutBuilder: newLayoutBuilder,
transitionBuilder: newTransitionBuilder,
),
),
);
expect(find.byType(Column), findsOneWidget);
for (Widget transition in transitions) {
expect(transition, const isInstanceOf<KeyedSubtree>());
expect(
find.descendant(of: find.byWidget(transition), matching: find.byType(SizeTransition)),
findsOneWidget,
);
}
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment