From 51168862fb920aded9587eb0da70296ecd5f47ff Mon Sep 17 00:00:00 2001 From: Benji Farquhar Date: Fri, 31 May 2024 13:44:32 +1200 Subject: [PATCH] Add support for ExpansionPanel custom splash color (#147126) Relates to https://github.com/flutter/flutter/pull/147098 and https://github.com/flutter/flutter/issues/147097 Aside from fixing the expland/collapse icon color in the other PR, I noticed the splash color on both the icon button and the full expansion panel (if ExpansionPanel.canTapOnHeader is set to true) is just `Theme.of(context).splashColor` on the `InkWell` and `Theme.of(context).highlightColor` on the `IconButton` which may not suit the color scheme of the `ExpansionPanel`, so I have added a custom field `splashColor`, which will effect both the `IconButton` and the full panel `Inkwell`. --- .../flutter/lib/src/material/expand_icon.dart | 18 +++++ .../lib/src/material/expansion_panel.dart | 31 +++++++++ .../test/material/expansion_panel_test.dart | 68 +++++++++++++++++++ 3 files changed, 117 insertions(+) diff --git a/packages/flutter/lib/src/material/expand_icon.dart b/packages/flutter/lib/src/material/expand_icon.dart index 6a7d603aa35..50e7910f342 100644 --- a/packages/flutter/lib/src/material/expand_icon.dart +++ b/packages/flutter/lib/src/material/expand_icon.dart @@ -39,6 +39,8 @@ class ExpandIcon extends StatefulWidget { this.color, this.disabledColor, this.expandedColor, + this.splashColor, + this.highlightColor, }); /// Whether the icon is in an expanded state. @@ -94,6 +96,20 @@ class ExpandIcon extends StatefulWidget { /// and for [dark theme](https://material.io/design/color/dark-theme.html#ui-application) final Color? expandedColor; + /// Defines the splash color of the IconButton. + /// + /// If [ThemeData.useMaterial3] is true, this field will be ignored, + /// as [IconButton.splashColor] will be ignored, and you should use + /// [highlightColor] instead. + /// + /// Defaults to [ThemeData.splashColor]. + final Color? splashColor; + + /// Defines the highlight color of the IconButton. + /// + /// Defaults to [ThemeData.highlightColor]. + final Color? highlightColor; + @override State createState() => _ExpandIconState(); } @@ -170,6 +186,8 @@ class _ExpandIconState extends State with SingleTickerProviderStateM child: IconButton( padding: widget.padding, iconSize: widget.size, + highlightColor: widget.highlightColor, + splashColor: widget.splashColor, color: _iconColor, disabledColor: widget.disabledColor, onPressed: widget.onPressed == null ? null : _handlePressed, diff --git a/packages/flutter/lib/src/material/expansion_panel.dart b/packages/flutter/lib/src/material/expansion_panel.dart index 068b5231736..ff1bbeb064c 100644 --- a/packages/flutter/lib/src/material/expansion_panel.dart +++ b/packages/flutter/lib/src/material/expansion_panel.dart @@ -6,6 +6,7 @@ import 'package:flutter/widgets.dart'; import 'constants.dart'; import 'expand_icon.dart'; +import 'icon_button.dart'; import 'ink_well.dart'; import 'material_localizations.dart'; import 'mergeable_material.dart'; @@ -80,6 +81,8 @@ class ExpansionPanel { this.isExpanded = false, this.canTapOnHeader = false, this.backgroundColor, + this.splashColor, + this.highlightColor, }); /// The widget builder that builds the expansion panels' header. @@ -95,6 +98,28 @@ class ExpansionPanel { /// Defaults to false. final bool isExpanded; + /// Defines the splash color of the panel if [canTapOnHeader] is true, + /// or the splash color of the expand/collapse IconButton if [canTapOnHeader] + /// is false. + /// + /// If [canTapOnHeader] is false, and [ThemeData.useMaterial3] is + /// true, this field will be ignored, as [IconButton.splashColor] + /// will be ignored, and you should use [highlightColor] instead. + /// + /// If this is null, then the icon button will use its default splash color + /// [ThemeData.splashColor], and the panel will use its default splash color + /// [ThemeData.splashColor] (if [canTapOnHeader] is true). + final Color? splashColor; + + /// Defines the highlight color of the panel if [canTapOnHeader] is true, or + /// the highlight color of the expand/collapse IconButton if [canTapOnHeader] + /// is false. + /// + /// If this is null, then the icon button will use its default highlight color + /// [ThemeData.highlightColor], and the panel will use its default highlight + /// color [ThemeData.highlightColor] (if [canTapOnHeader] is true). + final Color? highlightColor; + /// Whether tapping on the panel's header will expand/collapse it. /// /// Defaults to false. @@ -125,6 +150,8 @@ class ExpansionPanelRadio extends ExpansionPanel { required super.body, super.canTapOnHeader, super.backgroundColor, + super.splashColor, + super.highlightColor, }); /// The value that uniquely identifies a radio panel so that the currently @@ -366,6 +393,8 @@ class _ExpansionPanelListState extends State { disabledColor: child.canTapOnHeader ? widget.expandIconColor : null, isExpanded: _isChildExpanded(index), padding: _kExpandIconPadding, + splashColor: child.splashColor, + highlightColor: child.highlightColor, onPressed: !child.canTapOnHeader ? (bool isExpanded) => _handlePressed(isExpanded, index) : null, @@ -399,6 +428,8 @@ class _ExpansionPanelListState extends State { if (child.canTapOnHeader) { header = MergeSemantics( child: InkWell( + splashColor: child.splashColor, + highlightColor: child.highlightColor, onTap: () => _handlePressed(_isChildExpanded(index), index), child: header, ), diff --git a/packages/flutter/test/material/expansion_panel_test.dart b/packages/flutter/test/material/expansion_panel_test.dart index d8bc891366d..feab74d92d2 100644 --- a/packages/flutter/test/material/expansion_panel_test.dart +++ b/packages/flutter/test/material/expansion_panel_test.dart @@ -1948,6 +1948,74 @@ void main() { } }); + testWidgets('Ensure IconButton splashColor and highlightColor are correctly set when canTapOnHeader is false', (WidgetTester tester) async { + const Color expectedSplashColor = Colors.green; + const Color expectedHighlightColor = Colors.yellow; + + await tester.pumpWidget(MaterialApp( + home: SingleChildScrollView( + child: ExpansionPanelList( + children: [ + ExpansionPanel( + headerBuilder: (BuildContext context, bool isExpanded) { + return const ListTile(title: Text('Panel 1')); + }, + body: const ListTile(title: Text('Content for Panel 1')), + splashColor: expectedSplashColor, + highlightColor: expectedHighlightColor, + ), + ], + ), + ), + )); + + await tester.tap(find.text('Panel 1')); + await tester.pumpAndSettle(); + + final IconButton iconButton = tester.widget(find.byType(IconButton).first); + expect(iconButton.splashColor, expectedSplashColor); + expect(iconButton.highlightColor, expectedHighlightColor); + }); + + testWidgets('Ensure InkWell splashColor and highlightColor are correctly set when canTapOnHeader is true', (WidgetTester tester) async { + const Color expectedSplashColor = Colors.green; + const Color expectedHighlightColor = Colors.yellow; + + await tester.pumpWidget(MaterialApp( + home: SingleChildScrollView( + child: ExpansionPanelList( + children: [ + ExpansionPanel( + canTapOnHeader: true, + headerBuilder: (BuildContext context, bool isExpanded) { + return Container( + padding: const EdgeInsets.all(16), + alignment: Alignment.centerLeft, + child: const Text('Panel 1'), + ); + }, + body: const ListTile(title: Text('Content for Panel 1')), + splashColor: expectedSplashColor, + highlightColor: expectedHighlightColor, + ), + ], + ), + ), + )); + + await tester.pumpAndSettle(); + + final Finder inkWellFinder = find.descendant( + of: find.byType(ExpansionPanelList), + matching: find.byWidgetPredicate((Widget widget) => + widget is InkWell && widget.onTap != null) + ); + + final InkWell inkWell = tester.widget(inkWellFinder); + expect(inkWell.splashColor, expectedSplashColor); + expect(inkWell.highlightColor, expectedHighlightColor); + }); + testWidgets('ExpandIcon.disabledColor uses expandIconColor color when canTapOnHeader is true', (WidgetTester tester) async { const Color expandIconColor = Color(0xff0000ff);