Skip to content

Commit 8f47459

Browse files
authored
[flutter_adaptive_scaffold] Adds additional slot animation parameters (#7411)
Slots can currently be animated in and out of view, but there is not an ability to change the duration or curves of these animations. This PR adds additional parameters that are passed to the underlying AnimatedSwitcher when switching slots in and out of view. *List which issues are fixed by this PR. You must list at least one issue.* #6957
1 parent 6936868 commit 8f47459

File tree

5 files changed

+230
-12
lines changed

5 files changed

+230
-12
lines changed

packages/flutter_adaptive_scaffold/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
## 0.3.0
2+
3+
* Adds `inDuration`, `outDuration`, `inCurve`, and `outCurve` parameters for
4+
configuring additional `SlotLayoutConfig` animation behavior.
5+
* **BREAKING CHANGES**:
6+
* Removes `duration` parameter from `SlotLayoutConfig`.
7+
18
## 0.2.6
29

310
* Add new sample for using AdaptiveScaffold with GoRouter.

packages/flutter_adaptive_scaffold/lib/src/slot_layout.dart

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,20 @@ class SlotLayout extends StatefulWidget {
7272
WidgetBuilder? builder,
7373
Widget Function(Widget, Animation<double>)? inAnimation,
7474
Widget Function(Widget, Animation<double>)? outAnimation,
75-
Duration? duration,
75+
Duration? inDuration,
76+
Duration? outDuration,
77+
Curve? inCurve,
78+
Curve? outCurve,
7679
required Key key,
7780
}) =>
7881
SlotLayoutConfig._(
7982
builder: builder,
8083
inAnimation: inAnimation,
8184
outAnimation: outAnimation,
82-
duration: duration,
85+
inDuration: inDuration,
86+
outDuration: outDuration,
87+
inCurve: inCurve,
88+
outCurve: outCurve,
8389
key: key,
8490
);
8591

