Unverified Commit f794cf9d authored by Taha Tesser's avatar Taha Tesser Committed by GitHub

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.
parent 1f07909c
// 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')),
],
)
],
),
),
),
);
}
}
// 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 { ...@@ -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 { ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -157,6 +165,7 @@ class ExpansionTileThemeData with Diagnosticable {
shape, shape,
collapsedShape, collapsedShape,
clipBehavior, clipBehavior,
expansionAnimationStyle,
); );
} }
...@@ -180,7 +189,8 @@ class ExpansionTileThemeData with Diagnosticable { ...@@ -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 { ...@@ -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() { ...@@ -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() { ...@@ -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() { ...@@ -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() { ...@@ -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() { ...@@ -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() { ...@@ -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);
});
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment