mirror of
https://github.com/flutter/packages.git
synced 2025-07-03 17:18:22 +08:00
[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:
@ -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
|
## 0.1.11
|
||||||
|
|
||||||
* Updates minimum supported SDK version to Flutter 3.19/Dart 3.3.
|
* Updates minimum supported SDK version to Flutter 3.19/Dart 3.3.
|
||||||
|
@ -74,12 +74,14 @@ class SlotLayout extends StatefulWidget {
|
|||||||
WidgetBuilder? builder,
|
WidgetBuilder? builder,
|
||||||
Widget Function(Widget, Animation<double>)? inAnimation,
|
Widget Function(Widget, Animation<double>)? inAnimation,
|
||||||
Widget Function(Widget, Animation<double>)? outAnimation,
|
Widget Function(Widget, Animation<double>)? outAnimation,
|
||||||
|
Duration? duration,
|
||||||
required Key key,
|
required Key key,
|
||||||
}) =>
|
}) =>
|
||||||
SlotLayoutConfig._(
|
SlotLayoutConfig._(
|
||||||
builder: builder,
|
builder: builder,
|
||||||
inAnimation: inAnimation,
|
inAnimation: inAnimation,
|
||||||
outAnimation: outAnimation,
|
outAnimation: outAnimation,
|
||||||
|
duration: duration,
|
||||||
key: key,
|
key: key,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -96,7 +98,7 @@ class _SlotLayoutState extends State<SlotLayout>
|
|||||||
chosenWidget = SlotLayout.pickWidget(context, widget.config);
|
chosenWidget = SlotLayout.pickWidget(context, widget.config);
|
||||||
bool hasAnimation = false;
|
bool hasAnimation = false;
|
||||||
return AnimatedSwitcher(
|
return AnimatedSwitcher(
|
||||||
duration: const Duration(milliseconds: 1000),
|
duration: chosenWidget?.duration ?? const Duration(milliseconds: 1000),
|
||||||
layoutBuilder: (Widget? currentChild, List<Widget> previousChildren) {
|
layoutBuilder: (Widget? currentChild, List<Widget> previousChildren) {
|
||||||
final Stack elements = Stack(
|
final Stack elements = Stack(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
@ -137,6 +139,7 @@ class SlotLayoutConfig extends StatelessWidget {
|
|||||||
required this.builder,
|
required this.builder,
|
||||||
this.inAnimation,
|
this.inAnimation,
|
||||||
this.outAnimation,
|
this.outAnimation,
|
||||||
|
this.duration,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// The child Widget that [SlotLayout] eventually returns with an animation.
|
/// The child Widget that [SlotLayout] eventually returns with an animation.
|
||||||
@ -160,6 +163,9 @@ class SlotLayoutConfig extends StatelessWidget {
|
|||||||
/// as the returned widget.
|
/// as the returned widget.
|
||||||
final Widget Function(Widget, Animation<double>)? outAnimation;
|
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
|
/// An empty [SlotLayoutConfig] to be placed in a slot to indicate that the slot
|
||||||
/// should show nothing.
|
/// should show nothing.
|
||||||
static SlotLayoutConfig empty() {
|
static SlotLayoutConfig empty() {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
name: flutter_adaptive_scaffold
|
name: flutter_adaptive_scaffold
|
||||||
description: Widgets to easily build adaptive layouts, including navigation elements.
|
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
|
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
|
repository: https://github.com/flutter/packages/tree/main/packages/flutter_adaptive_scaffold
|
||||||
|
|
||||||
|
@ -120,11 +120,13 @@ void main() {
|
|||||||
testWidgets(
|
testWidgets(
|
||||||
'slot layout properly switches between items with the appropriate animation',
|
'slot layout properly switches between items with the appropriate animation',
|
||||||
(WidgetTester tester) async {
|
(WidgetTester tester) async {
|
||||||
await tester.pumpWidget(slot(300, tester));
|
await tester
|
||||||
|
.pumpWidget(slot(300, const Duration(milliseconds: 1000), tester));
|
||||||
expect(begin, findsOneWidget);
|
expect(begin, findsOneWidget);
|
||||||
expect(end, findsNothing);
|
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();
|
||||||
await tester.pump(const Duration(milliseconds: 500));
|
await tester.pump(const Duration(milliseconds: 500));
|
||||||
expect(tester.widget<SlideTransition>(slideOut('0')).position.value,
|
expect(tester.widget<SlideTransition>(slideOut('0')).position.value,
|
||||||
@ -146,7 +148,7 @@ void main() {
|
|||||||
testWidgets('AnimatedSwitcher does not spawn duplicate keys on rapid resize',
|
testWidgets('AnimatedSwitcher does not spawn duplicate keys on rapid resize',
|
||||||
(WidgetTester tester) async {
|
(WidgetTester tester) async {
|
||||||
// Populate the smaller slot layout and let the animation settle.
|
// 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();
|
await tester.pumpAndSettle();
|
||||||
expect(begin, findsOneWidget);
|
expect(begin, findsOneWidget);
|
||||||
expect(end, findsNothing);
|
expect(end, findsNothing);
|
||||||
@ -157,12 +159,12 @@ void main() {
|
|||||||
for (int i = 0; i < 2; i++) {
|
for (int i = 0; i < 2; i++) {
|
||||||
// Resize between the two slot layouts, but do not pump the animation
|
// Resize between the two slot layouts, but do not pump the animation
|
||||||
// until completion.
|
// 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));
|
await tester.pump(const Duration(milliseconds: 100));
|
||||||
expect(begin, findsOneWidget);
|
expect(begin, findsOneWidget);
|
||||||
expect(end, 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));
|
await tester.pump(const Duration(milliseconds: 100));
|
||||||
expect(begin, findsOneWidget);
|
expect(begin, findsOneWidget);
|
||||||
expect(end, findsOneWidget);
|
expect(end, findsOneWidget);
|
||||||
@ -171,18 +173,18 @@ void main() {
|
|||||||
|
|
||||||
testWidgets('slot layout can tolerate rapid changes in breakpoints',
|
testWidgets('slot layout can tolerate rapid changes in breakpoints',
|
||||||
(WidgetTester tester) async {
|
(WidgetTester tester) async {
|
||||||
await tester.pumpWidget(slot(300, tester));
|
await tester.pumpWidget(slot(300, const Duration(seconds: 1), tester));
|
||||||
expect(begin, findsOneWidget);
|
expect(begin, findsOneWidget);
|
||||||
expect(end, findsNothing);
|
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();
|
||||||
await tester.pump(const Duration(milliseconds: 100));
|
await tester.pump(const Duration(milliseconds: 100));
|
||||||
expect(tester.widget<SlideTransition>(slideOut('0')).position.value,
|
expect(tester.widget<SlideTransition>(slideOut('0')).position.value,
|
||||||
offsetMoreOrLessEquals(const Offset(-0.1, 0), epsilon: 0.05));
|
offsetMoreOrLessEquals(const Offset(-0.1, 0), epsilon: 0.05));
|
||||||
expect(tester.widget<SlideTransition>(slideIn('400')).position.value,
|
expect(tester.widget<SlideTransition>(slideIn('400')).position.value,
|
||||||
offsetMoreOrLessEquals(const Offset(-0.9, 0), epsilon: 0.05));
|
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();
|
await tester.pumpAndSettle();
|
||||||
expect(begin, findsOneWidget);
|
expect(begin, findsOneWidget);
|
||||||
expect(end, findsNothing);
|
expect(end, findsNothing);
|
||||||
@ -243,11 +245,35 @@ void main() {
|
|||||||
tester.getBottomRight(secondaryTestBreakpoint), const Offset(390, 790));
|
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',
|
testWidgets('adaptive layout does not animate when animations off',
|
||||||
(WidgetTester tester) async {
|
(WidgetTester tester) async {
|
||||||
final Finder testBreakpoint = find.byKey(const Key('Test Breakpoint'));
|
final Finder testBreakpoint = find.byKey(const Key('Test Breakpoint'));
|
||||||
final Finder secondaryTestBreakpoint =
|
|
||||||
find.byKey(const Key('Secondary Test Breakpoint'));
|
|
||||||
|
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
await layout(width: 400, tester: tester, animations: false));
|
await layout(width: 400, tester: tester, animations: false));
|
||||||
@ -257,9 +283,6 @@ void main() {
|
|||||||
|
|
||||||
expect(tester.getTopLeft(testBreakpoint), const Offset(10, 10));
|
expect(tester.getTopLeft(testBreakpoint), const Offset(10, 10));
|
||||||
expect(tester.getBottomRight(testBreakpoint), const Offset(200, 790));
|
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,
|
TextDirection directionality = TextDirection.ltr,
|
||||||
double? bodyRatio,
|
double? bodyRatio,
|
||||||
bool animations = true,
|
bool animations = true,
|
||||||
|
int durationMs = 1000,
|
||||||
}) async {
|
}) async {
|
||||||
await tester.binding.setSurfaceSize(Size(width, 800));
|
await tester.binding.setSurfaceSize(Size(width, 800));
|
||||||
return MediaQuery(
|
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(
|
return MediaQuery(
|
||||||
data: MediaQueryData.fromView(tester.view).copyWith(size: Size(width, 800)),
|
data: MediaQueryData.fromView(tester.view).copyWith(size: Size(width, 800)),
|
||||||
child: Directionality(
|
child: Directionality(
|
||||||
@ -425,12 +449,14 @@ MediaQuery slot(double width, WidgetTester tester) {
|
|||||||
TestBreakpoint0(): SlotLayout.from(
|
TestBreakpoint0(): SlotLayout.from(
|
||||||
inAnimation: leftOutIn,
|
inAnimation: leftOutIn,
|
||||||
outAnimation: leftInOut,
|
outAnimation: leftInOut,
|
||||||
|
duration: duration,
|
||||||
key: const Key('0'),
|
key: const Key('0'),
|
||||||
builder: (_) => const SizedBox(width: 10, height: 10),
|
builder: (_) => const SizedBox(width: 10, height: 10),
|
||||||
),
|
),
|
||||||
TestBreakpoint400(): SlotLayout.from(
|
TestBreakpoint400(): SlotLayout.from(
|
||||||
inAnimation: leftOutIn,
|
inAnimation: leftOutIn,
|
||||||
outAnimation: leftInOut,
|
outAnimation: leftInOut,
|
||||||
|
duration: duration,
|
||||||
key: const Key('400'),
|
key: const Key('400'),
|
||||||
builder: (_) => const SizedBox(width: 10, height: 10),
|
builder: (_) => const SizedBox(width: 10, height: 10),
|
||||||
),
|
),
|
||||||
|
Reference in New Issue
Block a user