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.dense,
|
||||||
this.visualDensity,
|
this.visualDensity,
|
||||||
this.enableFeedback = true,
|
this.enableFeedback = true,
|
||||||
|
this.expansionAnimationStyle,
|
||||||
}) : assert(
|
}) : assert(
|
||||||
expandedCrossAxisAlignment != CrossAxisAlignment.baseline,
|
expandedCrossAxisAlignment != CrossAxisAlignment.baseline,
|
||||||
'CrossAxisAlignment.baseline is not supported since the expanded children '
|
'CrossAxisAlignment.baseline is not supported since the expanded children '
|
||||||
@ -506,6 +507,28 @@ class ExpansionTile extends StatefulWidget {
|
|||||||
/// {@macro flutter.material.ListTile.enableFeedback}
|
/// {@macro flutter.material.ListTile.enableFeedback}
|
||||||
final bool? 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
|
@override
|
||||||
State<ExpansionTile> createState() => _ExpansionTileState();
|
State<ExpansionTile> createState() => _ExpansionTileState();
|
||||||
}
|
}
|
||||||
@ -519,6 +542,7 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider
|
|||||||
final ColorTween _headerColorTween = ColorTween();
|
final ColorTween _headerColorTween = ColorTween();
|
||||||
final ColorTween _iconColorTween = ColorTween();
|
final ColorTween _iconColorTween = ColorTween();
|
||||||
final ColorTween _backgroundColorTween = ColorTween();
|
final ColorTween _backgroundColorTween = ColorTween();
|
||||||
|
final CurveTween _heightFactorTween = CurveTween(curve: Curves.easeIn);
|
||||||
|
|
||||||
late AnimationController _animationController;
|
late AnimationController _animationController;
|
||||||
late Animation<double> _iconTurns;
|
late Animation<double> _iconTurns;
|
||||||
@ -535,7 +559,7 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_animationController = AnimationController(duration: _kExpand, vsync: this);
|
_animationController = AnimationController(duration: _kExpand, vsync: this);
|
||||||
_heightFactor = _animationController.drive(_easeInTween);
|
_heightFactor = _animationController.drive(_heightFactorTween);
|
||||||
_iconTurns = _animationController.drive(_halfTween.chain(_easeInTween));
|
_iconTurns = _animationController.drive(_halfTween.chain(_easeInTween));
|
||||||
_border = _animationController.drive(_borderTween.chain(_easeOutTween));
|
_border = _animationController.drive(_borderTween.chain(_easeOutTween));
|
||||||
_headerColor = _animationController.drive(_headerColorTween.chain(_easeInTween));
|
_headerColor = _animationController.drive(_headerColorTween.chain(_easeInTween));
|
||||||
@ -711,6 +735,10 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider
|
|||||||
|| widget.collapsedBackgroundColor != oldWidget.collapsedBackgroundColor) {
|
|| widget.collapsedBackgroundColor != oldWidget.collapsedBackgroundColor) {
|
||||||
_updateBackgroundColor(expansionTileTheme);
|
_updateBackgroundColor(expansionTileTheme);
|
||||||
}
|
}
|
||||||
|
if (widget.expansionAnimationStyle != oldWidget.expansionAnimationStyle) {
|
||||||
|
_updateAnimationDuration(expansionTileTheme);
|
||||||
|
_updateHeightFactorCurve(expansionTileTheme);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -720,13 +748,21 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider
|
|||||||
final ExpansionTileThemeData defaults = theme.useMaterial3
|
final ExpansionTileThemeData defaults = theme.useMaterial3
|
||||||
? _ExpansionTileDefaultsM3(context)
|
? _ExpansionTileDefaultsM3(context)
|
||||||
: _ExpansionTileDefaultsM2(context);
|
: _ExpansionTileDefaultsM2(context);
|
||||||
|
_updateAnimationDuration(expansionTileTheme);
|
||||||
_updateShapeBorder(expansionTileTheme, theme);
|
_updateShapeBorder(expansionTileTheme, theme);
|
||||||
_updateHeaderColor(expansionTileTheme, defaults);
|
_updateHeaderColor(expansionTileTheme, defaults);
|
||||||
_updateIconColor(expansionTileTheme, defaults);
|
_updateIconColor(expansionTileTheme, defaults);
|
||||||
_updateBackgroundColor(expansionTileTheme);
|
_updateBackgroundColor(expansionTileTheme);
|
||||||
|
_updateHeightFactorCurve(expansionTileTheme);
|
||||||
super.didChangeDependencies();
|
super.didChangeDependencies();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _updateAnimationDuration(ExpansionTileThemeData expansionTileTheme) {
|
||||||
|
_animationController.duration = widget.expansionAnimationStyle?.duration
|
||||||
|
?? expansionTileTheme.expansionAnimationStyle?.duration
|
||||||
|
?? _kExpand;
|
||||||
|
}
|
||||||
|
|
||||||
void _updateShapeBorder(ExpansionTileThemeData expansionTileTheme, ThemeData theme) {
|
void _updateShapeBorder(ExpansionTileThemeData expansionTileTheme, ThemeData theme) {
|
||||||
_borderTween
|
_borderTween
|
||||||
..begin = widget.collapsedShape
|
..begin = widget.collapsedShape
|
||||||
@ -765,6 +801,12 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider
|
|||||||
..end = widget.backgroundColor ?? expansionTileTheme.backgroundColor;
|
..end = widget.backgroundColor ?? expansionTileTheme.backgroundColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _updateHeightFactorCurve(ExpansionTileThemeData expansionTileTheme) {
|
||||||
|
_heightFactorTween.curve = widget.expansionAnimationStyle?.curve
|
||||||
|
?? expansionTileTheme.expansionAnimationStyle?.curve
|
||||||
|
?? Curves.easeIn;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final ExpansionTileThemeData expansionTileTheme = ExpansionTileTheme.of(context);
|
final ExpansionTileThemeData expansionTileTheme = ExpansionTileTheme.of(context);
|
||||||
|
@ -52,6 +52,7 @@ class ExpansionTileThemeData with Diagnosticable {
|
|||||||
this.shape,
|
this.shape,
|
||||||
this.collapsedShape,
|
this.collapsedShape,
|
||||||
this.clipBehavior,
|
this.clipBehavior,
|
||||||
|
this.expansionAnimationStyle,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Overrides the default value of [ExpansionTile.backgroundColor].
|
/// Overrides the default value of [ExpansionTile.backgroundColor].
|
||||||
@ -90,6 +91,9 @@ class ExpansionTileThemeData with Diagnosticable {
|
|||||||
/// Overrides the default value of [ExpansionTile.clipBehavior].
|
/// Overrides the default value of [ExpansionTile.clipBehavior].
|
||||||
final Clip? 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
|
/// Creates a copy of this object with the given fields replaced with the
|
||||||
/// new values.
|
/// new values.
|
||||||
ExpansionTileThemeData copyWith({
|
ExpansionTileThemeData copyWith({
|
||||||
@ -105,6 +109,7 @@ class ExpansionTileThemeData with Diagnosticable {
|
|||||||
ShapeBorder? shape,
|
ShapeBorder? shape,
|
||||||
ShapeBorder? collapsedShape,
|
ShapeBorder? collapsedShape,
|
||||||
Clip? clipBehavior,
|
Clip? clipBehavior,
|
||||||
|
AnimationStyle? expansionAnimationStyle,
|
||||||
}) {
|
}) {
|
||||||
return ExpansionTileThemeData(
|
return ExpansionTileThemeData(
|
||||||
backgroundColor: backgroundColor ?? this.backgroundColor,
|
backgroundColor: backgroundColor ?? this.backgroundColor,
|
||||||
@ -119,6 +124,7 @@ class ExpansionTileThemeData with Diagnosticable {
|
|||||||
shape: shape ?? this.shape,
|
shape: shape ?? this.shape,
|
||||||
collapsedShape: collapsedShape ?? this.collapsedShape,
|
collapsedShape: collapsedShape ?? this.collapsedShape,
|
||||||
clipBehavior: clipBehavior ?? this.clipBehavior,
|
clipBehavior: clipBehavior ?? this.clipBehavior,
|
||||||
|
expansionAnimationStyle: expansionAnimationStyle ?? this.expansionAnimationStyle,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,6 +145,8 @@ class ExpansionTileThemeData with Diagnosticable {
|
|||||||
collapsedTextColor: Color.lerp(a?.collapsedTextColor, b?.collapsedTextColor, t),
|
collapsedTextColor: Color.lerp(a?.collapsedTextColor, b?.collapsedTextColor, t),
|
||||||
shape: ShapeBorder.lerp(a?.shape, b?.shape, t),
|
shape: ShapeBorder.lerp(a?.shape, b?.shape, t),
|
||||||
collapsedShape: ShapeBorder.lerp(a?.collapsedShape, b?.collapsedShape, 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,
|
shape,
|
||||||
collapsedShape,
|
collapsedShape,
|
||||||
clipBehavior,
|
clipBehavior,
|
||||||
|
expansionAnimationStyle,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,7 +189,8 @@ class ExpansionTileThemeData with Diagnosticable {
|
|||||||
&& other.collapsedTextColor == collapsedTextColor
|
&& other.collapsedTextColor == collapsedTextColor
|
||||||
&& other.shape == shape
|
&& other.shape == shape
|
||||||
&& other.collapsedShape == collapsedShape
|
&& other.collapsedShape == collapsedShape
|
||||||
&& other.clipBehavior == clipBehavior;
|
&& other.clipBehavior == clipBehavior
|
||||||
|
&& other.expansionAnimationStyle == expansionAnimationStyle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -198,6 +208,7 @@ class ExpansionTileThemeData with Diagnosticable {
|
|||||||
properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
|
properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
|
||||||
properties.add(DiagnosticsProperty<ShapeBorder>('collapsedShape', collapsedShape, defaultValue: null));
|
properties.add(DiagnosticsProperty<ShapeBorder>('collapsedShape', collapsedShape, defaultValue: null));
|
||||||
properties.add(DiagnosticsProperty<Clip>('clipBehavior', clipBehavior, 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));
|
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', () {
|
group('Material 2', () {
|
||||||
// These tests are only relevant for Material 2. Once Material 2
|
// These tests are only relevant for Material 2. Once Material 2
|
||||||
// support is deprecated and the APIs are removed, these tests
|
// support is deprecated and the APIs are removed, these tests
|
||||||
|
@ -68,6 +68,7 @@ void main() {
|
|||||||
expect(theme.shape, null);
|
expect(theme.shape, null);
|
||||||
expect(theme.collapsedShape, null);
|
expect(theme.collapsedShape, null);
|
||||||
expect(theme.clipBehavior, null);
|
expect(theme.clipBehavior, null);
|
||||||
|
expect(theme.expansionAnimationStyle, null);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgetsWithLeakTracking('Default ExpansionTileThemeData debugFillProperties', (WidgetTester tester) async {
|
testWidgetsWithLeakTracking('Default ExpansionTileThemeData debugFillProperties', (WidgetTester tester) async {
|
||||||
@ -84,19 +85,20 @@ void main() {
|
|||||||
|
|
||||||
testWidgetsWithLeakTracking('ExpansionTileThemeData implements debugFillProperties', (WidgetTester tester) async {
|
testWidgetsWithLeakTracking('ExpansionTileThemeData implements debugFillProperties', (WidgetTester tester) async {
|
||||||
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
|
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
|
||||||
const ExpansionTileThemeData(
|
ExpansionTileThemeData(
|
||||||
backgroundColor: Color(0xff000000),
|
backgroundColor: const Color(0xff000000),
|
||||||
collapsedBackgroundColor: Color(0xff6f83fc),
|
collapsedBackgroundColor: const Color(0xff6f83fc),
|
||||||
tilePadding: EdgeInsets.all(20.0),
|
tilePadding: const EdgeInsets.all(20.0),
|
||||||
expandedAlignment: Alignment.bottomCenter,
|
expandedAlignment: Alignment.bottomCenter,
|
||||||
childrenPadding: EdgeInsets.all(10.0),
|
childrenPadding: const EdgeInsets.all(10.0),
|
||||||
iconColor: Color(0xffa7c61c),
|
iconColor: const Color(0xffa7c61c),
|
||||||
collapsedIconColor: Color(0xffdd0b1f),
|
collapsedIconColor: const Color(0xffdd0b1f),
|
||||||
textColor: Color(0xffffffff),
|
textColor: const Color(0xffffffff),
|
||||||
collapsedTextColor: Color(0xff522bab),
|
collapsedTextColor: const Color(0xff522bab),
|
||||||
shape: Border(),
|
shape: const Border(),
|
||||||
collapsedShape: Border(),
|
collapsedShape: const Border(),
|
||||||
clipBehavior: Clip.antiAlias,
|
clipBehavior: Clip.antiAlias,
|
||||||
|
expansionAnimationStyle: AnimationStyle(curve: Curves.easeInOut),
|
||||||
).debugFillProperties(builder);
|
).debugFillProperties(builder);
|
||||||
|
|
||||||
final List<String> description = builder.properties
|
final List<String> description = builder.properties
|
||||||
@ -104,7 +106,7 @@ void main() {
|
|||||||
.map((DiagnosticsNode node) => node.toString())
|
.map((DiagnosticsNode node) => node.toString())
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
expect(description, <String>[
|
expect(description, equalsIgnoringHashCodes(<String>[
|
||||||
'backgroundColor: Color(0xff000000)',
|
'backgroundColor: Color(0xff000000)',
|
||||||
'collapsedBackgroundColor: Color(0xff6f83fc)',
|
'collapsedBackgroundColor: Color(0xff6f83fc)',
|
||||||
'tilePadding: EdgeInsets.all(20.0)',
|
'tilePadding: EdgeInsets.all(20.0)',
|
||||||
@ -117,7 +119,8 @@ void main() {
|
|||||||
'shape: Border.all(BorderSide(width: 0.0, style: none))',
|
'shape: Border.all(BorderSide(width: 0.0, style: none))',
|
||||||
'collapsedShape: Border.all(BorderSide(width: 0.0, style: none))',
|
'collapsedShape: Border.all(BorderSide(width: 0.0, style: none))',
|
||||||
'clipBehavior: Clip.antiAlias',
|
'clipBehavior: Clip.antiAlias',
|
||||||
]);
|
'expansionAnimationStyle: AnimationStyle#983ac(curve: Cubic(0.42, 0.00, 0.58, 1.00))',
|
||||||
|
]));
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgetsWithLeakTracking('ExpansionTileTheme - collapsed', (WidgetTester tester) async {
|
testWidgetsWithLeakTracking('ExpansionTileTheme - collapsed', (WidgetTester tester) async {
|
||||||
@ -305,4 +308,109 @@ void main() {
|
|||||||
expect(childRect.right, paddingRect.right - 20);
|
expect(childRect.right, paddingRect.right - 20);
|
||||||
expect(childRect.bottom, paddingRect.bottom - 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