[flutter_adaptive_scaffold] Allows for the animation duration to be adjusted using SlotLayout.from() (#6510)

Added `duration` to the `SlotLayoutConfig` constructor and `from()` method. This allows a user to specify the duration of the in/out animations which they can already supply.

The default animation of 1 second is nice for demos where a slowed down animation helps to show the behavior, but it is (arguably?) way too slow for an application where one wants a snappy animation effect.

*List which issues are fixed by this PR. You must list at least one issue.*

Fixes https://github.com/flutter/flutter/issues/112938

*If you had to change anything in the [flutter/tests] repo, include a link to the migration guide as per the [breaking change policy].*

No change to tests

- [test-exempt] I added new tests to check the change I am making, or this PR is [test-exempt].
This commit is contained in:
Jim Cook
2024-06-24 14:14:52 -04:00
committed by GitHub
parent 02e71b087e
commit fdb78fcbd5
4 changed files with 53 additions and 16 deletions

View File

@ -1,3 +1,8 @@
## 0.1.11+1
* Allows custom animation duration for the NavigationRail and
BottomNavigationBar transitions. [flutter/flutter#112938](https://github.com/flutter/flutter/issues/112938)
## 0.1.11
* Updates minimum supported SDK version to Flutter 3.19/Dart 3.3.

View File

@ -74,12 +74,14 @@ class SlotLayout extends StatefulWidget {
WidgetBuilder? builder,
Widget Function(Widget, Animation<double>)? inAnimation,
Widget Function(Widget, Animation<double>)? outAnimation,
Duration? duration,
required Key key,
}) =>
SlotLayoutConfig._(
builder: builder,
inAnimation: inAnimation,
outAnimation: outAnimation,
duration: duration,
key: key,
);
@ -96,7 +98,7 @@ class _SlotLayoutState extends State<SlotLayout>
chosenWidget = SlotLayout.pickWidget(context, widget.config);
bool hasAnimation = false;
return AnimatedSwitcher(
duration: const Duration(milliseconds: 1000),
duration: chosenWidget?.duration ?? const Duration(milliseconds: 1000),
layoutBuilder: (Widget? currentChild, List<Widget> previousChildren) {
final Stack elements = Stack(
children: <Widget>[
@ -137,6 +139,7 @@ class SlotLayoutConfig extends StatelessWidget {
required this.builder,
this.inAnimation,
this.outAnimation,
this.duration,
});
/// The child Widget that [SlotLayout] eventually returns with an animation.
@ -160,6 +163,9 @@ class SlotLayoutConfig extends StatelessWidget {
/// as the returned widget.
final Widget Function(Widget, Animation<double>)? outAnimation;
/// The amount of time taken by the execution of the in and out animations.
final Duration? duration;
/// An empty [SlotLayoutConfig] to be placed in a slot to indicate that the slot
/// should show nothing.
static SlotLayoutConfig empty() {

View File

@ -1,6 +1,6 @@
name: flutter_adaptive_scaffold
description: Widgets to easily build adaptive layouts, including navigation elements.
version: 0.1.11
version: 0.1.11+1
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+flutter_adaptive_scaffold%22
repository: https://github.com/flutter/packages/tree/main/packages/flutter_adaptive_scaffold

View File

@ -120,11 +120,13 @@ void main() {
testWidgets(
'slot layout properly switches between items with the appropriate animation',
(WidgetTester tester) async {
await tester.pumpWidget(slot(300, tester));
await tester
.pumpWidget(slot(300, const Duration(milliseconds: 1000), tester));
expect(begin, findsOneWidget);
expect(end, findsNothing);
await tester.pumpWidget(slot(500, tester));
await tester
.pumpWidget(slot(500, const Duration(milliseconds: 1000), tester));
await tester.pump();
await tester.pump(const Duration(milliseconds: 500));
expect(tester.widget<SlideTransition>(slideOut('0')).position.value,
@ -146,7 +148,7 @@ void main() {
testWidgets('AnimatedSwitcher does not spawn duplicate keys on rapid resize',
(WidgetTester tester) async {
// Populate the smaller slot layout and let the animation settle.
await tester.pumpWidget(slot(300, tester));
await tester.pumpWidget(slot(300, const Duration(seconds: 1), tester));
await tester.pumpAndSettle();
expect(begin, findsOneWidget);
expect(end, findsNothing);
@ -157,12 +159,12 @@ void main() {
for (int i = 0; i < 2; i++) {
// Resize between the two slot layouts, but do not pump the animation
// until completion.
await tester.pumpWidget(slot(500, tester));
await tester.pumpWidget(slot(500, const Duration(seconds: 1), tester));
await tester.pump(const Duration(milliseconds: 100));
expect(begin, findsOneWidget);
expect(end, findsOneWidget);
await tester.pumpWidget(slot(300, tester));
await tester.pumpWidget(slot(300, const Duration(seconds: 1), tester));
await tester.pump(const Duration(milliseconds: 100));
expect(begin, findsOneWidget);
expect(end, findsOneWidget);
@ -171,18 +173,18 @@ void main() {
testWidgets('slot layout can tolerate rapid changes in breakpoints',
(WidgetTester tester) async {
await tester.pumpWidget(slot(300, tester));
await tester.pumpWidget(slot(300, const Duration(seconds: 1), tester));
expect(begin, findsOneWidget);
expect(end, findsNothing);
await tester.pumpWidget(slot(500, tester));
await tester.pumpWidget(slot(500, const Duration(seconds: 1), tester));
await tester.pump();
await tester.pump(const Duration(milliseconds: 100));
expect(tester.widget<SlideTransition>(slideOut('0')).position.value,
offsetMoreOrLessEquals(const Offset(-0.1, 0), epsilon: 0.05));
expect(tester.widget<SlideTransition>(slideIn('400')).position.value,
offsetMoreOrLessEquals(const Offset(-0.9, 0), epsilon: 0.05));
await tester.pumpWidget(slot(300, tester));
await tester.pumpWidget(slot(300, const Duration(seconds: 1), tester));
await tester.pumpAndSettle();
expect(begin, findsOneWidget);
expect(end, findsNothing);
@ -243,11 +245,35 @@ void main() {
tester.getBottomRight(secondaryTestBreakpoint), const Offset(390, 790));
});
testWidgets('adaptive layout can adjust animation duration',
(WidgetTester tester) async {
// Populate the smaller slot layout and let the animation settle.
await tester
.pumpWidget(slot(300, const Duration(milliseconds: 100), tester));
await tester.pumpAndSettle();
expect(begin, findsOneWidget);
expect(end, findsNothing);
// expand in 1/5 second.
await tester
.pumpWidget(slot(500, const Duration(milliseconds: 200), tester));
// after 100ms, we expect both widgets to be present.
await tester.pump(const Duration(milliseconds: 50));
expect(begin, findsOneWidget);
expect(end, findsOneWidget);
// After 1/5 second, all animations should be done.
await tester.pump(const Duration(milliseconds: 200));
expect(begin, findsNothing);
expect(end, findsOneWidget);
await tester.pumpAndSettle();
});
testWidgets('adaptive layout does not animate when animations off',
(WidgetTester tester) async {
final Finder testBreakpoint = find.byKey(const Key('Test Breakpoint'));
final Finder secondaryTestBreakpoint =
find.byKey(const Key('Secondary Test Breakpoint'));
await tester.pumpWidget(
await layout(width: 400, tester: tester, animations: false));
@ -257,9 +283,6 @@ void main() {
expect(tester.getTopLeft(testBreakpoint), const Offset(10, 10));
expect(tester.getBottomRight(testBreakpoint), const Offset(200, 790));
expect(tester.getTopLeft(secondaryTestBreakpoint), const Offset(200, 10));
expect(
tester.getBottomRight(secondaryTestBreakpoint), const Offset(390, 790));
});
}
@ -306,6 +329,7 @@ Future<MediaQuery> layout({
TextDirection directionality = TextDirection.ltr,
double? bodyRatio,
bool animations = true,
int durationMs = 1000,
}) async {
await tester.binding.setSurfaceSize(Size(width, 800));
return MediaQuery(
@ -415,7 +439,7 @@ AnimatedWidget leftInOut(Widget child, Animation<double> animation) {
);
}
MediaQuery slot(double width, WidgetTester tester) {
MediaQuery slot(double width, Duration duration, WidgetTester tester) {
return MediaQuery(
data: MediaQueryData.fromView(tester.view).copyWith(size: Size(width, 800)),
child: Directionality(
@ -425,12 +449,14 @@ MediaQuery slot(double width, WidgetTester tester) {
TestBreakpoint0(): SlotLayout.from(
inAnimation: leftOutIn,
outAnimation: leftInOut,
duration: duration,
key: const Key('0'),
builder: (_) => const SizedBox(width: 10, height: 10),
),
TestBreakpoint400(): SlotLayout.from(
inAnimation: leftOutIn,
outAnimation: leftInOut,
duration: duration,
key: const Key('400'),
builder: (_) => const SizedBox(width: 10, height: 10),
),