mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Reverts "Introduce AnimationStyle
" (#138628)
Reverts flutter/flutter#137945 Initiated by: HansMuller This change reverts the following previous change: Original Description: This PR introduces `AnimationStyle`, it is used to override default animation curves and durations in several widgets. fixes [Add the ability to customize MaterialApp theme animation duration](https://github.com/flutter/flutter/issues/78372) fixes [Allow customization of showMenu transition animation curves and duration](https://github.com/flutter/flutter/issues/135638) Here is an example where popup menu curve and transition duration is overriden: ```dart popUpAnimationStyle: AnimationStyle( curve: Easing.emphasizedAccelerate, duration: Durations.medium4, ), ``` Set `AnimationStyle.noAnimation` to disable animation. ```dart return MaterialApp( themeAnimationStyle: AnimationStyle.noAnimation, ```
This commit is contained in:
parent
e9de448420
commit
0135a3310a
@ -1,87 +0,0 @@
|
||||
// Copyright 2014 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/material.dart';
|
||||
|
||||
/// Flutter code sample for [MaterialApp].
|
||||
|
||||
void main() {
|
||||
runApp(const MaterialAppExample());
|
||||
}
|
||||
|
||||
enum AnimationStyles { defaultStyle, custom, none }
|
||||
const List<(AnimationStyles, String)> animationStyleSegments = <(AnimationStyles, String)>[
|
||||
(AnimationStyles.defaultStyle, 'Default'),
|
||||
(AnimationStyles.custom, 'Custom'),
|
||||
(AnimationStyles.none, 'None'),
|
||||
];
|
||||
|
||||
class MaterialAppExample extends StatefulWidget {
|
||||
const MaterialAppExample({super.key});
|
||||
|
||||
@override
|
||||
State<MaterialAppExample> createState() => _MaterialAppExampleState();
|
||||
}
|
||||
|
||||
class _MaterialAppExampleState extends State<MaterialAppExample> {
|
||||
Set<AnimationStyles> _animationStyleSelection = <AnimationStyles>{AnimationStyles.defaultStyle};
|
||||
AnimationStyle? _animationStyle;
|
||||
bool isDarkTheme = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
themeAnimationStyle: _animationStyle,
|
||||
themeMode: isDarkTheme ? ThemeMode.dark : ThemeMode.light,
|
||||
theme: ThemeData(colorSchemeSeed: Colors.green),
|
||||
darkTheme: ThemeData(
|
||||
colorSchemeSeed: Colors.green,
|
||||
brightness: Brightness.dark,
|
||||
),
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
SegmentedButton<AnimationStyles>(
|
||||
selected: _animationStyleSelection,
|
||||
onSelectionChanged: (Set<AnimationStyles> styles) {
|
||||
setState(() {
|
||||
_animationStyleSelection = styles;
|
||||
switch (styles.first) {
|
||||
case AnimationStyles.defaultStyle:
|
||||
_animationStyle = null;
|
||||
case AnimationStyles.custom:
|
||||
_animationStyle = AnimationStyle(
|
||||
curve: Easing.emphasizedAccelerate,
|
||||
duration: const Duration(seconds: 1),
|
||||
);
|
||||
case AnimationStyles.none:
|
||||
_animationStyle = AnimationStyle.noAnimation;
|
||||
}
|
||||
});
|
||||
},
|
||||
segments: animationStyleSegments
|
||||
.map<ButtonSegment<AnimationStyles>>(((AnimationStyles, String) shirt) {
|
||||
return ButtonSegment<AnimationStyles>(value: shirt.$1, label: Text(shirt.$2));
|
||||
})
|
||||
.toList(),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
OutlinedButton.icon(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
isDarkTheme = !isDarkTheme;
|
||||
});
|
||||
},
|
||||
icon: Icon(isDarkTheme ? Icons.wb_sunny : Icons.nightlight_round),
|
||||
label: const Text('Switch Theme Mode'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -30,7 +30,7 @@ class PopupMenuExample extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _PopupMenuExampleState extends State<PopupMenuExample> {
|
||||
SampleItem? selectedItem;
|
||||
SampleItem? selectedMenu;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -38,10 +38,11 @@ class _PopupMenuExampleState extends State<PopupMenuExample> {
|
||||
appBar: AppBar(title: const Text('PopupMenuButton')),
|
||||
body: Center(
|
||||
child: PopupMenuButton<SampleItem>(
|
||||
initialValue: selectedItem,
|
||||
initialValue: selectedMenu,
|
||||
// Callback that sets the selected popup menu item.
|
||||
onSelected: (SampleItem item) {
|
||||
setState(() {
|
||||
selectedItem = item;
|
||||
selectedMenu = item;
|
||||
});
|
||||
},
|
||||
itemBuilder: (BuildContext context) => <PopupMenuEntry<SampleItem>>[
|
||||
|
@ -31,7 +31,7 @@ class PopupMenuExample extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _PopupMenuExampleState extends State<PopupMenuExample> {
|
||||
SampleItem? selectedItem;
|
||||
SampleItem? selectedMenu;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -39,10 +39,11 @@ class _PopupMenuExampleState extends State<PopupMenuExample> {
|
||||
appBar: AppBar(title: const Text('PopupMenuButton')),
|
||||
body: Center(
|
||||
child: PopupMenuButton<SampleItem>(
|
||||
initialValue: selectedItem,
|
||||
initialValue: selectedMenu,
|
||||
// Callback that sets the selected popup menu item.
|
||||
onSelected: (SampleItem item) {
|
||||
setState(() {
|
||||
selectedItem = item;
|
||||
selectedMenu = item;
|
||||
});
|
||||
},
|
||||
itemBuilder: (BuildContext context) => <PopupMenuEntry<SampleItem>>[
|
||||
|
@ -1,129 +0,0 @@
|
||||
// Copyright 2014 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/material.dart';
|
||||
|
||||
/// Flutter code sample for [PopupMenuButton].
|
||||
|
||||
void main() => runApp(const PopupMenuApp());
|
||||
|
||||
class PopupMenuApp extends StatelessWidget {
|
||||
const PopupMenuApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const MaterialApp(
|
||||
home: PopupMenuExample(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
enum AnimationStyles { defaultStyle, custom, none }
|
||||
const List<(AnimationStyles, String)> animationStyleSegments = <(AnimationStyles, String)>[
|
||||
(AnimationStyles.defaultStyle, 'Default'),
|
||||
(AnimationStyles.custom, 'Custom'),
|
||||
(AnimationStyles.none, 'None'),
|
||||
];
|
||||
|
||||
enum Menu { preview, share, getLink, remove, download }
|
||||
|
||||
class PopupMenuExample extends StatefulWidget {
|
||||
const PopupMenuExample({super.key});
|
||||
|
||||
@override
|
||||
State<PopupMenuExample> createState() => _PopupMenuExampleState();
|
||||
}
|
||||
|
||||
class _PopupMenuExampleState extends State<PopupMenuExample> {
|
||||
Set<AnimationStyles> _animationStyleSelection = <AnimationStyles>{AnimationStyles.defaultStyle};
|
||||
AnimationStyle? _animationStyle;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 50),
|
||||
child: Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
SegmentedButton<AnimationStyles>(
|
||||
selected: _animationStyleSelection,
|
||||
onSelectionChanged: (Set<AnimationStyles> styles) {
|
||||
setState(() {
|
||||
_animationStyleSelection = styles;
|
||||
switch (styles.first) {
|
||||
case AnimationStyles.defaultStyle:
|
||||
_animationStyle = null;
|
||||
case AnimationStyles.custom:
|
||||
_animationStyle = AnimationStyle(
|
||||
curve: Easing.emphasizedDecelerate,
|
||||
duration: const Duration(seconds: 3),
|
||||
);
|
||||
case AnimationStyles.none:
|
||||
_animationStyle = AnimationStyle.noAnimation;
|
||||
}
|
||||
});
|
||||
},
|
||||
segments: animationStyleSegments
|
||||
.map<ButtonSegment<AnimationStyles>>(((AnimationStyles, String) shirt) {
|
||||
return ButtonSegment<AnimationStyles>(value: shirt.$1, label: Text(shirt.$2));
|
||||
})
|
||||
.toList(),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
PopupMenuButton<Menu>(
|
||||
popUpAnimationStyle: _animationStyle,
|
||||
icon: const Icon(Icons.more_vert),
|
||||
onSelected: (Menu item) { },
|
||||
itemBuilder: (BuildContext context) => <PopupMenuEntry<Menu>>[
|
||||
const PopupMenuItem<Menu>(
|
||||
value: Menu.preview,
|
||||
child: ListTile(
|
||||
leading: Icon(Icons.visibility_outlined),
|
||||
title: Text('Preview'),
|
||||
),
|
||||
),
|
||||
const PopupMenuItem<Menu>(
|
||||
value: Menu.share,
|
||||
child: ListTile(
|
||||
leading: Icon(Icons.share_outlined),
|
||||
title: Text('Share'),
|
||||
),
|
||||
),
|
||||
const PopupMenuItem<Menu>(
|
||||
value: Menu.getLink,
|
||||
child: ListTile(
|
||||
leading: Icon(Icons.link_outlined),
|
||||
title: Text('Get link'),
|
||||
),
|
||||
),
|
||||
const PopupMenuDivider(),
|
||||
const PopupMenuItem<Menu>(
|
||||
value: Menu.remove,
|
||||
child: ListTile(
|
||||
leading: Icon(Icons.delete_outline),
|
||||
title: Text('Remove'),
|
||||
),
|
||||
),
|
||||
const PopupMenuItem<Menu>(
|
||||
value: Menu.download,
|
||||
child: ListTile(
|
||||
leading: Icon(Icons.download_outlined),
|
||||
title: Text('Download'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
// Copyright 2014 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/material.dart';
|
||||
import 'package:flutter_api_samples/material/app/app.0.dart'
|
||||
as example;
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Theme Animation can be customized using AnimationStyle', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const example.MaterialAppExample(),
|
||||
);
|
||||
|
||||
Material getScaffoldMaterial() {
|
||||
return tester.widget<Material>(find.descendant(
|
||||
of: find.byType(Scaffold),
|
||||
matching: find.byType(Material).first,
|
||||
));
|
||||
}
|
||||
|
||||
final ThemeData lightTheme = ThemeData(colorSchemeSeed: Colors.green);
|
||||
final ThemeData darkTheme = ThemeData(
|
||||
colorSchemeSeed: Colors.green,
|
||||
brightness: Brightness.dark,
|
||||
);
|
||||
|
||||
// Test the default animation.
|
||||
expect(getScaffoldMaterial().color, lightTheme.colorScheme.background);
|
||||
|
||||
await tester.tap(find.text( 'Switch Theme Mode'));
|
||||
await tester.pump();
|
||||
// Advance the animation by half of the default duration.
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
|
||||
// The Scaffold background color is updated.
|
||||
expect(
|
||||
getScaffoldMaterial().color,
|
||||
Color.lerp(lightTheme.colorScheme.background, darkTheme.colorScheme.background, 0.5),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// The Scaffold background color is now fully dark.
|
||||
expect(getScaffoldMaterial().color, darkTheme.colorScheme.background);
|
||||
|
||||
// Test the custom animation curve and duration.
|
||||
await tester.tap(find.text('Custom'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.text('Switch Theme Mode'));
|
||||
await tester.pump();
|
||||
// Advance the animation by half of the custom duration.
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
|
||||
// The Scaffold background color is updated.
|
||||
expect(getScaffoldMaterial().color, const Color(0xff3c3e3b));
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// The Scaffold background color is now fully light.
|
||||
expect(getScaffoldMaterial().color, lightTheme.colorScheme.background);
|
||||
|
||||
// Test the no animation style.
|
||||
await tester.tap(find.text('None'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.text('Switch Theme Mode'));
|
||||
// Advance the animation by only one frame.
|
||||
await tester.pump();
|
||||
|
||||
// The Scaffold background color is updated immediately.
|
||||
expect(getScaffoldMaterial().color, darkTheme.colorScheme.background);
|
||||
});
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
// Copyright 2014 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/material.dart';
|
||||
import 'package:flutter_api_samples/material/popup_menu/popup_menu.2.dart' as example;
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Popup animation can be customized using AnimationStyle', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const example.PopupMenuApp(),
|
||||
);
|
||||
|
||||
// Test the default popup animation.
|
||||
await tester.tap(find.byIcon(Icons.more_vert));
|
||||
await tester.pump();
|
||||
// Advance the animation by half of the default duration.
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
|
||||
expect(tester.getSize(find.byType(Material).last), within(distance: 0.1, from: const Size(224.0, 130.0)));
|
||||
|
||||
// Let the animation finish.
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(tester.getSize(find.byType(Material).last), within(distance: 0.1, from: const Size(224.0, 312.0)));
|
||||
|
||||
// Tap outside the popup menu to close it.
|
||||
await tester.tapAt(const Offset(1, 1));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Test the custom animation curve and duration.
|
||||
await tester.tap(find.text('Custom'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.byIcon(Icons.more_vert));
|
||||
await tester.pump();
|
||||
// Advance the animation by one third of the custom duration.
|
||||
await tester.pump(const Duration(milliseconds: 1000));
|
||||
|
||||
expect(tester.getSize(find.byType(Material).last), within(distance: 0.1, from: const Size(224.0, 312.0)));
|
||||
|
||||
// Let the animation finish.
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(tester.getSize(find.byType(Material).last), within(distance: 0.1, from: const Size(224.0, 312.0)));
|
||||
|
||||
// Tap outside the popup menu to close it.
|
||||
await tester.tapAt(const Offset(1, 1));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Test the no animation style.
|
||||
await tester.tap(find.text('None'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.byIcon(Icons.more_vert));
|
||||
// Advance the animation by only one frame.
|
||||
await tester.pump();
|
||||
|
||||
// The popup menu is shown immediately.
|
||||
expect(tester.getSize(find.byType(Material).last), within(distance: 0.1, from: const Size(224.0, 312.0)));
|
||||
});
|
||||
}
|
@ -165,7 +165,6 @@ export 'package:flutter/scheduler.dart' show TickerCanceled;
|
||||
|
||||
export 'src/animation/animation.dart';
|
||||
export 'src/animation/animation_controller.dart';
|
||||
export 'src/animation/animation_style.dart';
|
||||
export 'src/animation/animations.dart';
|
||||
export 'src/animation/curves.dart';
|
||||
export 'src/animation/listener_helpers.dart';
|
||||
|
@ -1,45 +0,0 @@
|
||||
// Copyright 2014 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 'curves.dart';
|
||||
|
||||
/// Used to override the default parameters of an animation.
|
||||
///
|
||||
/// Currently, this class is used by the following widgets:
|
||||
/// - [ExpansionTile]
|
||||
/// - [MaterialApp]
|
||||
/// - [PopupMenuButton]
|
||||
///
|
||||
/// If [duration] and [reverseDuration] are set to [Duration.zero], the
|
||||
/// corresponding animation will be disabled.
|
||||
///
|
||||
/// All of the parameters are optional. If no parameters are specified,
|
||||
/// the default animation will be used.
|
||||
class AnimationStyle {
|
||||
/// Creates an instance of Animation Style class.
|
||||
AnimationStyle({
|
||||
this.curve,
|
||||
this.duration,
|
||||
this.reverseCurve,
|
||||
this.reverseDuration,
|
||||
});
|
||||
|
||||
/// Creates an instance of Animation Style class with no animation.
|
||||
static AnimationStyle noAnimation = AnimationStyle(
|
||||
duration: Duration.zero,
|
||||
reverseDuration: Duration.zero,
|
||||
);
|
||||
|
||||
/// When specified, the animation will use this curve.
|
||||
final Curve? curve;
|
||||
|
||||
/// When specified, the animation will use this duration.
|
||||
final Duration? duration;
|
||||
|
||||
/// When specified, the reverse animation will use this curve.
|
||||
final Curve? reverseCurve;
|
||||
|
||||
/// When specified, the reverse animation will use this duration.
|
||||
final Duration? reverseDuration;
|
||||
}
|
@ -184,7 +184,6 @@ enum ThemeMode {
|
||||
/// ),
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [Scaffold], which provides standard app elements like an [AppBar] and a [Drawer].
|
||||
@ -247,7 +246,6 @@ class MaterialApp extends StatefulWidget {
|
||||
'This feature was deprecated after v3.7.0-29.0.pre.'
|
||||
)
|
||||
this.useInheritedMediaQuery = false,
|
||||
this.themeAnimationStyle,
|
||||
}) : routeInformationProvider = null,
|
||||
routeInformationParser = null,
|
||||
routerDelegate = null,
|
||||
@ -298,7 +296,6 @@ class MaterialApp extends StatefulWidget {
|
||||
'This feature was deprecated after v3.7.0-29.0.pre.'
|
||||
)
|
||||
this.useInheritedMediaQuery = false,
|
||||
this.themeAnimationStyle,
|
||||
}) : assert(routerDelegate != null || routerConfig != null),
|
||||
navigatorObservers = null,
|
||||
navigatorKey = null,
|
||||
@ -756,28 +753,6 @@ class MaterialApp extends StatefulWidget {
|
||||
)
|
||||
final bool useInheritedMediaQuery;
|
||||
|
||||
/// Used to override the theme animation curve and duration.
|
||||
///
|
||||
/// If [AnimationStyle.duration] is provided, it will be used to override
|
||||
/// the theme animation duration in the underlying [AnimatedTheme] widget.
|
||||
/// If it is null, then [themeAnimationDuration] will be used. Otherwise,
|
||||
/// defaults to 200ms.
|
||||
///
|
||||
/// If [AnimationStyle.curve] is provided, it will be used to override
|
||||
/// the theme animation curve in the underlying [AnimatedTheme] widget.
|
||||
/// If it is null, then [themeAnimationCurve] will be used. Otherwise,
|
||||
/// defaults to [Curves.linear].
|
||||
///
|
||||
/// To disable the theme animation, use [AnimationStyle.noAnimation].
|
||||
///
|
||||
/// {@tool dartpad}
|
||||
/// This sample showcases how to override the theme animation curve and
|
||||
/// duration in the [MaterialApp] widget using [AnimationStyle].
|
||||
///
|
||||
/// ** See code in examples/api/lib/material/app/app.0.dart **
|
||||
/// {@end-tool}
|
||||
final AnimationStyle? themeAnimationStyle;
|
||||
|
||||
@override
|
||||
State<MaterialApp> createState() => _MaterialAppState();
|
||||
|
||||
@ -955,34 +930,6 @@ class _MaterialAppState extends State<MaterialApp> {
|
||||
final Color effectiveSelectionColor = theme.textSelectionTheme.selectionColor ?? theme.colorScheme.primary.withOpacity(0.40);
|
||||
final Color effectiveCursorColor = theme.textSelectionTheme.cursorColor ?? theme.colorScheme.primary;
|
||||
|
||||
Widget childWidget = widget.builder != null
|
||||
? Builder(
|
||||
builder: (BuildContext context) {
|
||||
// Why are we surrounding a builder with a builder?
|
||||
//
|
||||
// The widget.builder may contain code that invokes
|
||||
// Theme.of(), which should return the theme we selected
|
||||
// above in AnimatedTheme. However, if we invoke
|
||||
// widget.builder() directly as the child of AnimatedTheme
|
||||
// then there is no Context separating them, and the
|
||||
// widget.builder() will not find the theme. Therefore, we
|
||||
// surround widget.builder with yet another builder so that
|
||||
// a context separates them and Theme.of() correctly
|
||||
// resolves to the theme we passed to AnimatedTheme.
|
||||
return widget.builder!(context, child);
|
||||
},
|
||||
)
|
||||
: child ?? const SizedBox.shrink();
|
||||
|
||||
if (widget.themeAnimationStyle != AnimationStyle.noAnimation) {
|
||||
childWidget = AnimatedTheme(
|
||||
data: theme,
|
||||
duration: widget.themeAnimationStyle?.duration ?? widget.themeAnimationDuration,
|
||||
curve: widget.themeAnimationStyle?.curve ?? widget.themeAnimationCurve,
|
||||
child: childWidget,
|
||||
);
|
||||
}
|
||||
|
||||
return ScaffoldMessenger(
|
||||
key: widget.scaffoldMessengerKey,
|
||||
child: DefaultSelectionStyle(
|
||||
@ -990,9 +937,26 @@ class _MaterialAppState extends State<MaterialApp> {
|
||||
cursorColor: effectiveCursorColor,
|
||||
child: AnimatedTheme(
|
||||
data: theme,
|
||||
duration: widget.themeAnimationStyle?.duration ?? widget.themeAnimationDuration,
|
||||
curve: widget.themeAnimationStyle?.curve ?? widget.themeAnimationCurve,
|
||||
child: childWidget,
|
||||
duration: widget.themeAnimationDuration,
|
||||
curve: widget.themeAnimationCurve,
|
||||
child: widget.builder != null
|
||||
? Builder(
|
||||
builder: (BuildContext context) {
|
||||
// Why are we surrounding a builder with a builder?
|
||||
//
|
||||
// The widget.builder may contain code that invokes
|
||||
// Theme.of(), which should return the theme we selected
|
||||
// above in AnimatedTheme. However, if we invoke
|
||||
// widget.builder() directly as the child of AnimatedTheme
|
||||
// then there is no Context separating them, and the
|
||||
// widget.builder() will not find the theme. Therefore, we
|
||||
// surround widget.builder with yet another builder so that
|
||||
// a context separates them and Theme.of() correctly
|
||||
// resolves to the theme we passed to AnimatedTheme.
|
||||
return widget.builder!(context, child);
|
||||
},
|
||||
)
|
||||
: child ?? const SizedBox.shrink(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -816,7 +816,6 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
|
||||
this.constraints,
|
||||
required this.clipBehavior,
|
||||
super.settings,
|
||||
this.popUpAnimationStyle,
|
||||
}) : itemSizes = List<Size?>.filled(items.length, null),
|
||||
// Menus always cycle focus through their items irrespective of the
|
||||
// focus traversal edge behavior set in the Navigator.
|
||||
@ -835,22 +834,18 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
|
||||
final CapturedThemes capturedThemes;
|
||||
final BoxConstraints? constraints;
|
||||
final Clip clipBehavior;
|
||||
final AnimationStyle? popUpAnimationStyle;
|
||||
|
||||
@override
|
||||
Animation<double> createAnimation() {
|
||||
if (popUpAnimationStyle != AnimationStyle.noAnimation) {
|
||||
return CurvedAnimation(
|
||||
parent: super.createAnimation(),
|
||||
curve: popUpAnimationStyle?.curve ?? Curves.linear,
|
||||
reverseCurve: popUpAnimationStyle?.reverseCurve ?? const Interval(0.0, _kMenuCloseIntervalEnd),
|
||||
);
|
||||
}
|
||||
return super.createAnimation();
|
||||
return CurvedAnimation(
|
||||
parent: super.createAnimation(),
|
||||
curve: Curves.linear,
|
||||
reverseCurve: const Interval(0.0, _kMenuCloseIntervalEnd),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Duration get transitionDuration => popUpAnimationStyle?.duration ?? _kMenuDuration;
|
||||
Duration get transitionDuration => _kMenuDuration;
|
||||
|
||||
@override
|
||||
bool get barrierDismissible => true;
|
||||
@ -982,7 +977,6 @@ Future<T?> showMenu<T>({
|
||||
BoxConstraints? constraints,
|
||||
Clip clipBehavior = Clip.none,
|
||||
RouteSettings? routeSettings,
|
||||
AnimationStyle? popUpAnimationStyle,
|
||||
}) {
|
||||
assert(items.isNotEmpty);
|
||||
assert(debugCheckHasMaterialLocalizations(context));
|
||||
@ -1014,7 +1008,6 @@ Future<T?> showMenu<T>({
|
||||
constraints: constraints,
|
||||
clipBehavior: clipBehavior,
|
||||
settings: routeSettings,
|
||||
popUpAnimationStyle: popUpAnimationStyle,
|
||||
));
|
||||
}
|
||||
|
||||
@ -1102,13 +1095,6 @@ typedef PopupMenuItemBuilder<T> = List<PopupMenuEntry<T>> Function(BuildContext
|
||||
/// ** See code in examples/api/lib/material/popup_menu/popup_menu.1.dart **
|
||||
/// {@end-tool}
|
||||
///
|
||||
/// {@tool dartpad}
|
||||
/// This sample showcases how to override the [PopupMenuButton] animation
|
||||
/// curves and duration using [AnimationStyle].
|
||||
///
|
||||
/// ** See code in examples/api/lib/material/popup_menu/popup_menu.2.dart **
|
||||
/// {@end-tool}
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [PopupMenuItem], a popup menu entry for a single value.
|
||||
@ -1143,7 +1129,6 @@ class PopupMenuButton<T> extends StatefulWidget {
|
||||
this.position,
|
||||
this.clipBehavior = Clip.none,
|
||||
this.useRootNavigator = false,
|
||||
this.popUpAnimationStyle,
|
||||
}) : assert(
|
||||
!(child != null && icon != null),
|
||||
'You can only pass [child] or [icon], not both.',
|
||||
@ -1314,24 +1299,6 @@ class PopupMenuButton<T> extends StatefulWidget {
|
||||
/// Defaults to false.
|
||||
final bool useRootNavigator;
|
||||
|
||||
/// Used to override the default animation curves and durations of the popup
|
||||
/// menu's open and close transitions.
|
||||
///
|
||||
/// If [AnimationStyle.curve] is provided, it will be used to override
|
||||
/// the default popup animation curve. Otherwise, defaults to [Curves.linear].
|
||||
///
|
||||
/// If [AnimationStyle.reverseCurve] is provided, it will be used to
|
||||
/// override the default popup animation reverse curve. Otherwise, defaults to
|
||||
/// `Interval(0.0, 2.0 / 3.0)`.
|
||||
///
|
||||
/// If [AnimationStyle.duration] is provided, it will be used to override
|
||||
/// the default popup animation duration. Otherwise, defaults to 300ms.
|
||||
///
|
||||
/// To disable the theme animation, use [AnimationStyle.noAnimation].
|
||||
///
|
||||
/// If this is null, then the default animation will be used.
|
||||
final AnimationStyle? popUpAnimationStyle;
|
||||
|
||||
@override
|
||||
PopupMenuButtonState<T> createState() => PopupMenuButtonState<T>();
|
||||
}
|
||||
@ -1389,7 +1356,6 @@ class PopupMenuButtonState<T> extends State<PopupMenuButton<T>> {
|
||||
constraints: widget.constraints,
|
||||
clipBehavior: widget.clipBehavior,
|
||||
useRootNavigator: widget.useRootNavigator,
|
||||
popUpAnimationStyle: widget.popUpAnimationStyle,
|
||||
)
|
||||
.then<void>((T? newValue) {
|
||||
if (!mounted) {
|
||||
|
@ -1525,71 +1525,6 @@ void main() {
|
||||
defaultBehavior.buildScrollbar(capturedContext, child, details);
|
||||
}
|
||||
}, variant: TargetPlatformVariant.all());
|
||||
|
||||
testWidgetsWithLeakTracking('Override theme animation using AnimationStyle', (WidgetTester tester) async {
|
||||
final ThemeData lightTheme = ThemeData.light();
|
||||
final ThemeData darkTheme = ThemeData.dark();
|
||||
|
||||
Widget buildWidget({ ThemeMode themeMode = ThemeMode.light, AnimationStyle? animationStyle }) {
|
||||
return MaterialApp(
|
||||
theme: lightTheme,
|
||||
darkTheme: darkTheme,
|
||||
themeMode: themeMode,
|
||||
themeAnimationStyle: animationStyle,
|
||||
home: const Scaffold(body: Text('body')),
|
||||
);
|
||||
}
|
||||
|
||||
// Test the initial Scaffold background color.
|
||||
await tester.pumpWidget(buildWidget());
|
||||
|
||||
expect(tester.widget<Material>(find.byType(Material)).color, const Color(0xfffffbfe));
|
||||
|
||||
// Test the Scaffold background color animation from light to dark theme.
|
||||
await tester.pumpWidget(buildWidget(themeMode: ThemeMode.dark));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 50)); // Advance animation by 50 milliseconds.
|
||||
|
||||
// Scaffold background color is slightly updated.
|
||||
expect(tester.widget<Material>(find.byType(Material)).color, const Color(0xffc6c3c6));
|
||||
|
||||
// Let the animation finish.
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Scaffold background color is fully updated to dark theme.
|
||||
expect(tester.widget<Material>(find.byType(Material)).color, const Color(0xff1c1b1f));
|
||||
|
||||
// Reset to light theme to compare the Scaffold background color animation
|
||||
// with the default animation curve.
|
||||
await tester.pumpWidget(buildWidget());
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Switch to dark theme with overriden animation curve.
|
||||
await tester.pumpWidget(buildWidget(
|
||||
themeMode: ThemeMode.dark,
|
||||
animationStyle: AnimationStyle(curve: Curves.easeIn,
|
||||
)));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 50));
|
||||
|
||||
// Scaffold background color is slightly updated but with a different
|
||||
// color than the default animation curve.
|
||||
expect(tester.widget<Material>(find.byType(Material)).color, const Color(0xffe9e5e9));
|
||||
|
||||
// Let the animation finish.
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Scaffold background color is fully updated to dark theme.
|
||||
expect(tester.widget<Material>(find.byType(Material)).color, const Color(0xff1c1b1f));
|
||||
|
||||
// Switch from dark to light theme with overriden animation duration.
|
||||
await tester.pumpWidget(buildWidget(animationStyle: AnimationStyle.noAnimation));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 1));
|
||||
|
||||
expect(tester.widget<Material>(find.byType(Material)).color, isNot(const Color(0xff1c1b1f)));
|
||||
expect(tester.widget<Material>(find.byType(Material)).color, const Color(0xfffffbfe));
|
||||
});
|
||||
}
|
||||
|
||||
class MockScrollBehavior extends ScrollBehavior {
|
||||
|
@ -3872,90 +3872,6 @@ void main() {
|
||||
expect(rootObserver.menuCount, 0);
|
||||
expect(nestedObserver.menuCount, 1);
|
||||
});
|
||||
|
||||
testWidgetsWithLeakTracking('Override Popup Menu animation using AnimationStyle', (WidgetTester tester) async {
|
||||
final Key targetKey = UniqueKey();
|
||||
|
||||
Widget buildPopupMenu({ AnimationStyle? animationStyle }) {
|
||||
return MaterialApp(
|
||||
home: Material(
|
||||
child: Center(
|
||||
child: PopupMenuButton<int>(
|
||||
key: targetKey,
|
||||
popUpAnimationStyle: animationStyle,
|
||||
itemBuilder: (BuildContext context) {
|
||||
return <PopupMenuItem<int>>[
|
||||
const PopupMenuItem<int>(
|
||||
value: 1,
|
||||
child: Text('One'),
|
||||
),
|
||||
const PopupMenuItem<int>(
|
||||
value: 2,
|
||||
child: Text('Two'),
|
||||
),
|
||||
const PopupMenuItem<int>(
|
||||
value: 3,
|
||||
child: Text('Three'),
|
||||
),
|
||||
];
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Test default animation.
|
||||
await tester.pumpWidget(buildPopupMenu());
|
||||
|
||||
await tester.tap(find.byKey(targetKey));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 100)); // Advance the animation by 1/3 of its duration.
|
||||
|
||||
expect(tester.getSize(find.byType(Material).last), within(distance: 0.1, from: const Size(112.0, 80.0)));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 100)); // Advance the animation by 2/3 of its duration.
|
||||
|
||||
expect(tester.getSize(find.byType(Material).last), within(distance: 0.1, from: const Size(112.0, 160.0)));
|
||||
|
||||
await tester.pumpAndSettle(); // Advance the animation to the end.
|
||||
|
||||
expect(tester.getSize(find.byType(Material).last), within(distance: 0.1, from: const Size(112.0, 160.0)));
|
||||
|
||||
// Tap outside to dismiss the menu.
|
||||
await tester.tapAt(const Offset(20.0, 20.0));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Override the animation duration.
|
||||
await tester.pumpWidget(buildPopupMenu(animationStyle: AnimationStyle(duration: Duration.zero)));
|
||||
|
||||
await tester.tap(find.byKey(targetKey));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 1)); // Advance the animation by 1 millisecond.
|
||||
|
||||
expect(tester.getSize(find.byType(Material).last), within(distance: 0.1, from: const Size(112.0, 160.0)));
|
||||
|
||||
// Tap outside to dismiss the menu.
|
||||
await tester.tapAt(const Offset(20.0, 20.0));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Override the animation curve.
|
||||
await tester.pumpWidget(buildPopupMenu(animationStyle: AnimationStyle(curve: Easing.emphasizedAccelerate)));
|
||||
|
||||
await tester.tap(find.byKey(targetKey));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 100)); // Advance the animation by 1/3 of its duration.
|
||||
|
||||
expect(tester.getSize(find.byType(Material).last), within(distance: 0.1, from: const Size(32.4, 15.4)));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 100)); // Advance the animation by 2/3 of its duration.
|
||||
|
||||
expect(tester.getSize(find.byType(Material).last), within(distance: 0.1, from: const Size(112.0, 72.2)));
|
||||
|
||||
await tester.pumpAndSettle(); // Advance the animation to the end.
|
||||
|
||||
expect(tester.getSize(find.byType(Material).last), within(distance: 0.1, from: const Size(112.0, 160.0)));
|
||||
});
|
||||
}
|
||||
|
||||
class TestApp extends StatelessWidget {
|
||||
|
Loading…
Reference in New Issue
Block a user