Unverified Commit 3f22a079 authored by DattatreyaReddy Panta's avatar DattatreyaReddy Panta Committed by GitHub

Added ShapeBorder to expansionTile (#112218)

parent 21861423
......@@ -10,6 +10,7 @@ import 'expansion_tile_theme.dart';
import 'icons.dart';
import 'list_tile.dart';
import 'list_tile_theme.dart';
import 'material.dart';
import 'theme.dart';
const Duration _kExpand = Duration(milliseconds: 200);
......@@ -68,6 +69,9 @@ class ExpansionTile extends StatefulWidget {
this.collapsedTextColor,
this.iconColor,
this.collapsedIconColor,
this.shape,
this.collapsedShape,
this.clipBehavior,
this.controlAffinity,
}) : assert(initiallyExpanded != null),
assert(maintainState != null),
......@@ -254,6 +258,39 @@ class ExpansionTile extends StatefulWidget {
/// [ExpansionTileThemeData].
final Color? collapsedTextColor;
/// The tile's border shape when the sublist is expanded.
///
/// If this property is null, the [ExpansionTileThemeData.shape] is used. If that
/// is also null, a [Border] with vertical sides default to [ThemeData.dividerColor] is used
///
/// See also:
///
/// * [ExpansionTileTheme.of], which returns the nearest [ExpansionTileTheme]'s
/// [ExpansionTileThemeData].
final ShapeBorder? shape;
/// The tile's border shape when the sublist is collapsed.
///
/// If this property is null, the [ExpansionTileThemeData.collapsedShape] is used. If that
/// is also null, a [Border] with vertical sides default to Color [Colors.transparent] is used
///
/// See also:
///
/// * [ExpansionTileTheme.of], which returns the nearest [ExpansionTileTheme]'s
/// [ExpansionTileThemeData].
final ShapeBorder? collapsedShape;
/// {@macro flutter.material.Material.clipBehavior}
///
/// If this property is null, the [ExpansionTileThemeData.clipBehavior] is used. If that
/// is also null, a [Clip.none] is used
///
/// See also:
///
/// * [ExpansionTileTheme.of], which returns the nearest [ExpansionTileTheme]'s
/// [ExpansionTileThemeData].
final Clip? clipBehavior;
/// Typically used to force the expansion arrow icon to the tile's leading or trailing edge.
///
/// By default, the value of [controlAffinity] is [ListTileControlAffinity.platform],
......@@ -269,7 +306,7 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider
static final Animatable<double> _easeInTween = CurveTween(curve: Curves.easeIn);
static final Animatable<double> _halfTween = Tween<double>(begin: 0.0, end: 0.5);
final ColorTween _borderColorTween = ColorTween();
final ShapeBorderTween _borderTween = ShapeBorderTween();
final ColorTween _headerColorTween = ColorTween();
final ColorTween _iconColorTween = ColorTween();
final ColorTween _backgroundColorTween = ColorTween();
......@@ -277,7 +314,7 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider
late AnimationController _controller;
late Animation<double> _iconTurns;
late Animation<double> _heightFactor;
late Animation<Color?> _borderColor;
late Animation<ShapeBorder?> _border;
late Animation<Color?> _headerColor;
late Animation<Color?> _iconColor;
late Animation<Color?> _backgroundColor;
......@@ -290,7 +327,7 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider
_controller = AnimationController(duration: _kExpand, vsync: this);
_heightFactor = _controller.drive(_easeInTween);
_iconTurns = _controller.drive(_halfTween.chain(_easeInTween));
_borderColor = _controller.drive(_borderColorTween.chain(_easeOutTween));
_border = _controller.drive(_borderTween.chain(_easeOutTween));
_headerColor = _controller.drive(_headerColorTween.chain(_easeInTween));
_iconColor = _controller.drive(_iconColorTween.chain(_easeInTween));
_backgroundColor = _controller.drive(_backgroundColorTween.chain(_easeOutTween));
......@@ -361,15 +398,17 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider
Widget _buildChildren(BuildContext context, Widget? child) {
final ExpansionTileThemeData expansionTileTheme = ExpansionTileTheme.of(context);
final Color borderSideColor = _borderColor.value ?? Colors.transparent;
final ShapeBorder expansionTileBorder = _border.value ?? const Border(
top: BorderSide(color: Colors.transparent),
bottom: BorderSide(color: Colors.transparent),
);
final Clip clipBehavior = widget.clipBehavior ?? expansionTileTheme.clipBehavior ?? Clip.none;
return Container(
decoration: BoxDecoration(
clipBehavior: clipBehavior,
decoration: ShapeDecoration(
color: _backgroundColor.value ?? expansionTileTheme.backgroundColor ?? Colors.transparent,
border: Border(
top: BorderSide(color: borderSideColor),
bottom: BorderSide(color: borderSideColor),
),
shape: expansionTileBorder,
),
child: Column(
mainAxisSize: MainAxisSize.min,
......@@ -405,7 +444,19 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider
final ThemeData theme = Theme.of(context);
final ExpansionTileThemeData expansionTileTheme = ExpansionTileTheme.of(context);
final ColorScheme colorScheme = theme.colorScheme;
_borderColorTween.end = theme.dividerColor;
_borderTween
..begin = widget.collapsedShape
?? expansionTileTheme.collapsedShape
?? const Border(
top: BorderSide(color: Colors.transparent),
bottom: BorderSide(color: Colors.transparent),
)
..end = widget.shape
?? expansionTileTheme.collapsedShape
?? Border(
top: BorderSide(color: theme.dividerColor),
bottom: BorderSide(color: theme.dividerColor),
);
_headerColorTween
..begin = widget.collapsedTextColor
?? expansionTileTheme.collapsedTextColor
......
......@@ -49,6 +49,9 @@ class ExpansionTileThemeData with Diagnosticable {
this.collapsedIconColor,
this.textColor,
this.collapsedTextColor,
this.shape,
this.collapsedShape,
this.clipBehavior,
});
/// Overrides the default value of [ExpansionTile.backgroundColor].
......@@ -78,6 +81,15 @@ class ExpansionTileThemeData with Diagnosticable {
/// Overrides the default value of [ExpansionTile.collapsedTextColor].
final Color? collapsedTextColor;
/// Overrides the default value of [ExpansionTile.shape].
final ShapeBorder? shape;
/// Overrides the default value of [ExpansionTile.collapsedShape].
final ShapeBorder? collapsedShape;
/// Overrides the default value of [ExpansionTile.clipBehavior].
final Clip? clipBehavior;
/// Creates a copy of this object with the given fields replaced with the
/// new values.
ExpansionTileThemeData copyWith({
......@@ -90,6 +102,9 @@ class ExpansionTileThemeData with Diagnosticable {
Color? collapsedIconColor,
Color? textColor,
Color? collapsedTextColor,
ShapeBorder? shape,
ShapeBorder? collapsedShape,
Clip? clipBehavior,
}) {
return ExpansionTileThemeData(
backgroundColor: backgroundColor ?? this.backgroundColor,
......@@ -101,6 +116,9 @@ class ExpansionTileThemeData with Diagnosticable {
collapsedIconColor: collapsedIconColor ?? this.collapsedIconColor,
textColor: textColor ?? this.textColor,
collapsedTextColor: collapsedTextColor ?? this.collapsedTextColor,
shape: shape ?? this.shape,
collapsedShape: collapsedShape ?? this.collapsedShape,
clipBehavior: clipBehavior ?? this.clipBehavior,
);
}
......@@ -120,6 +138,8 @@ class ExpansionTileThemeData with Diagnosticable {
collapsedIconColor: Color.lerp(a?.collapsedIconColor, b?.collapsedIconColor, t),
textColor: Color.lerp(a?.textColor, b?.textColor, t),
collapsedTextColor: Color.lerp(a?.collapsedTextColor, b?.collapsedTextColor, t),
shape: ShapeBorder.lerp(a?.shape, b?.shape, t),
collapsedShape: ShapeBorder.lerp(a?.collapsedShape, b?.collapsedShape, t),
);
}
......@@ -135,6 +155,9 @@ class ExpansionTileThemeData with Diagnosticable {
collapsedIconColor,
textColor,
collapsedTextColor,
shape,
collapsedShape,
clipBehavior,
);
}
......@@ -155,7 +178,10 @@ class ExpansionTileThemeData with Diagnosticable {
&& other.iconColor == iconColor
&& other.collapsedIconColor == collapsedIconColor
&& other.textColor == textColor
&& other.collapsedTextColor == collapsedTextColor;
&& other.collapsedTextColor == collapsedTextColor
&& other.shape == shape
&& other.collapsedShape == collapsedShape
&& other.clipBehavior == clipBehavior;
}
@override
......@@ -170,6 +196,9 @@ class ExpansionTileThemeData with Diagnosticable {
properties.add(ColorProperty('collapsedIconColor', collapsedIconColor, defaultValue: null));
properties.add(ColorProperty('textColor', textColor, defaultValue: null));
properties.add(ColorProperty('collapsedTextColor', collapsedTextColor, defaultValue: null));
properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
properties.add(DiagnosticsProperty<ShapeBorder>('collapsedShape', collapsedShape, defaultValue: null));
properties.add(DiagnosticsProperty<Clip>('clipBehavior', clipBehavior, defaultValue: null));
}
}
......
......@@ -54,6 +54,7 @@ void main() {
const Key defaultKey = PageStorageKey<String>('default');
final Key tileKey = UniqueKey();
const Clip clipBehavior = Clip.antiAlias;
await tester.pumpWidget(MaterialApp(
theme: ThemeData(
......@@ -69,6 +70,7 @@ void main() {
initiallyExpanded: true,
title: const Text('Expanded'),
backgroundColor: Colors.red,
clipBehavior: clipBehavior,
children: <Widget>[
ListTile(
key: tileKey,
......@@ -109,15 +111,18 @@ void main() {
expect(getHeight(topKey), getHeight(collapsedKey) - 2.0);
expect(getHeight(topKey), getHeight(defaultKey) - 2.0);
BoxDecoration expandedContainerDecoration = getContainer(expandedKey).decoration! as BoxDecoration;
// expansionTile should have Clip.antiAlias as clipBehavior
expect(getContainer(expandedKey).clipBehavior, clipBehavior);
ShapeDecoration expandedContainerDecoration = getContainer(expandedKey).decoration! as ShapeDecoration;
expect(expandedContainerDecoration.color, Colors.red);
expect(expandedContainerDecoration.border!.top.color, dividerColor);
expect(expandedContainerDecoration.border!.bottom.color, dividerColor);
expect((expandedContainerDecoration.shape as Border).top.color, dividerColor);
expect((expandedContainerDecoration.shape as Border).bottom.color, dividerColor);
BoxDecoration collapsedContainerDecoration = getContainer(collapsedKey).decoration! as BoxDecoration;
ShapeDecoration collapsedContainerDecoration = getContainer(collapsedKey).decoration! as ShapeDecoration;
expect(collapsedContainerDecoration.color, Colors.transparent);
expect(collapsedContainerDecoration.border!.top.color, Colors.transparent);
expect(collapsedContainerDecoration.border!.bottom.color, Colors.transparent);
expect((collapsedContainerDecoration.shape as Border).top.color, Colors.transparent);
expect((collapsedContainerDecoration.shape as Border).bottom.color, Colors.transparent);
await tester.tap(find.text('Expanded'));
await tester.tap(find.text('Collapsed'));
......@@ -127,11 +132,10 @@ void main() {
// Pump to the middle of the animation for expansion.
await tester.pump(const Duration(milliseconds: 100));
final BoxDecoration collapsingContainerDecoration = getContainer(collapsedKey).decoration! as BoxDecoration;
final ShapeDecoration collapsingContainerDecoration = getContainer(collapsedKey).decoration! as ShapeDecoration;
expect(collapsingContainerDecoration.color, Colors.transparent);
// Opacity should change but color component should remain the same.
expect(collapsingContainerDecoration.border!.top.color, const Color(0x15333333));
expect(collapsingContainerDecoration.border!.bottom.color, const Color(0x15333333));
expect((collapsingContainerDecoration.shape as Border).top.color, const Color(0x15222222));
expect((collapsingContainerDecoration.shape as Border).bottom.color, const Color(0x15222222));
// Pump all the way to the end now.
await tester.pump(const Duration(seconds: 1));
......@@ -141,16 +145,16 @@ void main() {
expect(getHeight(topKey), getHeight(defaultKey) - getHeight(tileKey) - 2.0);
// Expanded should be collapsed now.
expandedContainerDecoration = getContainer(expandedKey).decoration! as BoxDecoration;
expandedContainerDecoration = getContainer(expandedKey).decoration! as ShapeDecoration;
expect(expandedContainerDecoration.color, Colors.transparent);
expect(expandedContainerDecoration.border!.top.color, Colors.transparent);
expect(expandedContainerDecoration.border!.bottom.color, Colors.transparent);
expect((expandedContainerDecoration.shape as Border).top.color, Colors.transparent);
expect((expandedContainerDecoration.shape as Border).bottom.color, Colors.transparent);
// Collapsed should be expanded now.
collapsedContainerDecoration = getContainer(collapsedKey).decoration! as BoxDecoration;
collapsedContainerDecoration = getContainer(collapsedKey).decoration! as ShapeDecoration;
expect(collapsedContainerDecoration.color, Colors.transparent);
expect(collapsedContainerDecoration.border!.top.color, dividerColor);
expect(collapsedContainerDecoration.border!.bottom.color, dividerColor);
expect((collapsedContainerDecoration.shape as Border).top.color, dividerColor);
expect((collapsedContainerDecoration.shape as Border).bottom.color, dividerColor);
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
testWidgets('ExpansionTile Theme dependencies', (WidgetTester tester) async {
......@@ -500,22 +504,22 @@ void main() {
),
));
BoxDecoration boxDecoration = tester.firstWidget<Container>(find.descendant(
ShapeDecoration shapeDecoration = tester.firstWidget<Container>(find.descendant(
of: find.byKey(expansionTileKey),
matching: find.byType(Container),
)).decoration! as BoxDecoration;
)).decoration! as ShapeDecoration;
expect(boxDecoration.color, collapsedBackgroundColor);
expect(shapeDecoration.color, collapsedBackgroundColor);
await tester.tap(find.text('Title'));
await tester.pumpAndSettle();
boxDecoration = tester.firstWidget<Container>(find.descendant(
shapeDecoration = tester.firstWidget<Container>(find.descendant(
of: find.byKey(expansionTileKey),
matching: find.byType(Container),
)).decoration! as BoxDecoration;
)).decoration! as ShapeDecoration;
expect(boxDecoration.color, backgroundColor);
expect(shapeDecoration.color, backgroundColor);
});
testWidgets('ExpansionTile iconColor, textColor', (WidgetTester tester) async {
......@@ -555,6 +559,54 @@ void main() {
expect(getTextColor(), textColor);
});
testWidgets('ExpansionTile Border', (WidgetTester tester) async {
const Key expansionTileKey = PageStorageKey<String>('expansionTile');
const Border collapsedShape = Border(
top: BorderSide(color: Colors.blue),
bottom: BorderSide(color: Colors.green)
);
final Border shape = Border.all(color: Colors.red);
await tester.pumpWidget(MaterialApp(
home: Material(
child: SingleChildScrollView(
child: Column(
children: <Widget>[
ExpansionTile(
key: expansionTileKey,
title: const Text('ExpansionTile'),
collapsedShape: collapsedShape,
shape: shape,
children: const <Widget>[
ListTile(
title: Text('0'),
),
],
),
],
),
),
),
));
Container getContainer(Key key) => tester.firstWidget(find.descendant(
of: find.byKey(key),
matching: find.byType(Container),
));
// expansionTile should be Collapsed now.
ShapeDecoration expandedContainerDecoration = getContainer(expansionTileKey).decoration! as ShapeDecoration;
expect(expandedContainerDecoration.shape, collapsedShape);
await tester.tap(find.text('ExpansionTile'));
await tester.pumpAndSettle();
// expansionTile should be Expanded now.
expandedContainerDecoration = getContainer(expansionTileKey).decoration! as ShapeDecoration;
expect(expandedContainerDecoration.shape, shape);
});
testWidgets('ExpansionTile platform controlAffinity test', (WidgetTester tester) async {
await tester.pumpWidget(const MaterialApp(
home: Material(
......
......@@ -58,6 +58,9 @@ void main() {
expect(theme.collapsedIconColor, null);
expect(theme.textColor, null);
expect(theme.collapsedTextColor, null);
expect(theme.shape, null);
expect(theme.collapsedShape, null);
expect(theme.clipBehavior, null);
});
testWidgets('Default ExpansionTileThemeData debugFillProperties', (WidgetTester tester) async {
......@@ -84,6 +87,9 @@ void main() {
collapsedIconColor: Color(0xffdd0b1f),
textColor: Color(0xffffffff),
collapsedTextColor: Color(0xff522bab),
shape: Border(),
collapsedShape: Border(),
clipBehavior: Clip.antiAlias,
).debugFillProperties(builder);
final List<String> description = builder.properties
......@@ -101,6 +107,9 @@ void main() {
'collapsedIconColor: Color(0xffdd0b1f)',
'textColor: Color(0xffffffff)',
'collapsedTextColor: Color(0xff522bab)',
'shape: Border.all(BorderSide(width: 0.0, style: none))',
'collapsedShape: Border.all(BorderSide(width: 0.0, style: none))',
'clipBehavior: Clip.antiAlias',
]);
});
......@@ -114,6 +123,15 @@ void main() {
const Color collapsedIconColor = Colors.blue;
const Color textColor = Colors.black;
const Color collapsedTextColor = Colors.white;
const ShapeBorder shape = Border(
top: BorderSide(color: Colors.red),
bottom: BorderSide(color: Colors.red),
);
const ShapeBorder collapsedShape = Border(
top: BorderSide(color: Colors.green),
bottom: BorderSide(color: Colors.green),
);
const Clip clipBehavior = Clip.antiAlias;
await tester.pumpWidget(
MaterialApp(
......@@ -128,6 +146,9 @@ void main() {
collapsedIconColor: collapsedIconColor,
textColor: textColor,
collapsedTextColor: collapsedTextColor,
shape: shape,
collapsedShape: collapsedShape,
clipBehavior: clipBehavior,
),
),
home: Material(
......@@ -143,12 +164,21 @@ void main() {
),
);
final BoxDecoration boxDecoration = tester.firstWidget<Container>(find.descendant(
final ShapeDecoration shapeDecoration = tester.firstWidget<Container>(find.descendant(
of: find.byKey(tileKey),
matching: find.byType(Container),
)).decoration! as ShapeDecoration;
final Clip tileClipBehavior = tester.firstWidget<Container>(find.descendant(
of: find.byKey(tileKey),
matching: find.byType(Container),
)).decoration! as BoxDecoration;
)).clipBehavior;
// expansionTile should have Clip.antiAlias as clipBehavior
expect(tileClipBehavior, clipBehavior);
// Check the tile's collapsed background color when collapsedBackgroundColor is applied.
expect(boxDecoration.color, collapsedBackgroundColor);
expect(shapeDecoration.color, collapsedBackgroundColor);
final Rect titleRect = tester.getRect(find.text('Collapsed Tile'));
final Rect trailingRect = tester.getRect(find.byIcon(Icons.expand_more));
......@@ -171,6 +201,8 @@ void main() {
expect(getIconColor(), collapsedIconColor);
// Check the collapsed text color when textColor is applied.
expect(getTextColor(), collapsedTextColor);
// Check the collapsed ShapeBorder when shape is applied.
expect(shapeDecoration.shape, collapsedShape);
});
testWidgets('ExpansionTileTheme - expanded', (WidgetTester tester) async {
......@@ -183,6 +215,14 @@ void main() {
const Color collapsedIconColor = Colors.blue;
const Color textColor = Colors.black;
const Color collapsedTextColor = Colors.white;
const ShapeBorder shape = Border(
top: BorderSide(color: Colors.red),
bottom: BorderSide(color: Colors.red),
);
const ShapeBorder collapsedShape = Border(
top: BorderSide(color: Colors.green),
bottom: BorderSide(color: Colors.green),
);
await tester.pumpWidget(
MaterialApp(
......@@ -197,6 +237,8 @@ void main() {
collapsedIconColor: collapsedIconColor,
textColor: textColor,
collapsedTextColor: collapsedTextColor,
shape: shape,
collapsedShape: collapsedShape,
),
),
home: Material(
......@@ -213,12 +255,12 @@ void main() {
),
);
final BoxDecoration boxDecoration = tester.firstWidget<Container>(find.descendant(
final ShapeDecoration shapeDecoration = tester.firstWidget<Container>(find.descendant(
of: find.byKey(tileKey),
matching: find.byType(Container),
)).decoration! as BoxDecoration;
)).decoration! as ShapeDecoration;
// Check the tile's background color when backgroundColor is applied.
expect(boxDecoration.color, backgroundColor);
expect(shapeDecoration.color, backgroundColor);
final Rect titleRect = tester.getRect(find.text('Expanded Tile'));
final Rect trailingRect = tester.getRect(find.byIcon(Icons.expand_more));
......@@ -241,6 +283,8 @@ void main() {
expect(getIconColor(), iconColor);
// Check the expanded text color when textColor is applied.
expect(getTextColor(), textColor);
// Check the expanded ShapeBorder when shape is applied.
expect(shapeDecoration.shape, collapsedShape);
// Check the child position when expandedAlignment is applied.
final Rect childRect = tester.getRect(find.text('Tile 1'));
......
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