mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Add AnimationStyle
to ExpansionTile
(#139664)
fixes [Expose animation parameters for the [ExpansionTile] widget](https://github.com/flutter/flutter/issues/138047) ### Description Add `AnimationStyle` to the `ExpansionTile` widget to override the default expand and close animation. Syntax: ```dart child: ExpansionTile( title: const Text('Tap to expand'), expansionAnimationStyle: AnimationStyle( duration: Durations.extralong1, curve: Easing.emphasizedAccelerate, ), children: const <Widget>[FlutterLogo(size: 200)], ), ``` ### Code sample <details> <summary>expand to view the code sample</summary> ```dart // 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 [ExpansionTile] and [AnimationStyle]. void main() { runApp(const ExpansionTileAnimationStyleApp()); } enum AnimationStyles { defaultStyle, custom, none } const List<(AnimationStyles, String)> animationStyleSegments = <(AnimationStyles, String)>[ (AnimationStyles.defaultStyle, 'Default'), (AnimationStyles.custom, 'Custom'), (AnimationStyles.none, 'None'), ]; class ExpansionTileAnimationStyleApp extends StatefulWidget { const ExpansionTileAnimationStyleApp({super.key}); @override State<ExpansionTileAnimationStyleApp> createState() => _ExpansionTileAnimationStyleAppState(); } class _ExpansionTileAnimationStyleAppState extends State<ExpansionTileAnimationStyleApp> { Set<AnimationStyles> _animationStyleSelection = <AnimationStyles>{AnimationStyles.defaultStyle}; AnimationStyle? _animationStyle; @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( body: SafeArea( 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: Durations.extralong1, ); 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: 20), ExpansionTile( expansionAnimationStyle: _animationStyle, title: const Text('ExpansionTile'), children: const <Widget>[ ListTile(title: Text('Expanded Item 1')), ListTile(title: Text('Expanded Item 2')), ], ) ], ), ), ), ); } } ``` </details> Related to https://github.com/flutter/flutter/pull/138721.
This commit is contained in:
parent
1f07909c4d
commit
f794cf9d97
@ -0,0 +1,78 @@
|
||||
// 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 [ExpansionTile] and [AnimationStyle].
|
||||
|
||||
void main() {
|
||||
runApp(const ExpansionTileAnimationStyleApp());
|
||||
}
|
||||
|
||||
enum AnimationStyles { defaultStyle, custom, none }
|
||||
const List<(AnimationStyles, String)> animationStyleSegments = <(AnimationStyles, String)>[
|
||||
(AnimationStyles.defaultStyle, 'Default'),
|
||||
(AnimationStyles.custom, 'Custom'),
|
||||
(AnimationStyles.none, 'None'),
|
||||
];
|
||||
|
||||
class ExpansionTileAnimationStyleApp extends StatefulWidget {
|
||||
const ExpansionTileAnimationStyleApp({super.key});
|
||||
|
||||
@override
|
||||
State<ExpansionTileAnimationStyleApp> createState() => _ExpansionTileAnimationStyleAppState();
|
||||
}
|
||||
|
||||
class _ExpansionTileAnimationStyleAppState extends State<ExpansionTileAnimationStyleApp> {
|
||||
Set<AnimationStyles> _animationStyleSelection = <AnimationStyles>{AnimationStyles.defaultStyle};
|
||||
AnimationStyle? _animationStyle;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
home: Scaffold(
|
||||
body: SafeArea(
|
||||
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: Durations.extralong1,
|
||||
);
|
||||
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: 20),
|
||||
ExpansionTile(
|
||||
expansionAnimationStyle: _animationStyle,
|
||||
title: const Text('ExpansionTile'),
|
||||
children: const <Widget>[
|
||||
ListTile(title: Text('Expanded Item 1')),
|
||||
ListTile(title: Text('Expanded Item 2')),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
// 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/expansion_tile/expansion_tile.2.dart' as example;
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('ExpansionTile animation can be customized using AnimationStyle', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const example.ExpansionTileAnimationStyleApp(),
|
||||
);
|
||||
|
||||
double getHeight(WidgetTester tester) {
|
||||
return tester.getSize(find.byType(ExpansionTile)).height;
|
||||
}
|
||||
|
||||
expect(getHeight(tester), 58.0);
|
||||
|
||||
// Test the default animation style.
|
||||
await tester.tap(find.text('ExpansionTile'));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
|
||||
expect(getHeight(tester), closeTo(93.4, 0.1));
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(getHeight(tester), 170.0);
|
||||
|
||||
// Tap to collapse.
|
||||
await tester.tap(find.text('ExpansionTile'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Test the custom animation style.
|
||||
await tester.tap(find.text('Custom'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.text('ExpansionTile'));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
|
||||
expect(getHeight(tester), closeTo(59.2, 0.1));
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(getHeight(tester), 170.0);
|
||||
|
||||
// Tap to collapse.
|
||||
await tester.tap(find.text('ExpansionTile'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Test the no animation style.
|
||||
await tester.tap(find.text('None'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.text('ExpansionTile'));
|
||||
await tester.pump();
|
||||
|
||||
expect(getHeight(tester), 170.0);
|
||||
});
|
||||
}
|
@ -252,6 +252,7 @@ class ExpansionTile extends StatefulWidget {
|
||||
this.dense,
|
||||
this.visualDensity,
|
||||
this.enableFeedback = true,
|
||||
this.expansionAnimationStyle,
|
||||
}) : assert(
|
||||
expandedCrossAxisAlignment != CrossAxisAlignment.baseline,
|
||||
'CrossAxisAlignment.baseline is not supported since the expanded children '
|
||||
@ -506,6 +507,28 @@ class ExpansionTile extends StatefulWidget {
|
||||
/// {@macro flutter.material.ListTile.enableFeedback}
|
||||
final bool? enableFeedback;
|
||||
|
||||
/// Used to override the expansion animation curve and duration.
|
||||
///
|
||||
/// If [AnimationStyle.duration] is provided, it will be used to override
|
||||
/// the expansion animation duration. If it is null, then [AnimationStyle.duration]
|
||||
/// from the [ExpansionTileThemeData.expansionAnimationStyle] will be used.
|
||||
/// Otherwise, defaults to 200ms.
|
||||
///
|
||||
/// If [AnimationStyle.curve] is provided, it will be used to override
|
||||
/// the expansion animation curve. If it is null, then [AnimationStyle.curve]
|
||||
/// from the [ExpansionTileThemeData.expansionAnimationStyle] will be used.
|
||||
/// Otherwise, defaults to [Curves.easeIn].
|
||||
///
|
||||
/// To disable the theme animation, use [AnimationStyle.noAnimation].
|
||||
///
|
||||
/// {@tool dartpad}
|
||||
/// This sample showcases how to override the [ExpansionTile] expansion
|
||||
/// animation curve and duration using [AnimationStyle].
|
||||
///
|
||||
/// ** See code in examples/api/lib/material/expansion_tile/expansion_tile.2.dart **
|
||||
/// {@end-tool}
|
||||
final AnimationStyle? expansionAnimationStyle;
|
||||
|
||||
@override
|
||||
State<ExpansionTile> createState() => _ExpansionTileState();
|
||||
}
|
||||
@ -519,6 +542,7 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider
|
||||
final ColorTween _headerColorTween = ColorTween();
|
||||
final ColorTween _iconColorTween = ColorTween();
|
||||
final ColorTween _backgroundColorTween = ColorTween();
|
||||
final CurveTween _heightFactorTween = CurveTween(curve: Curves.easeIn);
|
||||
|
||||
late AnimationController _animationController;
|
||||
late Animation<double> _iconTurns;
|
||||
@ -535,7 +559,7 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider
|
||||
void initState() {
|
||||
super.initState();
|
||||
_animationController = AnimationController(duration: _kExpand, vsync: this);
|
||||
_heightFactor = _animationController.drive(_easeInTween);
|
||||
_heightFactor = _animationController.drive(_heightFactorTween);
|
||||
_iconTurns = _animationController.drive(_halfTween.chain(_easeInTween));
|
||||
_border = _animationController.drive(_borderTween.chain(_easeOutTween));
|
||||
_headerColor = _animationController.drive(_headerColorTween.chain(_easeInTween));
|
||||
@ -711,6 +735,10 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider
|
||||
|| widget.collapsedBackgroundColor != oldWidget.collapsedBackgroundColor) {
|
||||
_updateBackgroundColor(expansionTileTheme);
|
||||
}
|
||||
if (widget.expansionAnimationStyle != oldWidget.expansionAnimationStyle) {
|
||||
_updateAnimationDuration(expansionTileTheme);
|
||||
_updateHeightFactorCurve(expansionTileTheme);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@ -720,13 +748,21 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider
|
||||
final ExpansionTileThemeData defaults = theme.useMaterial3
|
||||
? _ExpansionTileDefaultsM3(context)
|
||||
: _ExpansionTileDefaultsM2(context);
|
||||
_updateAnimationDuration(expansionTileTheme);
|
||||
_updateShapeBorder(expansionTileTheme, theme);
|
||||
_updateHeaderColor(expansionTileTheme, defaults);
|
||||
_updateIconColor(expansionTileTheme, defaults);
|
||||
_updateBackgroundColor(expansionTileTheme);
|
||||
_updateHeightFactorCurve(expansionTileTheme);
|
||||
super.didChangeDependencies();
|
||||
}
|
||||
|
||||
void _updateAnimationDuration(ExpansionTileThemeData expansionTileTheme) {
|
||||
_animationController.duration = widget.expansionAnimationStyle?.duration
|
||||
?? expansionTileTheme.expansionAnimationStyle?.duration
|
||||
?? _kExpand;
|
||||
}
|
||||
|
||||
void _updateShapeBorder(ExpansionTileThemeData expansionTileTheme, ThemeData theme) {
|
||||
_borderTween
|
||||
..begin = widget.collapsedShape
|
||||
@ -765,6 +801,12 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider
|
||||
..end = widget.backgroundColor ?? expansionTileTheme.backgroundColor;
|
||||
}
|
||||
|
||||
void _updateHeightFactorCurve(ExpansionTileThemeData expansionTileTheme) {
|
||||
_heightFactorTween.curve = widget.expansionAnimationStyle?.curve
|
||||
?? expansionTileTheme.expansionAnimationStyle?.curve
|
||||
?? Curves.easeIn;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final ExpansionTileThemeData expansionTileTheme = ExpansionTileTheme.of(context);
|
||||
|
@ -52,6 +52,7 @@ class ExpansionTileThemeData with Diagnosticable {
|
||||
this.shape,
|
||||
this.collapsedShape,
|
||||
this.clipBehavior,
|
||||
this.expansionAnimationStyle,
|
||||
});
|
||||
|
||||
/// Overrides the default value of [ExpansionTile.backgroundColor].
|
||||
@ -90,6 +91,9 @@ class ExpansionTileThemeData with Diagnosticable {
|
||||
/// Overrides the default value of [ExpansionTile.clipBehavior].
|
||||
final Clip? clipBehavior;
|
||||
|
||||
/// Overrides the default value of [ExpansionTile.expansionAnimationStyle].
|
||||
final AnimationStyle? expansionAnimationStyle;
|
||||
|
||||
/// Creates a copy of this object with the given fields replaced with the
|
||||
/// new values.
|
||||
ExpansionTileThemeData copyWith({
|
||||
@ -105,6 +109,7 @@ class ExpansionTileThemeData with Diagnosticable {
|
||||
ShapeBorder? shape,
|
||||
ShapeBorder? collapsedShape,
|
||||
Clip? clipBehavior,
|
||||
AnimationStyle? expansionAnimationStyle,
|
||||
}) {
|
||||
return ExpansionTileThemeData(
|
||||
backgroundColor: backgroundColor ?? this.backgroundColor,
|
||||
@ -119,6 +124,7 @@ class ExpansionTileThemeData with Diagnosticable {
|
||||
shape: shape ?? this.shape,
|
||||
collapsedShape: collapsedShape ?? this.collapsedShape,
|
||||
clipBehavior: clipBehavior ?? this.clipBehavior,
|
||||
expansionAnimationStyle: expansionAnimationStyle ?? this.expansionAnimationStyle,
|
||||
);
|
||||
}
|
||||
|
||||
@ -139,6 +145,8 @@ class ExpansionTileThemeData with Diagnosticable {
|
||||
collapsedTextColor: Color.lerp(a?.collapsedTextColor, b?.collapsedTextColor, t),
|
||||
shape: ShapeBorder.lerp(a?.shape, b?.shape, t),
|
||||
collapsedShape: ShapeBorder.lerp(a?.collapsedShape, b?.collapsedShape, t),
|
||||
clipBehavior: t < 0.5 ? a?.clipBehavior : b?.clipBehavior,
|
||||
expansionAnimationStyle: t < 0.5 ? a?.expansionAnimationStyle : b?.expansionAnimationStyle,
|
||||
);
|
||||
}
|
||||
|
||||
@ -157,6 +165,7 @@ class ExpansionTileThemeData with Diagnosticable {
|
||||
shape,
|
||||
collapsedShape,
|
||||
clipBehavior,
|
||||
expansionAnimationStyle,
|
||||
);
|
||||
}
|
||||
|
||||
@ -180,7 +189,8 @@ class ExpansionTileThemeData with Diagnosticable {
|
||||
&& other.collapsedTextColor == collapsedTextColor
|
||||
&& other.shape == shape
|
||||
&& other.collapsedShape == collapsedShape
|
||||
&& other.clipBehavior == clipBehavior;
|
||||
&& other.clipBehavior == clipBehavior
|
||||
&& other.expansionAnimationStyle == expansionAnimationStyle;
|
||||
}
|
||||
|
||||
@override
|
||||
@ -198,6 +208,7 @@ class ExpansionTileThemeData with Diagnosticable {
|
||||
properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<ShapeBorder>('collapsedShape', collapsedShape, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<Clip>('clipBehavior', clipBehavior, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<AnimationStyle>('expansionAnimationStyle', expansionAnimationStyle, defaultValue: null));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1049,6 +1049,107 @@ void main() {
|
||||
expect(tester.state<TestTextState>(find.byType(TestText)).textStyle.color, const Color(0xffffffff));
|
||||
});
|
||||
|
||||
testWidgetsWithLeakTracking('Override ExpansionTile animation using AnimationStyle', (WidgetTester tester) async {
|
||||
const Key expansionTileKey = Key('expansionTileKey');
|
||||
|
||||
Widget buildExpansionTile({ AnimationStyle? animationStyle }) {
|
||||
return MaterialApp(
|
||||
home: Material(
|
||||
child: Center(
|
||||
child: ExpansionTile(
|
||||
key: expansionTileKey,
|
||||
expansionAnimationStyle: animationStyle,
|
||||
title: const TestText('title'),
|
||||
children: const <Widget>[
|
||||
SizedBox(height: 100, width: 100),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(buildExpansionTile());
|
||||
|
||||
double getHeight(Key key) => tester.getSize(find.byKey(key)).height;
|
||||
|
||||
// Test initial ExpansionTile height.
|
||||
expect(getHeight(expansionTileKey), 58.0);
|
||||
|
||||
// Test the default expansion animation.
|
||||
await tester.tap(find.text('title'));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 50)); // Advance the animation by 1/4 of its duration.
|
||||
|
||||
expect(getHeight(expansionTileKey), closeTo(67.4, 0.1));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 50)); // Advance the animation by 2/4 of its duration.
|
||||
|
||||
expect(getHeight(expansionTileKey), closeTo(89.6, 0.1));
|
||||
|
||||
await tester.pumpAndSettle(); // Advance the animation to the end.
|
||||
|
||||
expect(getHeight(expansionTileKey), 158.0);
|
||||
|
||||
// Tap to collapse the ExpansionTile.
|
||||
await tester.tap(find.text('title'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Override the animation duration.
|
||||
await tester.pumpWidget(buildExpansionTile(animationStyle: AnimationStyle(duration: const Duration(milliseconds: 800))));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Test the overridden animation duration.
|
||||
await tester.tap(find.text('title'));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 200)); // Advance the animation by 1/4 of its duration.
|
||||
|
||||
expect(getHeight(expansionTileKey), closeTo(67.4, 0.1));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 200)); // Advance the animation by 2/4 of its duration.
|
||||
|
||||
expect(getHeight(expansionTileKey), closeTo(89.6, 0.1));
|
||||
|
||||
await tester.pumpAndSettle(); // Advance the animation to the end.
|
||||
|
||||
expect(getHeight(expansionTileKey), 158.0);
|
||||
|
||||
// Tap to collapse the ExpansionTile.
|
||||
await tester.tap(find.text('title'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Override the animation curve.
|
||||
await tester.pumpWidget(buildExpansionTile(animationStyle: AnimationStyle(curve: Easing.emphasizedDecelerate)));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Test the overridden animation curve.
|
||||
await tester.tap(find.text('title'));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 50)); // Advance the animation by 1/4 of its duration.
|
||||
|
||||
expect(getHeight(expansionTileKey), closeTo(141.2, 0.1));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 50)); // Advance the animation by 2/4 of its duration.
|
||||
|
||||
expect(getHeight(expansionTileKey), closeTo(153, 0.1));
|
||||
|
||||
await tester.pumpAndSettle(); // Advance the animation to the end.
|
||||
|
||||
expect(getHeight(expansionTileKey), 158.0);
|
||||
|
||||
// Tap to collapse the ExpansionTile.
|
||||
await tester.tap(find.text('title'));
|
||||
|
||||
// Test no animation.
|
||||
await tester.pumpWidget(buildExpansionTile(animationStyle: AnimationStyle.noAnimation));
|
||||
|
||||
// Tap to expand the ExpansionTile.
|
||||
await tester.tap(find.text('title'));
|
||||
await tester.pump();
|
||||
|
||||
expect(getHeight(expansionTileKey), 158.0);
|
||||
});
|
||||
|
||||
group('Material 2', () {
|
||||
// These tests are only relevant for Material 2. Once Material 2
|
||||
// support is deprecated and the APIs are removed, these tests
|
||||
|
@ -68,6 +68,7 @@ void main() {
|
||||
expect(theme.shape, null);
|
||||
expect(theme.collapsedShape, null);
|
||||
expect(theme.clipBehavior, null);
|
||||
expect(theme.expansionAnimationStyle, null);
|
||||
});
|
||||
|
||||
testWidgetsWithLeakTracking('Default ExpansionTileThemeData debugFillProperties', (WidgetTester tester) async {
|
||||
@ -84,19 +85,20 @@ void main() {
|
||||
|
||||
testWidgetsWithLeakTracking('ExpansionTileThemeData implements debugFillProperties', (WidgetTester tester) async {
|
||||
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
|
||||
const ExpansionTileThemeData(
|
||||
backgroundColor: Color(0xff000000),
|
||||
collapsedBackgroundColor: Color(0xff6f83fc),
|
||||
tilePadding: EdgeInsets.all(20.0),
|
||||
ExpansionTileThemeData(
|
||||
backgroundColor: const Color(0xff000000),
|
||||
collapsedBackgroundColor: const Color(0xff6f83fc),
|
||||
tilePadding: const EdgeInsets.all(20.0),
|
||||
expandedAlignment: Alignment.bottomCenter,
|
||||
childrenPadding: EdgeInsets.all(10.0),
|
||||
iconColor: Color(0xffa7c61c),
|
||||
collapsedIconColor: Color(0xffdd0b1f),
|
||||
textColor: Color(0xffffffff),
|
||||
collapsedTextColor: Color(0xff522bab),
|
||||
shape: Border(),
|
||||
collapsedShape: Border(),
|
||||
childrenPadding: const EdgeInsets.all(10.0),
|
||||
iconColor: const Color(0xffa7c61c),
|
||||
collapsedIconColor: const Color(0xffdd0b1f),
|
||||
textColor: const Color(0xffffffff),
|
||||
collapsedTextColor: const Color(0xff522bab),
|
||||
shape: const Border(),
|
||||
collapsedShape: const Border(),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
expansionAnimationStyle: AnimationStyle(curve: Curves.easeInOut),
|
||||
).debugFillProperties(builder);
|
||||
|
||||
final List<String> description = builder.properties
|
||||
@ -104,7 +106,7 @@ void main() {
|
||||
.map((DiagnosticsNode node) => node.toString())
|
||||
.toList();
|
||||
|
||||
expect(description, <String>[
|
||||
expect(description, equalsIgnoringHashCodes(<String>[
|
||||
'backgroundColor: Color(0xff000000)',
|
||||
'collapsedBackgroundColor: Color(0xff6f83fc)',
|
||||
'tilePadding: EdgeInsets.all(20.0)',
|
||||
@ -117,7 +119,8 @@ void main() {
|
||||
'shape: Border.all(BorderSide(width: 0.0, style: none))',
|
||||
'collapsedShape: Border.all(BorderSide(width: 0.0, style: none))',
|
||||
'clipBehavior: Clip.antiAlias',
|
||||
]);
|
||||
'expansionAnimationStyle: AnimationStyle#983ac(curve: Cubic(0.42, 0.00, 0.58, 1.00))',
|
||||
]));
|
||||
});
|
||||
|
||||
testWidgetsWithLeakTracking('ExpansionTileTheme - collapsed', (WidgetTester tester) async {
|
||||
@ -305,4 +308,109 @@ void main() {
|
||||
expect(childRect.right, paddingRect.right - 20);
|
||||
expect(childRect.bottom, paddingRect.bottom - 20);
|
||||
});
|
||||
|
||||
testWidgetsWithLeakTracking('Override ExpansionTile animation using ExpansionTileThemeData.AnimationStyle', (WidgetTester tester) async {
|
||||
const Key expansionTileKey = Key('expansionTileKey');
|
||||
|
||||
Widget buildExpansionTile({ AnimationStyle? animationStyle }) {
|
||||
return MaterialApp(
|
||||
theme: ThemeData(
|
||||
expansionTileTheme: ExpansionTileThemeData(
|
||||
expansionAnimationStyle: animationStyle,
|
||||
),
|
||||
),
|
||||
home: const Material(
|
||||
child: Center(
|
||||
child: ExpansionTile(
|
||||
key: expansionTileKey,
|
||||
title: TestText('title'),
|
||||
children: <Widget>[
|
||||
SizedBox(height: 100, width: 100),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(buildExpansionTile());
|
||||
|
||||
double getHeight(Key key) => tester.getSize(find.byKey(key)).height;
|
||||
|
||||
// Test initial ExpansionTile height.
|
||||
expect(getHeight(expansionTileKey), 58.0);
|
||||
|
||||
// Test the default expansion animation.
|
||||
await tester.tap(find.text('title'));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 50)); // Advance the animation by 1/4 of its duration.
|
||||
|
||||
expect(getHeight(expansionTileKey), closeTo(67.4, 0.1));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 50)); // Advance the animation by 2/4 of its duration.
|
||||
|
||||
expect(getHeight(expansionTileKey), closeTo(89.6, 0.1));
|
||||
|
||||
await tester.pumpAndSettle(); // Advance the animation to the end.
|
||||
|
||||
expect(getHeight(expansionTileKey), 158.0);
|
||||
|
||||
// Tap to collapse the ExpansionTile.
|
||||
await tester.tap(find.text('title'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Override the animation duration.
|
||||
await tester.pumpWidget(buildExpansionTile(animationStyle: AnimationStyle(duration: const Duration(milliseconds: 800))));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Test the overridden animation duration.
|
||||
await tester.tap(find.text('title'));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 200)); // Advance the animation by 1/4 of its duration.
|
||||
|
||||
expect(getHeight(expansionTileKey), closeTo(67.4, 0.1));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 200)); // Advance the animation by 2/4 of its duration.
|
||||
|
||||
expect(getHeight(expansionTileKey), closeTo(89.6, 0.1));
|
||||
|
||||
await tester.pumpAndSettle(); // Advance the animation to the end.
|
||||
|
||||
expect(getHeight(expansionTileKey), 158.0);
|
||||
|
||||
// Tap to collapse the ExpansionTile.
|
||||
await tester.tap(find.text('title'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Override the animation curve.
|
||||
await tester.pumpWidget(buildExpansionTile(animationStyle: AnimationStyle(curve: Easing.emphasizedDecelerate)));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Test the overridden animation curve.
|
||||
await tester.tap(find.text('title'));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 50)); // Advance the animation by 1/4 of its duration.
|
||||
|
||||
expect(getHeight(expansionTileKey), closeTo(141.2, 0.1));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 50)); // Advance the animation by 2/4 of its duration.
|
||||
|
||||
expect(getHeight(expansionTileKey), closeTo(153, 0.1));
|
||||
|
||||
await tester.pumpAndSettle(); // Advance the animation to the end.
|
||||
|
||||
expect(getHeight(expansionTileKey), 158.0);
|
||||
|
||||
// Tap to collapse the ExpansionTile.
|
||||
await tester.tap(find.text('title'));
|
||||
|
||||
// Test no animation.
|
||||
await tester.pumpWidget(buildExpansionTile(animationStyle: AnimationStyle.noAnimation));
|
||||
|
||||
// Tap to expand the ExpansionTile.
|
||||
await tester.tap(find.text('title'));
|
||||
await tester.pump();
|
||||
|
||||
expect(getHeight(expansionTileKey), 158.0);
|
||||
});
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user