@@ -96,7 +102,11 @@ class _SlotLayoutState extends State<SlotLayout>
96102
chosenWidget = SlotLayout.pickWidget(context, widget.config);
97103
bool hasAnimation = false;
98104
return AnimatedSwitcher(
99-
duration: chosenWidget?.duration ?? const Duration(milliseconds: 1000),
105+
duration:
106+
chosenWidget?.inDuration ?? const Duration(milliseconds: 1000),
107+
reverseDuration: chosenWidget?.outDuration,
108+
switchInCurve: chosenWidget?.inCurve ?? Curves.linear,
109+
switchOutCurve: chosenWidget?.outCurve ?? Curves.linear,
100110
layoutBuilder: (Widget? currentChild, List<Widget> previousChildren) {
101111
final Stack elements = Stack(
102112
children: <Widget>[
@@ -137,7 +147,10 @@ class SlotLayoutConfig extends StatelessWidget {
137147
required this.builder,
138148
this.inAnimation,
139149
this.outAnimation,
140-
this.duration,
150+
this.inDuration,
151+
this.outDuration,
152+
this.inCurve,
153+
this.outCurve,
141154
});
142155

143156
/// The child Widget that [SlotLayout] eventually returns with an animation.
@@ -161,8 +174,21 @@ class SlotLayoutConfig extends StatelessWidget {
161174
/// as the returned widget.
162175
final Widget Function(Widget, Animation<double>)? outAnimation;
163176

164-
/// The amount of time taken by the execution of the in and out animations.
165-
final Duration? duration;
177+
/// The duration of the transition from the old child to the new one during
178+
/// a switch in [SlotLayout].
179+
final Duration? inDuration;
180+
181+
/// The duration of the transition from the new child to the old one during
182+
/// a switch in [SlotLayout].
183+
final Duration? outDuration;
184+
185+
/// The animation curve to use when transitioning in a new child during a
186+
/// switch in [SlotLayout].
187+
final Curve? inCurve;
188+
189+
/// The animation curve to use when transitioning a previous slot out during
190+
/// a switch in [SlotLayout].
191+
final Curve? outCurve;
166192

167193
/// An empty [SlotLayoutConfig] to be placed in a slot to indicate that the slot
168194
/// should show nothing.

packages/flutter_adaptive_scaffold/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: flutter_adaptive_scaffold
22
description: Widgets to easily build adaptive layouts, including navigation elements.
3-
version: 0.2.6
3+
version: 0.3.0
44
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+flutter_adaptive_scaffold%22
55
repository: https://github.com/flutter/packages/tree/main/packages/flutter_adaptive_scaffold
66

packages/flutter_adaptive_scaffold/test/adaptive_layout_test.dart

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -664,35 +664,35 @@ MediaQuery slot(double width, Duration duration, WidgetTester tester) {
664664
TestBreakpoint0(): SlotLayout.from(
665665
inAnimation: leftOutIn,
666666
outAnimation: leftInOut,
667-
duration: duration,
667+
inDuration: duration,
668668
key: const Key('0'),
669669
builder: (_) => const SizedBox(width: 10, height: 10),
670670
),
671671
TestBreakpoint400(): SlotLayout.from(
672672
inAnimation: leftOutIn,
673673
outAnimation: leftInOut,
674-
duration: duration,
674+
inDuration: duration,
675675
key: const Key('400'),
676676
builder: (_) => const SizedBox(width: 10, height: 10),
677677
),
678678
TestBreakpoint800(): SlotLayout.from(
679679
inAnimation: leftOutIn,
680680
outAnimation: leftInOut,
681-
duration: duration,
681+
inDuration: duration,
682682
key: const Key('800'),
683683
builder: (_) => const SizedBox(width: 10, height: 10),
684684
),
685685
TestBreakpoint1200(): SlotLayout.from(
686686
inAnimation: leftOutIn,
687687
outAnimation: leftInOut,
688-
duration: duration,
688+
inDuration: duration,
689689
key: const Key('1200'),
690690
builder: (_) => const SizedBox(width: 10, height: 10),
691691
),
692692
TestBreakpoint1600(): SlotLayout.from(
693693
inAnimation: leftOutIn,
694694
outAnimation: leftInOut,
695-
duration: duration,
695+
inDuration: duration,
696696
key: const Key('1600'),
697697
builder: (_) => const SizedBox(width: 10, height: 10),
698698
),
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter/material.dart';
6+
import 'package:flutter/widgets.dart';
7+
import 'package:flutter_adaptive_scaffold/src/breakpoints.dart';
8+
import 'package:flutter_adaptive_scaffold/src/slot_layout.dart';
9+
import 'package:flutter_test/flutter_test.dart';
10+
11+
void main() {
12+
testWidgets(
13+
'SlotLayout displays correct widget based on screen width',
14+
(WidgetTester tester) async {
15+
MediaQuery slot(double width) {
16+
return MediaQuery(
17+
data: MediaQueryData(size: Size(width, 2000)),
18+
child: Directionality(
19+
textDirection: TextDirection.ltr,
20+
child: SlotLayout(
21+
config: <Breakpoint, SlotLayoutConfig>{
22+
Breakpoints.smallAndUp: SlotLayout.from(
23+
key: const Key('0'), builder: (_) => const Text('Small')),
24+
Breakpoints.mediumAndUp: SlotLayout.from(
25+
key: const Key('400'),
26+
builder: (_) => const Text('Medium')),
27+
Breakpoints.largeAndUp: SlotLayout.from(
28+
key: const Key('800'), builder: (_) => const Text('Large')),
29+
},
30+
),
31+
),
32+
);
33+
}
34+
35+
await tester.pumpWidget(slot(300));
36+
expect(find.text('Small'), findsOneWidget);
37+
expect(find.text('Medium'), findsNothing);
38+
expect(find.text('Large'), findsNothing);
39+
40+
await tester.pumpWidget(slot(600));
41+
expect(find.text('Small'), findsNothing);
42+
expect(find.text('Medium'), findsOneWidget);
43+
expect(find.text('Large'), findsNothing);
44+
45+
await tester.pumpWidget(slot(1200));
46+
expect(find.text('Small'), findsNothing);
47+
expect(find.text('Medium'), findsNothing);
48+
expect(find.text('Large'), findsOneWidget);
49+
},
50+
);
51+
52+
testWidgets(
53+
'SlotLayout handles null configurations gracefully',
54+
(WidgetTester tester) async {
55+
await tester.pumpWidget(
56+
MediaQuery(
57+
data: MediaQueryData.fromView(tester.view)
58+
.copyWith(size: const Size(500, 2000)),
59+
child: Directionality(
60+
textDirection: TextDirection.ltr,
61+
child: SlotLayout(
62+
config: <Breakpoint, SlotLayoutConfig?>{
63+
Breakpoints.smallAndUp: SlotLayout.from(
64+
key: const Key('0'),
65+
builder: (BuildContext context) => Container(),
66+
),
67+
Breakpoints.mediumAndUp: null,
68+
Breakpoints.largeAndUp: SlotLayout.from(
69+
key: const Key('800'),
70+
builder: (BuildContext context) => Container(),
71+
),
72+
},
73+
),
74+
),
75+
),
76+
);
77+
78+
expect(find.byKey(const Key('0')), findsOneWidget);
79+
expect(find.byKey(const Key('400')), findsNothing);
80+
expect(find.byKey(const Key('800')), findsNothing);
81+
},
82+
);
83+
84+
testWidgets(
85+
'SlotLayout builder generates widgets correctly',
86+
(WidgetTester tester) async {
87+
await tester.pumpWidget(
88+
MediaQuery(
89+
data: const MediaQueryData(size: Size(600, 2000)),
90+
child: Directionality(
91+
textDirection: TextDirection.ltr,
92+
child: SlotLayout(
93+
config: <Breakpoint, SlotLayoutConfig>{
94+
Breakpoints.mediumAndUp: SlotLayout.from(
95+
key: const Key('0'),
96+
builder: (_) => const Text('Builder Test')),
97+
},
98+
),
99+
),
100+
),
101+
);
102+
103+
expect(find.text('Builder Test'), findsOneWidget);
104+
},
105+
);
106+
107+
testWidgets(
108+
'SlotLayout applies inAnimation and outAnimation correctly when changing breakpoints',
109+
(WidgetTester tester) async {
110+
// Define a SlotLayout with custom animations.
111+
Widget buildSlotLayout(double width) {
112+
return MediaQuery(
113+
data: MediaQueryData(size: Size(width, 2000)),
114+
child: Directionality(
115+
textDirection: TextDirection.ltr,
116+
child: SlotLayout(
117+
config: <Breakpoint, SlotLayoutConfig>{
118+
Breakpoints.smallAndUp: SlotLayout.from(
119+
key: const Key('small'),
120+
builder: (_) => const SizedBox(
121+
key: Key('smallBox'), width: 100, height: 100),
122+
inAnimation: (Widget widget, Animation<double> animation) =>
123+
ScaleTransition(
124+
scale: animation,
125+
child: widget,
126+
),
127+
outAnimation: (Widget widget, Animation<double> animation) =>
128+
FadeTransition(
129+
opacity: animation,
130+
child: widget,
131+
),
132+
inDuration: const Duration(seconds: 1),
133+
outDuration: const Duration(seconds: 2),
134+
inCurve: Curves.easeIn,
135+
outCurve: Curves.easeOut,
136+
),
137+
Breakpoints.mediumAndUp: SlotLayout.from(
138+
key: const Key('medium'),
139+
builder: (_) => const SizedBox(
140+
key: Key('mediumBox'), width: 200, height: 200),
141+
inAnimation: (Widget widget, Animation<double> animation) =>
142+
ScaleTransition(
143+
scale: animation,
144+
child: widget,
145+
),
146+
outAnimation: (Widget widget, Animation<double> animation) =>
147+
FadeTransition(
148+
opacity: animation,
149+
child: widget,
150+
),
151+
inDuration: const Duration(seconds: 1),
152+
outDuration: const Duration(seconds: 2),
153+
inCurve: Curves.easeIn,
154+
outCurve: Curves.easeOut,
155+
),
156+
},
157+
),
158+
),
159+
);
160+
}
161+
162+
// Pump the widget with the SlotLayout at small breakpoint.
163+
await tester.pumpWidget(buildSlotLayout(300));
164+
expect(find.byKey(const Key('smallBox')), findsOneWidget);
165+
expect(find.byKey(const Key('mediumBox')), findsNothing);
166+
167+
// Change to medium breakpoint to trigger outAnimation for small and inAnimation for medium.
168+
await tester.pumpWidget(buildSlotLayout(600));
169+
await tester.pump(); // Start the animation.
170+
await tester.pump(const Duration(
171+
milliseconds: 1000)); // Halfway through the outDuration.
172+
173+
// Verify that the outAnimation is in progress for smallBox.
174+
final FadeTransition fadeTransitionMid =
175+
tester.widget(find.byType(FadeTransition));
176+
expect(fadeTransitionMid.opacity.value, lessThan(1.0));
177+
expect(fadeTransitionMid.opacity.value, greaterThan(0.0));
178+
179+
// Complete the animation.
180+
await tester.pumpAndSettle();
181+
expect(find.byKey(const Key('smallBox')), findsNothing);
182+
expect(find.byKey(const Key('mediumBox')), findsOneWidget);
183+
},
184+
);
185+
}

0 commit comments

Comments
 (0)