diff --git a/packages/flutter/lib/src/cupertino/icon_theme_data.dart b/packages/flutter/lib/src/cupertino/icon_theme_data.dart index 078c13e40c5..68c28469a91 100644 --- a/packages/flutter/lib/src/cupertino/icon_theme_data.dart +++ b/packages/flutter/lib/src/cupertino/icon_theme_data.dart @@ -29,7 +29,7 @@ class CupertinoIconThemeData extends IconThemeData with Diagnosticable { /// Creates a copy of this icon theme but with the given fields replaced with /// the new values. @override - CupertinoIconThemeData copyWith({ Color? color, double? opacity, double? size }) { + CupertinoIconThemeData copyWith({ Color? color, double? opacity, double? size, double? splashRadius }) { return CupertinoIconThemeData( color: color ?? this.color, opacity: opacity ?? this.opacity, diff --git a/packages/flutter/lib/src/material/icon_button.dart b/packages/flutter/lib/src/material/icon_button.dart index 15151495322..afe7ee8e71d 100644 --- a/packages/flutter/lib/src/material/icon_button.dart +++ b/packages/flutter/lib/src/material/icon_button.dart @@ -306,6 +306,7 @@ class IconButton extends StatelessWidget { Widget build(BuildContext context) { assert(debugCheckHasMaterial(context)); final ThemeData theme = Theme.of(context); + final IconThemeData iconTheme = IconTheme.of(context); Color? currentColor; if (onPressed != null) currentColor = color; @@ -319,7 +320,7 @@ class IconButton extends StatelessWidget { minHeight: _kMinButtonSize, ); final BoxConstraints adjustedConstraints = effectiveVisualDensity.effectiveConstraints(unadjustedConstraints); - final double effectiveIconSize = iconSize ?? IconTheme.of(context).size ?? 24.0; + final double effectiveIconSize = iconSize ?? iconTheme.size ?? 24.0; Widget result = ConstrainedBox( constraints: adjustedConstraints, @@ -363,11 +364,12 @@ class IconButton extends StatelessWidget { hoverColor: hoverColor ?? theme.hoverColor, highlightColor: highlightColor ?? theme.highlightColor, splashColor: splashColor ?? theme.splashColor, - radius: splashRadius ?? math.max( - Material.defaultSplashRadius, - (effectiveIconSize + math.min(padding.horizontal, padding.vertical)) * 0.7, - // x 0.5 for diameter -> radius and + 40% overflow derived from other Material apps. - ), + radius: splashRadius ?? iconTheme.splashRadius ?? + math.max( + Material.defaultSplashRadius, + (effectiveIconSize + math.min(padding.horizontal, padding.vertical)) * 0.7, + // x 0.5 for diameter -> radius and + 40% overflow derived from other Material apps. + ), child: result, ), ); diff --git a/packages/flutter/lib/src/widgets/icon_theme_data.dart b/packages/flutter/lib/src/widgets/icon_theme_data.dart index 1626b1affab..846b2d72b49 100644 --- a/packages/flutter/lib/src/widgets/icon_theme_data.dart +++ b/packages/flutter/lib/src/widgets/icon_theme_data.dart @@ -23,7 +23,7 @@ class IconThemeData with Diagnosticable { /// /// The opacity applies to both explicit and default icon colors. The value /// is clamped between 0.0 and 1.0. - const IconThemeData({this.color, double? opacity, this.size}) : _opacity = opacity; + const IconThemeData({this.color, double? opacity, this.size, this.splashRadius}) : _opacity = opacity; /// Creates an icon theme with some reasonable default values. /// @@ -31,15 +31,17 @@ class IconThemeData with Diagnosticable { const IconThemeData.fallback() : color = const Color(0xFF000000), _opacity = 1.0, - size = 24.0; + size = 24.0, + splashRadius = null; /// Creates a copy of this icon theme but with the given fields replaced with /// the new values. - IconThemeData copyWith({ Color? color, double? opacity, double? size }) { + IconThemeData copyWith({ Color? color, double? opacity, double? size, double? splashRadius }) { return IconThemeData( color: color ?? this.color, opacity: opacity ?? this.opacity, size: size ?? this.size, + splashRadius: splashRadius ?? this.splashRadius, ); } @@ -53,6 +55,7 @@ class IconThemeData with Diagnosticable { color: other.color, opacity: other.opacity, size: other.size, + splashRadius: other.splashRadius, ); } @@ -87,6 +90,9 @@ class IconThemeData with Diagnosticable { /// The default size for icons. final double? size; + /// The default splash radius for [IconButton]s. + final double? splashRadius; + /// Linearly interpolate between two icon theme data objects. /// /// {@macro dart.ui.shadow.lerp} @@ -96,6 +102,7 @@ class IconThemeData with Diagnosticable { color: Color.lerp(a?.color, b?.color, t), opacity: ui.lerpDouble(a?.opacity, b?.opacity, t), size: ui.lerpDouble(a?.size, b?.size, t), + splashRadius: ui.lerpDouble(a?.splashRadius, b?.splashRadius, t), ); } @@ -106,11 +113,12 @@ class IconThemeData with Diagnosticable { return other is IconThemeData && other.color == color && other.opacity == opacity - && other.size == size; + && other.size == size + && other.splashRadius == splashRadius; } @override - int get hashCode => hashValues(color, opacity, size); + int get hashCode => hashValues(color, opacity, size, splashRadius); @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { @@ -118,5 +126,6 @@ class IconThemeData with Diagnosticable { properties.add(ColorProperty('color', color, defaultValue: null)); properties.add(DoubleProperty('opacity', opacity, defaultValue: null)); properties.add(DoubleProperty('size', size, defaultValue: null)); + properties.add(DoubleProperty('splashRadius', splashRadius, defaultValue: null)); } } diff --git a/packages/flutter/test/material/icon_button_test.dart b/packages/flutter/test/material/icon_button_test.dart index 1ad4a4da539..97e4b3fd800 100644 --- a/packages/flutter/test/material/icon_button_test.dart +++ b/packages/flutter/test/material/icon_button_test.dart @@ -480,6 +480,40 @@ void main() { await gesture.up(); }); + testWidgets('IconButton can inherit splashRadius from IconTheme', (WidgetTester tester) async { + const double splashRadius = 30.0; + await tester.pumpWidget( + MaterialApp( + home: Material( + child: Center( + child: IconTheme( + data: const IconThemeData( + splashRadius: splashRadius, + ), + child: IconButton( + icon: const Icon(Icons.android), + onPressed: () { /* enable the button */ }, + ), + ), + ), + ), + ), + ); + + final Offset center = tester.getCenter(find.byType(IconButton)); + final TestGesture gesture = await tester.startGesture(center); + await tester.pump(); // Start gesture. + await tester.pump(const Duration(milliseconds: 1000)); // Wait for splash to be well under way. + + expect( + Material.of(tester.element(find.byType(IconButton))), + paints + ..circle(radius: splashRadius), + ); + + await gesture.up(); + }); + testWidgets('IconButton Semantics (enabled)', (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); diff --git a/packages/flutter/test/widgets/icon_theme_data_test.dart b/packages/flutter/test/widgets/icon_theme_data_test.dart index 83e48c6b0a5..bed49cead4d 100644 --- a/packages/flutter/test/widgets/icon_theme_data_test.dart +++ b/packages/flutter/test/widgets/icon_theme_data_test.dart @@ -7,7 +7,7 @@ import 'package:flutter_test/flutter_test.dart'; void main() { test('IconThemeData control test', () { - const IconThemeData data = IconThemeData(color: Color(0xAAAAAAAA), opacity: 0.5, size: 16.0); + const IconThemeData data = IconThemeData(color: Color(0xAAAAAAAA), opacity: 0.5, size: 16.0, splashRadius: 30.0); expect(data, hasOneLineDescription); expect(data, equals(data.copyWith())); @@ -17,24 +17,27 @@ void main() { expect(lerped.color, const Color(0xBF7F7F7F)); expect(lerped.opacity, 0.625); expect(lerped.size, 18.0); + expect(lerped.splashRadius, 22.5); }); test('IconThemeData lerp with first null', () { - const IconThemeData data = IconThemeData(color: Color(0xFFFFFFFF), opacity: 1.0, size: 16.0); + const IconThemeData data = IconThemeData(color: Color(0xFFFFFFFF), opacity: 1.0, size: 16.0, splashRadius: 30.0); final IconThemeData lerped = IconThemeData.lerp(null, data, 0.25); expect(lerped.color, const Color(0x40FFFFFF)); expect(lerped.opacity, 0.25); expect(lerped.size, 4.0); + expect(lerped.splashRadius, 7.5); }); test('IconThemeData lerp with second null', () { - const IconThemeData data = IconThemeData(color: Color(0xFFFFFFFF), opacity: 1.0, size: 16.0); + const IconThemeData data = IconThemeData(color: Color(0xFFFFFFFF), opacity: 1.0, size: 16.0, splashRadius: 30.0); final IconThemeData lerped = IconThemeData.lerp(data, null, 0.25); expect(lerped.color, const Color(0xBFFFFFFF)); expect(lerped.opacity, 0.75); expect(lerped.size, 12.0); + expect(lerped.splashRadius, 22.5); }); test('IconThemeData lerp with both null', () { @@ -42,5 +45,6 @@ void main() { expect(lerped.color, null); expect(lerped.opacity, null); expect(lerped.size, null); + expect(lerped.splashRadius, null); }); }