// 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 'dart:math' as math; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'constants.dart'; import 'debug.dart'; import 'icons.dart'; import 'ink_well.dart'; import 'material.dart'; import 'theme.dart'; import 'theme_data.dart'; import 'tooltip.dart'; // Minimum logical pixel size of the IconButton. // See: <https://material.io/design/usability/accessibility.html#layout-typography>. const double _kMinButtonSize = kMinInteractiveDimension; /// A material design icon button. /// /// An icon button is a picture printed on a [Material] widget that reacts to /// touches by filling with color (ink). /// /// Icon buttons are commonly used in the [AppBar.actions] field, but they can /// be used in many other places as well. /// /// If the [onPressed] callback is null, then the button will be disabled and /// will not react to touch. /// /// Requires one of its ancestors to be a [Material] widget. /// /// The hit region of an icon button will, if possible, be at least /// kMinInteractiveDimension pixels in size, regardless of the actual /// [iconSize], to satisfy the [touch target size](https://material.io/design/layout/spacing-methods.html#touch-targets) /// requirements in the Material Design specification. The [alignment] controls /// how the icon itself is positioned within the hit region. /// /// {@tool dartpad --template=stateful_widget_scaffold_center} /// /// This sample shows an `IconButton` that uses the Material icon "volume_up" to /// increase the volume. /// /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/icon_button.png) /// /// ```dart preamble /// double _volume = 0.0; /// ``` /// /// ```dart /// Widget build(BuildContext context) { /// return Column( /// mainAxisSize: MainAxisSize.min, /// children: <Widget>[ /// IconButton( /// icon: Icon(Icons.volume_up), /// tooltip: 'Increase volume by 10', /// onPressed: () { /// setState(() { /// _volume += 10; /// }); /// }, /// ), /// Text('Volume : $_volume') /// ], /// ); /// } /// ``` /// {@end-tool} /// /// ### Adding a filled background /// /// Icon buttons don't support specifying a background color or other /// background decoration because typically the icon is just displayed /// on top of the parent widget's background. Icon buttons that appear /// in [AppBar.actions] are an example of this. /// /// It's easy enough to create an icon button with a filled background /// using the [Ink] widget. The [Ink] widget renders a decoration on /// the underlying [Material] along with the splash and highlight /// [InkResponse] contributed by descendant widgets. /// /// {@tool dartpad --template=stateless_widget_scaffold} /// /// In this sample the icon button's background color is defined with an [Ink] /// widget whose child is an [IconButton]. The icon button's filled background /// is a light shade of blue, it's a filled circle, and it's as big as the /// button is. /// /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/icon_button_background.png) /// /// ```dart /// Widget build(BuildContext context) { /// return Material( /// color: Colors.white, /// child: Center( /// child: Ink( /// decoration: const ShapeDecoration( /// color: Colors.lightBlue, /// shape: CircleBorder(), /// ), /// child: IconButton( /// icon: Icon(Icons.android), /// color: Colors.white, /// onPressed: () {}, /// ), /// ), /// ), /// ); /// } /// ``` /// {@end-tool} /// /// See also: /// /// * [Icons], a library of predefined icons. /// * [BackButton], an icon button for a "back" affordance which adapts to the /// current platform's conventions. /// * [CloseButton], an icon button for closing pages. /// * [AppBar], to show a toolbar at the top of an application. /// * [RaisedButton] and [FlatButton], for buttons with text in them. /// * [InkResponse] and [InkWell], for the ink splash effect itself. class IconButton extends StatelessWidget { /// Creates an icon button. /// /// Icon buttons are commonly used in the [AppBar.actions] field, but they can /// be used in many other places as well. /// /// Requires one of its ancestors to be a [Material] widget. /// /// The [iconSize], [padding], [autofocus], and [alignment] arguments must not /// be null (though they each have default values). /// /// The [icon] argument must be specified, and is typically either an [Icon] /// or an [ImageIcon]. const IconButton({ Key key, this.iconSize = 24.0, this.visualDensity, this.padding = const EdgeInsets.all(8.0), this.alignment = Alignment.center, @required this.icon, this.color, this.focusColor, this.hoverColor, this.highlightColor, this.splashColor, this.disabledColor, @required this.onPressed, this.focusNode, this.autofocus = false, this.tooltip, this.enableFeedback = true, this.constraints, }) : assert(iconSize != null), assert(padding != null), assert(alignment != null), assert(autofocus != null), assert(icon != null), super(key: key); /// The size of the icon inside the button. /// /// This property must not be null. It defaults to 24.0. /// /// The size given here is passed down to the widget in the [icon] property /// via an [IconTheme]. Setting the size here instead of in, for example, the /// [Icon.size] property allows the [IconButton] to size the splash area to /// fit the [Icon]. If you were to set the size of the [Icon] using /// [Icon.size] instead, then the [IconButton] would default to 24.0 and then /// the [Icon] itself would likely get clipped. final double iconSize; /// Defines how compact the icon button's layout will be. /// /// {@macro flutter.material.themedata.visualDensity} /// /// See also: /// /// * [ThemeData.visualDensity], which specifies the [density] for all widgets /// within a [Theme]. final VisualDensity visualDensity; /// The padding around the button's icon. The entire padded icon will react /// to input gestures. /// /// This property must not be null. It defaults to 8.0 padding on all sides. final EdgeInsetsGeometry padding; /// Defines how the icon is positioned within the IconButton. /// /// This property must not be null. It defaults to [Alignment.center]. /// /// See also: /// /// * [Alignment], a class with convenient constants typically used to /// specify an [AlignmentGeometry]. /// * [AlignmentDirectional], like [Alignment] for specifying alignments /// relative to text direction. final AlignmentGeometry alignment; /// The icon to display inside the button. /// /// The [Icon.size] and [Icon.color] of the icon is configured automatically /// based on the [iconSize] and [color] properties of _this_ widget using an /// [IconTheme] and therefore should not be explicitly given in the icon /// widget. /// /// This property must not be null. /// /// See [Icon], [ImageIcon]. final Widget icon; /// The color for the button's icon when it has the input focus. /// /// Defaults to [ThemeData.focusColor] of the ambient theme. final Color focusColor; /// The color for the button's icon when a pointer is hovering over it. /// /// Defaults to [ThemeData.hoverColor] of the ambient theme. final Color hoverColor; /// The color to use for the icon inside the button, if the icon is enabled. /// Defaults to leaving this up to the [icon] widget. /// /// The icon is enabled if [onPressed] is not null. /// /// ```dart /// IconButton( /// color: Colors.blue, /// onPressed: _handleTap, /// icon: Icons.widgets, /// ) /// ``` final Color color; /// The primary color of the button when the button is in the down (pressed) state. /// The splash is represented as a circular overlay that appears above the /// [highlightColor] overlay. The splash overlay has a center point that matches /// the hit point of the user touch event. The splash overlay will expand to /// fill the button area if the touch is held for long enough time. If the splash /// color has transparency then the highlight and button color will show through. /// /// Defaults to the Theme's splash color, [ThemeData.splashColor]. final Color splashColor; /// The secondary color of the button when the button is in the down (pressed) /// state. The highlight color is represented as a solid color that is overlaid over the /// button color (if any). If the highlight color has transparency, the button color /// will show through. The highlight fades in quickly as the button is held down. /// /// Defaults to the Theme's highlight color, [ThemeData.highlightColor]. final Color highlightColor; /// The color to use for the icon inside the button, if the icon is disabled. /// Defaults to the [ThemeData.disabledColor] of the current [Theme]. /// /// The icon is disabled if [onPressed] is null. final Color disabledColor; /// The callback that is called when the button is tapped or otherwise activated. /// /// If this is set to null, the button will be disabled. final VoidCallback onPressed; /// {@macro flutter.widgets.Focus.focusNode} final FocusNode focusNode; /// {@macro flutter.widgets.Focus.autofocus} final bool autofocus; /// Text that describes the action that will occur when the button is pressed. /// /// This text is displayed when the user long-presses on the button and is /// 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; /// Optional size constraints for the button. /// /// When unspecified, defaults to: /// ```dart /// const BoxConstraints( /// minWidth: kMinInteractiveDimension, /// minHeight: kMinInteractiveDimension, /// ) /// ``` /// where [kMinInteractiveDimension] is 48.0, and then with visual density /// applied. /// /// The default constraints ensure that the button is accessible. /// Specifying this parameter enables creation of buttons smaller than /// the minimum size, but it is not recommended. /// /// The visual density uses the [visualDensity] parameter if specified, /// and `Theme.of(context).visualDensity` otherwise. final BoxConstraints constraints; @override Widget build(BuildContext context) { assert(debugCheckHasMaterial(context)); final ThemeData theme = Theme.of(context); Color currentColor; if (onPressed != null) currentColor = color; else currentColor = disabledColor ?? theme.disabledColor; final VisualDensity effectiveVisualDensity = visualDensity ?? theme.visualDensity; final BoxConstraints unadjustedConstraints = constraints ?? const BoxConstraints( minWidth: _kMinButtonSize, minHeight: _kMinButtonSize, ); final BoxConstraints adjustedConstraints = effectiveVisualDensity.effectiveConstraints(unadjustedConstraints); Widget result = ConstrainedBox( constraints: adjustedConstraints, child: Padding( padding: padding, child: SizedBox( height: iconSize, width: iconSize, child: Align( alignment: alignment, child: IconTheme.merge( data: IconThemeData( size: iconSize, color: currentColor, ), child: icon, ), ), ), ), ); if (tooltip != null) { result = Tooltip( message: tooltip, child: result, ); } return Semantics( button: true, enabled: onPressed != null, child: InkResponse( focusNode: focusNode, autofocus: autofocus, canRequestFocus: onPressed != null, onTap: onPressed, enableFeedback: enableFeedback, child: result, focusColor: focusColor ?? Theme.of(context).focusColor, hoverColor: hoverColor ?? Theme.of(context).hoverColor, highlightColor: highlightColor ?? Theme.of(context).highlightColor, splashColor: splashColor ?? Theme.of(context).splashColor, radius: math.max( Material.defaultSplashRadius, (iconSize + math.min(padding.horizontal, padding.vertical)) * 0.7, // x 0.5 for diameter -> radius and + 40% overflow derived from other Material apps. ), ), ); } @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(DiagnosticsProperty<Widget>('icon', icon, showName: false)); properties.add(StringProperty('tooltip', tooltip, defaultValue: null, quoted: false)); properties.add(ObjectFlagProperty<VoidCallback>('onPressed', onPressed, ifNull: 'disabled')); properties.add(ColorProperty('color', color, defaultValue: null)); properties.add(ColorProperty('disabledColor', disabledColor, defaultValue: null)); properties.add(ColorProperty('focusColor', focusColor, defaultValue: null)); properties.add(ColorProperty('hoverColor', hoverColor, defaultValue: null)); properties.add(ColorProperty('highlightColor', highlightColor, defaultValue: null)); properties.add(ColorProperty('splashColor', splashColor, defaultValue: null)); properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null)); properties.add(DiagnosticsProperty<FocusNode>('focusNode', focusNode, defaultValue: null)); } }