Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 20 additions & 33 deletions packages/animations/lib/src/open_container.dart
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -412,12 +412,9 @@ class _OpenContainerRoute extends ModalRoute<void> {
// work. // work.
final GlobalKey _openBuilderKey = GlobalKey(); final GlobalKey _openBuilderKey = GlobalKey();


// Defines the position of the (opening) [OpenContainer] within the bounds of // Defines the position and the size of the (opening) [OpenContainer] within
// the enclosing [Navigator]. // the bounds of the enclosing [Navigator].
final RectTween _insetsTween = RectTween(end: Rect.zero); final RectTween _rectTween = RectTween();

// Defines the size of the [OpenContainer].
final SizeTween _sizeTween = SizeTween();


AnimationStatus _lastAnimationStatus; AnimationStatus _lastAnimationStatus;
AnimationStatus _currentAnimationStatus; AnimationStatus _currentAnimationStatus;
Expand Down Expand Up @@ -465,21 +462,14 @@ class _OpenContainerRoute extends ModalRoute<void> {
final RenderBox navigator = final RenderBox navigator =
Navigator.of(navigatorContext).context.findRenderObject(); Navigator.of(navigatorContext).context.findRenderObject();
final Size navSize = _getSize(navigator); final Size navSize = _getSize(navigator);
_sizeTween.end = navSize; _rectTween.end = Offset.zero & navSize;


void takeMeasurementsInSourceRoute([Duration _]) { void takeMeasurementsInSourceRoute([Duration _]) {
if (!navigator.attached || hideableKey.currentContext == null) { if (!navigator.attached || hideableKey.currentContext == null) {
return; return;
} }
final Rect srcRect = _getRect(hideableKey, navigator); _rectTween.begin = _getRect(hideableKey, navigator);
_sizeTween.begin = srcRect.size; hideableKey.currentState.placeholderSize = _rectTween.begin.size;
_insetsTween.begin = Rect.fromLTRB(
srcRect.left,
srcRect.top,
navSize.width - srcRect.right,
navSize.height - srcRect.bottom,
);
hideableKey.currentState.placeholderSize = _sizeTween.begin;
} }


if (delayForSourceRoute) { if (delayForSourceRoute) {
Expand Down Expand Up @@ -600,21 +590,17 @@ class _OpenContainerRoute extends ModalRoute<void> {
assert(false); // Unreachable. assert(false); // Unreachable.
break; break;
} }
final Rect rect = _insetsTween.evaluate(curvedAnimation); final Rect rect = _rectTween.evaluate(curvedAnimation);
final Size size = _sizeTween.evaluate(curvedAnimation);
return SizedBox.expand( return SizedBox.expand(
child: Container( child: Container(
color: scrimTween.evaluate(curvedAnimation), color: scrimTween.evaluate(curvedAnimation),
child: Padding( child: Align(
padding: EdgeInsets.fromLTRB( alignment: Alignment.topLeft,
rect.left, child: Transform.translate(
rect.top, offset: Offset(rect.left, rect.top),
rect.right,
rect.bottom,
),
child: SizedBox( child: SizedBox(
width: size.width, width: rect.width,
height: size.height, height: rect.height,
child: Material( child: Material(
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
animationDuration: Duration.zero, animationDuration: Duration.zero,
Expand All @@ -629,13 +615,13 @@ class _OpenContainerRoute extends ModalRoute<void> {
fit: BoxFit.fitWidth, fit: BoxFit.fitWidth,
alignment: Alignment.topLeft, alignment: Alignment.topLeft,
child: SizedBox( child: SizedBox(
width: _sizeTween.begin.width, width: _rectTween.begin.width,
height: _sizeTween.begin.height, height: _rectTween.begin.height,
child: hideableKey.currentState.isInTree child: hideableKey.currentState.isInTree
? null ? null
: Opacity( : Opacity(
opacity: opacity: closedOpacityTween
closedOpacityTween.evaluate(animation), .evaluate(animation),
child: Builder( child: Builder(
key: closedBuilderKey, key: closedBuilderKey,
builder: (BuildContext context) { builder: (BuildContext context) {
Expand All @@ -653,8 +639,8 @@ class _OpenContainerRoute extends ModalRoute<void> {
fit: BoxFit.fitWidth, fit: BoxFit.fitWidth,
alignment: Alignment.topLeft, alignment: Alignment.topLeft,
child: SizedBox( child: SizedBox(
width: _sizeTween.end.width, width: _rectTween.end.width,
height: _sizeTween.end.height, height: _rectTween.end.height,
child: Opacity( child: Opacity(
opacity: openOpacityTween.evaluate(animation), opacity: openOpacityTween.evaluate(animation),
child: Builder( child: Builder(
Expand All @@ -672,6 +658,7 @@ class _OpenContainerRoute extends ModalRoute<void> {
), ),
), ),
), ),
),
); );
}, },
), ),
Expand Down
4 changes: 4 additions & 0 deletions packages/animations/lib/src/utils/curves.dart
Original file line number Original file line Diff line number Diff line change
@@ -1,3 +1,7 @@
// Copyright 2019 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/animation.dart'; import 'package:flutter/animation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
Expand Down
145 changes: 145 additions & 0 deletions packages/animations/test/open_container_test.dart
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -940,6 +940,151 @@ void main() {
await tester.pump(const Duration(milliseconds: 100)); await tester.pump(const Duration(milliseconds: 100));
expect(_getScrimColor(tester), Colors.transparent); expect(_getScrimColor(tester), Colors.transparent);
}); });

testWidgets(
'Container partly offscreen can be opened without crash - vertical',
(WidgetTester tester) async {
final ScrollController controller =
ScrollController(initialScrollOffset: 50);
await tester.pumpWidget(Center(
child: SizedBox(
height: 200,
width: 200,
child: _boilerplate(
child: ListView.builder(
cacheExtent: 0,
controller: controller,
itemBuilder: (BuildContext context, int index) {
return OpenContainer(
closedBuilder: (BuildContext context, VoidCallback _) {
return SizedBox(
height: 100,
width: 100,
child: Text('Closed $index'),
);
},
openBuilder: (BuildContext context, VoidCallback _) {
return Text('Open $index');
},
);
},
),
),
),
));

void expectClosedState() {
expect(find.text('Closed 0'), findsOneWidget);
expect(find.text('Closed 1'), findsOneWidget);
expect(find.text('Closed 2'), findsOneWidget);
expect(find.text('Closed 3'), findsNothing);

expect(find.text('Open 0'), findsNothing);
expect(find.text('Open 1'), findsNothing);
expect(find.text('Open 2'), findsNothing);
expect(find.text('Open 3'), findsNothing);
}

expectClosedState();

// Open container that's partly visible at top.
await tester.tapAt(
tester.getBottomRight(find.text('Closed 0')) - const Offset(20, 20),
);
await tester.pump();
await tester.pumpAndSettle();
expect(find.text('Closed 0'), findsNothing);
expect(find.text('Open 0'), findsOneWidget);

final NavigatorState navigator = tester.state(find.byType(Navigator));
navigator.pop();
await tester.pump();
await tester.pumpAndSettle();
expectClosedState();

// Open container that's partly visible at bottom.
await tester.tapAt(
tester.getTopLeft(find.text('Closed 2')) + const Offset(20, 20),
);
await tester.pump();
await tester.pumpAndSettle();

expect(find.text('Closed 2'), findsNothing);
expect(find.text('Open 2'), findsOneWidget);
});

testWidgets(
'Container partly offscreen can be opened without crash - horizontal',
(WidgetTester tester) async {
final ScrollController controller =
ScrollController(initialScrollOffset: 50);
await tester.pumpWidget(Center(
child: SizedBox(
height: 200,
width: 200,
child: _boilerplate(
child: ListView.builder(
scrollDirection: Axis.horizontal,
cacheExtent: 0,
controller: controller,
itemBuilder: (BuildContext context, int index) {
return OpenContainer(
closedBuilder: (BuildContext context, VoidCallback _) {
return SizedBox(
height: 100,
width: 100,
child: Text('Closed $index'),
);
},
openBuilder: (BuildContext context, VoidCallback _) {
return Text('Open $index');
},
);
},
),
),
),
));

void expectClosedState() {
expect(find.text('Closed 0'), findsOneWidget);
expect(find.text('Closed 1'), findsOneWidget);
expect(find.text('Closed 2'), findsOneWidget);
expect(find.text('Closed 3'), findsNothing);

expect(find.text('Open 0'), findsNothing);
expect(find.text('Open 1'), findsNothing);
expect(find.text('Open 2'), findsNothing);
expect(find.text('Open 3'), findsNothing);
}

expectClosedState();

// Open container that's partly visible at left edge.
await tester.tapAt(
tester.getBottomRight(find.text('Closed 0')) - const Offset(20, 20),
);
await tester.pump();
await tester.pumpAndSettle();
expect(find.text('Closed 0'), findsNothing);
expect(find.text('Open 0'), findsOneWidget);

final NavigatorState navigator = tester.state(find.byType(Navigator));
navigator.pop();
await tester.pump();
await tester.pumpAndSettle();
expectClosedState();

// Open container that's partly visible at right edge.
await tester.tapAt(
tester.getTopLeft(find.text('Closed 2')) + const Offset(20, 20),
);
await tester.pump();
await tester.pumpAndSettle();

expect(find.text('Closed 2'), findsNothing);
expect(find.text('Open 2'), findsOneWidget);
});
} }


Color _getScrimColor(WidgetTester tester) { Color _getScrimColor(WidgetTester tester) {
Expand Down