diff --git a/packages/flutter/lib/src/material/button.dart b/packages/flutter/lib/src/material/button.dart index 4b4c9cc5c5..daf89a04c4 100644 --- a/packages/flutter/lib/src/material/button.dart +++ b/packages/flutter/lib/src/material/button.dart @@ -60,6 +60,7 @@ class RawMaterialButton extends StatefulWidget { this.autofocus = false, MaterialTapTargetSize materialTapTargetSize, this.child, + this.enableFeedback = true, }) : materialTapTargetSize = materialTapTargetSize ?? MaterialTapTargetSize.padded, assert(shape != null), assert(elevation != null && elevation >= 0.0), @@ -259,6 +260,16 @@ class RawMaterialButton extends StatefulWidget { /// Defaults to [Clip.none], and must not be null. final Clip clipBehavior; + /// Whether detected gestures should provide acoustic and/or haptic feedback. + /// + /// For example, on Android a tap will produce a clicking sound and a + /// long-press will produce a short vibration, when feedback is enabled. + /// + /// See also: + /// + /// * [Feedback] for providing platform-specific feedback to certain actions. + final bool enableFeedback; + @override _RawMaterialButtonState createState() => _RawMaterialButtonState(); } @@ -367,6 +378,7 @@ class _RawMaterialButtonState extends State { onHover: _handleHoveredChanged, onTap: widget.onPressed, onLongPress: widget.onLongPress, + enableFeedback: widget.enableFeedback, customBorder: effectiveShape, child: IconTheme.merge( data: IconThemeData(color: effectiveTextColor), diff --git a/packages/flutter/lib/src/material/icon_button.dart b/packages/flutter/lib/src/material/icon_button.dart index 6fe788db1d..ba9046831c 100644 --- a/packages/flutter/lib/src/material/icon_button.dart +++ b/packages/flutter/lib/src/material/icon_button.dart @@ -151,6 +151,7 @@ class IconButton extends StatelessWidget { this.focusNode, this.autofocus = false, this.tooltip, + this.enableFeedback = true, }) : assert(iconSize != null), assert(padding != null), assert(alignment != null), @@ -269,6 +270,16 @@ class IconButton extends StatelessWidget { /// used for accessibility. final String tooltip; + /// Whether detected gestures should provide acoustic and/or haptic feedback. + /// + /// For example, on Android a tap will produce a clicking sound and a + /// long-press will produce a short vibration, when feedback is enabled. + /// + /// See also: + /// + /// * [Feedback] for providing platform-specific feedback to certain actions. + final bool enableFeedback; + @override Widget build(BuildContext context) { assert(debugCheckHasMaterial(context)); @@ -314,6 +325,7 @@ class IconButton extends StatelessWidget { autofocus: autofocus, canRequestFocus: onPressed != null, onTap: onPressed, + enableFeedback: enableFeedback, child: result, focusColor: focusColor ?? Theme.of(context).focusColor, hoverColor: hoverColor ?? Theme.of(context).hoverColor, diff --git a/packages/flutter/lib/src/material/material_button.dart b/packages/flutter/lib/src/material/material_button.dart index 5b12358471..20a2b04239 100644 --- a/packages/flutter/lib/src/material/material_button.dart +++ b/packages/flutter/lib/src/material/material_button.dart @@ -77,6 +77,7 @@ class MaterialButton extends StatelessWidget { this.animationDuration, this.minWidth, this.height, + this.enableFeedback = true, this.child, }) : assert(clipBehavior != null), assert(autofocus != null), @@ -355,6 +356,16 @@ class MaterialButton extends StatelessWidget { /// Defaults to the value from the current [ButtonTheme]. final double height; + /// Whether detected gestures should provide acoustic and/or haptic feedback. + /// + /// For example, on Android a tap will produce a clicking sound and a + /// long-press will produce a short vibration, when feedback is enabled. + /// + /// See also: + /// + /// * [Feedback] for providing platform-specific feedback to certain actions. + final bool enableFeedback; + @override Widget build(BuildContext context) { final ThemeData theme = Theme.of(context); @@ -363,6 +374,7 @@ class MaterialButton extends StatelessWidget { return RawMaterialButton( onPressed: onPressed, onLongPress: onLongPress, + enableFeedback: enableFeedback, onHighlightChanged: onHighlightChanged, fillColor: buttonTheme.getFillColor(this), textStyle: theme.textTheme.button.copyWith(color: buttonTheme.getTextColor(this)), diff --git a/packages/flutter/test/material/icon_button_test.dart b/packages/flutter/test/material/icon_button_test.dart index 247441fe57..8cf16f3015 100644 --- a/packages/flutter/test/material/icon_button_test.dart +++ b/packages/flutter/test/material/icon_button_test.dart @@ -10,6 +10,7 @@ import 'package:flutter_test/flutter_test.dart'; import '../rendering/mock_canvas.dart'; import '../widgets/semantics_tester.dart'; +import 'feedback_tester.dart'; class MockOnPressedFunction implements Function { int called = 0; @@ -398,6 +399,74 @@ void main() { expect(focusNode1.hasPrimaryFocus, isTrue); expect(focusNode2.hasPrimaryFocus, isFalse); }); + + group('feedback', () { + FeedbackTester feedback; + + setUp(() { + feedback = FeedbackTester(); + }); + + tearDown(() { + feedback?.dispose(); + }); + + testWidgets('IconButton with disabled feedback', (WidgetTester tester) async { + await tester.pumpWidget(Material( + child: Directionality( + textDirection: TextDirection.ltr, + child: Center( + child: IconButton( + onPressed: () {}, + enableFeedback: false, + icon: const Icon(Icons.link), + ), + ), + ), + )); + await tester.tap(find.byType(IconButton), pointer: 1); + await tester.pump(const Duration(seconds: 1)); + expect(feedback.clickSoundCount, 0); + expect(feedback.hapticCount, 0); + }); + + testWidgets('IconButton with enabled feedback', (WidgetTester tester) async { + await tester.pumpWidget(Material( + child: Directionality( + textDirection: TextDirection.ltr, + child: Center( + child: IconButton( + onPressed: () {}, + enableFeedback: true, + icon: const Icon(Icons.link), + ), + ), + ), + )); + await tester.tap(find.byType(IconButton), pointer: 1); + await tester.pump(const Duration(seconds: 1)); + expect(feedback.clickSoundCount, 1); + expect(feedback.hapticCount, 0); + }); + + testWidgets('IconButton with enabled feedback by default', (WidgetTester tester) async { + await tester.pumpWidget(Material( + child: Directionality( + textDirection: TextDirection.ltr, + child: Center( + child: IconButton( + onPressed: () {}, + icon: const Icon(Icons.link), + ), + ), + ), + )); + await tester.tap(find.byType(IconButton), pointer: 1); + await tester.pump(const Duration(seconds: 1)); + expect(feedback.clickSoundCount, 1); + expect(feedback.hapticCount, 0); + }); + }); } Widget wrap({ Widget child }) {