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> {
|
class _PopupMenuExampleState extends State<PopupMenuExample> {
|
||||||
SampleItem? selectedItem;
|
SampleItem? selectedMenu;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -38,10 +38,11 @@ class _PopupMenuExampleState extends State<PopupMenuExample> {
|
|||||||
appBar: AppBar(title: const Text('PopupMenuButton')),
|
appBar: AppBar(title: const Text('PopupMenuButton')),
|
||||||
body: Center(
|
body: Center(
|
||||||
child: PopupMenuButton<SampleItem>(
|
child: PopupMenuButton<SampleItem>(
|
||||||
initialValue: selectedItem,
|
initialValue: selectedMenu,
|
||||||
|
// Callback that sets the selected popup menu item.
|
||||||
onSelected: (SampleItem item) {
|
onSelected: (SampleItem item) {
|
||||||
setState(() {
|
setState(() {
|
||||||
selectedItem = item;
|
selectedMenu = item;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
itemBuilder: (BuildContext context) => <PopupMenuEntry<SampleItem>>[
|
itemBuilder: (BuildContext context) => <PopupMenuEntry<SampleItem>>[
|
||||||
|
@ -31,7 +31,7 @@ class PopupMenuExample extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _PopupMenuExampleState extends State<PopupMenuExample> {
|
class _PopupMenuExampleState extends State<PopupMenuExample> {
|
||||||
SampleItem? selectedItem;
|
SampleItem? selectedMenu;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -39,10 +39,11 @@ class _PopupMenuExampleState extends State<PopupMenuExample> {
|
|||||||
appBar: AppBar(title: const Text('PopupMenuButton')),
|
appBar: AppBar(title: const Text('PopupMenuButton')),
|
||||||
body: Center(
|
body: Center(
|
||||||
child: PopupMenuButton<SampleItem>(
|
child: PopupMenuButton<SampleItem>(
|
||||||
initialValue: selectedItem,
|
initialValue: selectedMenu,
|
||||||
|
// Callback that sets the selected popup menu item.
|
||||||
onSelected: (SampleItem item) {
|
onSelected: (SampleItem item) {
|
||||||
setState(() {
|
setState(() {
|
||||||
selectedItem = item;
|
selectedMenu = item;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
itemBuilder: (BuildContext context) => <PopupMenuEntry<SampleItem>>[
|
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.dart';
|
||||||
export 'src/animation/animation_controller.dart';
|
export 'src/animation/animation_controller.dart';
|
||||||
export 'src/animation/animation_style.dart';
|
|
||||||
export 'src/animation/animations.dart';
|
export 'src/animation/animations.dart';
|
||||||
export 'src/animation/curves.dart';
|
export 'src/animation/curves.dart';
|
||||||
export 'src/animation/listener_helpers.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:
|
/// See also:
|
||||||
///
|
///
|
||||||
/// * [Scaffold], which provides standard app elements like an [AppBar] and a [Drawer].
|
/// * [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 feature was deprecated after v3.7.0-29.0.pre.'
|
||||||
)
|
)
|
||||||
this.useInheritedMediaQuery = false,
|
this.useInheritedMediaQuery = false,
|
||||||
this.themeAnimationStyle,
|
|
||||||
}) : routeInformationProvider = null,
|
}) : routeInformationProvider = null,
|
||||||
routeInformationParser = null,
|
routeInformationParser = null,
|
||||||
routerDelegate = null,
|
routerDelegate = null,
|
||||||
@ -298,7 +296,6 @@ class MaterialApp extends StatefulWidget {
|
|||||||
'This feature was deprecated after v3.7.0-29.0.pre.'
|
'This feature was deprecated after v3.7.0-29.0.pre.'
|
||||||
)
|
)
|
||||||
this.useInheritedMediaQuery = false,
|
this.useInheritedMediaQuery = false,
|
||||||
this.themeAnimationStyle,
|
|
||||||
}) : assert(routerDelegate != null || routerConfig != null),
|
}) : assert(routerDelegate != null || routerConfig != null),
|
||||||
navigatorObservers = null,
|
navigatorObservers = null,
|
||||||
navigatorKey = null,
|
navigatorKey = null,
|
||||||
@ -756,28 +753,6 @@ class MaterialApp extends StatefulWidget {
|
|||||||
)
|
)
|
||||||
final bool useInheritedMediaQuery;
|
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
|
@override
|
||||||
State<MaterialApp> createState() => _MaterialAppState();
|
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 effectiveSelectionColor = theme.textSelectionTheme.selectionColor ?? theme.colorScheme.primary.withOpacity(0.40);
|
||||||
final Color effectiveCursorColor = theme.textSelectionTheme.cursorColor ?? theme.colorScheme.primary;
|
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(
|
return ScaffoldMessenger(
|
||||||
key: widget.scaffoldMessengerKey,
|
key: widget.scaffoldMessengerKey,
|
||||||
child: DefaultSelectionStyle(
|
child: DefaultSelectionStyle(
|
||||||
@ -990,9 +937,26 @@ class _MaterialAppState extends State<MaterialApp> {
|
|||||||
cursorColor: effectiveCursorColor,
|
cursorColor: effectiveCursorColor,
|
||||||
child: AnimatedTheme(
|
child: AnimatedTheme(
|
||||||
data: theme,
|
data: theme,
|
||||||
duration: widget.themeAnimationStyle?.duration ?? widget.themeAnimationDuration,
|
duration: widget.themeAnimationDuration,
|
||||||
curve: widget.themeAnimationStyle?.curve ?? widget.themeAnimationCurve,
|
curve: widget.themeAnimationCurve,
|
||||||
child: childWidget,
|
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,
|
this.constraints,
|
||||||
required this.clipBehavior,
|
required this.clipBehavior,
|
||||||
super.settings,
|
super.settings,
|
||||||
this.popUpAnimationStyle,
|
|
||||||
}) : itemSizes = List<Size?>.filled(items.length, null),
|
}) : itemSizes = List<Size?>.filled(items.length, null),
|
||||||
// Menus always cycle focus through their items irrespective of the
|
// Menus always cycle focus through their items irrespective of the
|
||||||
// focus traversal edge behavior set in the Navigator.
|
// focus traversal edge behavior set in the Navigator.
|
||||||
@ -835,22 +834,18 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
|
|||||||
final CapturedThemes capturedThemes;
|
final CapturedThemes capturedThemes;
|
||||||
final BoxConstraints? constraints;
|
final BoxConstraints? constraints;
|
||||||
final Clip clipBehavior;
|
final Clip clipBehavior;
|
||||||
final AnimationStyle? popUpAnimationStyle;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Animation<double> createAnimation() {
|
Animation<double> createAnimation() {
|
||||||
if (popUpAnimationStyle != AnimationStyle.noAnimation) {
|
return CurvedAnimation(
|
||||||
return CurvedAnimation(
|
parent: super.createAnimation(),
|
||||||
parent: super.createAnimation(),
|
curve: Curves.linear,
|
||||||
curve: popUpAnimationStyle?.curve ?? Curves.linear,
|
reverseCurve: const Interval(0.0, _kMenuCloseIntervalEnd),
|
||||||
reverseCurve: popUpAnimationStyle?.reverseCurve ?? const Interval(0.0, _kMenuCloseIntervalEnd),
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
return super.createAnimation();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Duration get transitionDuration => popUpAnimationStyle?.duration ?? _kMenuDuration;
|
Duration get transitionDuration => _kMenuDuration;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get barrierDismissible => true;
|
bool get barrierDismissible => true;
|
||||||
@ -982,7 +977,6 @@ Future<T?> showMenu<T>({
|
|||||||
BoxConstraints? constraints,
|
BoxConstraints? constraints,
|
||||||
Clip clipBehavior = Clip.none,
|
Clip clipBehavior = Clip.none,
|
||||||
RouteSettings? routeSettings,
|
RouteSettings? routeSettings,
|
||||||
AnimationStyle? popUpAnimationStyle,
|
|
||||||
}) {
|
}) {
|
||||||
assert(items.isNotEmpty);
|
assert(items.isNotEmpty);
|
||||||
assert(debugCheckHasMaterialLocalizations(context));
|
assert(debugCheckHasMaterialLocalizations(context));
|
||||||
@ -1014,7 +1008,6 @@ Future<T?> showMenu<T>({
|
|||||||
constraints: constraints,
|
constraints: constraints,
|
||||||
clipBehavior: clipBehavior,
|
clipBehavior: clipBehavior,
|
||||||
settings: routeSettings,
|
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 **
|
/// ** See code in examples/api/lib/material/popup_menu/popup_menu.1.dart **
|
||||||
/// {@end-tool}
|
/// {@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:
|
/// See also:
|
||||||
///
|
///
|
||||||
/// * [PopupMenuItem], a popup menu entry for a single value.
|
/// * [PopupMenuItem], a popup menu entry for a single value.
|
||||||
@ -1143,7 +1129,6 @@ class PopupMenuButton<T> extends StatefulWidget {
|
|||||||
this.position,
|
this.position,
|
||||||
this.clipBehavior = Clip.none,
|
this.clipBehavior = Clip.none,
|
||||||
this.useRootNavigator = false,
|
this.useRootNavigator = false,
|
||||||
this.popUpAnimationStyle,
|
|
||||||
}) : assert(
|
}) : assert(
|
||||||
!(child != null && icon != null),
|
!(child != null && icon != null),
|
||||||
'You can only pass [child] or [icon], not both.',
|
'You can only pass [child] or [icon], not both.',
|
||||||
@ -1314,24 +1299,6 @@ class PopupMenuButton<T> extends StatefulWidget {
|
|||||||
/// Defaults to false.
|
/// Defaults to false.
|
||||||
final bool useRootNavigator;
|
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
|
@override
|
||||||
PopupMenuButtonState<T> createState() => PopupMenuButtonState<T>();
|
PopupMenuButtonState<T> createState() => PopupMenuButtonState<T>();
|
||||||
}
|
}
|
||||||
@ -1389,7 +1356,6 @@ class PopupMenuButtonState<T> extends State<PopupMenuButton<T>> {
|
|||||||
constraints: widget.constraints,
|
constraints: widget.constraints,
|
||||||
clipBehavior: widget.clipBehavior,
|
clipBehavior: widget.clipBehavior,
|
||||||
useRootNavigator: widget.useRootNavigator,
|
useRootNavigator: widget.useRootNavigator,
|
||||||
popUpAnimationStyle: widget.popUpAnimationStyle,
|
|
||||||
)
|
)
|
||||||
.then<void>((T? newValue) {
|
.then<void>((T? newValue) {
|
||||||
if (!mounted) {
|
if (!mounted) {
|
||||||
|
@ -1525,71 +1525,6 @@ void main() {
|
|||||||
defaultBehavior.buildScrollbar(capturedContext, child, details);
|
defaultBehavior.buildScrollbar(capturedContext, child, details);
|
||||||
}
|
}
|
||||||
}, variant: TargetPlatformVariant.all());
|
}, 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 {
|
class MockScrollBehavior extends ScrollBehavior {
|
||||||
|
@ -3872,90 +3872,6 @@ void main() {
|
|||||||
expect(rootObserver.menuCount, 0);
|
expect(rootObserver.menuCount, 0);
|
||||||
expect(nestedObserver.menuCount, 1);
|
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 {
|
class TestApp extends StatelessWidget {
|
||||||
|
Loading…
Reference in New Issue
Block a user