icon_button.dart 13 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
import 'dart:math' as math;

7
import 'package:flutter/foundation.dart';
8
import 'package:flutter/widgets.dart';
9

10
import 'constants.dart';
11
import 'debug.dart';
12
import 'icons.dart';
13
import 'ink_well.dart';
14
import 'material.dart';
15
import 'theme.dart';
16
import 'theme_data.dart';
Hixie's avatar
Hixie committed
17
import 'tooltip.dart';
18

19
// Minimum logical pixel size of the IconButton.
20 21
// See: <https://material.io/design/usability/accessibility.html#layout-typography>.
const double _kMinButtonSize = kMinInteractiveDimension;
22

23
/// A material design icon button.
24 25
///
/// An icon button is a picture printed on a [Material] widget that reacts to
26
/// touches by filling with color (ink).
27
///
28 29
/// Icon buttons are commonly used in the [AppBar.actions] field, but they can
/// be used in many other places as well.
30
///
31 32
/// If the [onPressed] callback is null, then the button will be disabled and
/// will not react to touch.
33
///
34 35
/// Requires one of its ancestors to be a [Material] widget.
///
36 37 38
/// 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/guidelines/layout/metrics-keylines.html#metrics-keylines-touch-target-size)
39 40
/// requirements in the Material Design specification. The [alignment] controls
/// how the icon itself is positioned within the hit region.
41
///
42
/// {@tool snippet --template=stateful_widget_scaffold_center}
43 44 45 46
///
/// This sample shows an `IconButton` that uses the Material icon "volume_up" to
/// increase the volume.
///
47 48
/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/icon_button.png)
///
49 50 51
/// ```dart preamble
/// double _volume = 0.0;
/// ```
52 53
///
/// ```dart
54
/// Widget build(BuildContext context) {
55 56 57 58 59 60 61 62 63 64 65
///   return Column(
///     mainAxisSize: MainAxisSize.min,
///     children: <Widget>[
///       IconButton(
///         icon: Icon(Icons.volume_up),
///         tooltip: 'Increase volume by 10',
///         onPressed: () {
///           setState(() {
///             _volume += 10;
///           });
///         },
66
///       ),
67 68
///       Text('Volume : $_volume')
///     ],
69 70
///   );
/// }
71
/// ```
72
/// {@end-tool}
73
///
74 75 76 77 78 79 80 81 82 83 84 85
/// ### 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.
///
86
/// {@tool snippet --template=stateless_widget_scaffold}
87 88 89 90 91 92
///
/// 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.
///
93 94
/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/icon_button_background.png)
///
95
/// ```dart
96
/// Widget build(BuildContext context) {
97 98 99
///   return Material(
///     color: Colors.white,
///     child: Center(
100
///       child: Ink(
101
///         decoration: const ShapeDecoration(
102 103 104 105 106 107
///           color: Colors.lightBlue,
///           shape: CircleBorder(),
///         ),
///         child: IconButton(
///           icon: Icon(Icons.android),
///           color: Colors.white,
108
///           onPressed: () {},
109 110 111 112 113
///         ),
///       ),
///     ),
///   );
/// }
114 115 116
/// ```
/// {@end-tool}
///
117 118
/// See also:
///
119 120 121 122 123 124 125
///  * [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.
126
class IconButton extends StatelessWidget {
127 128 129 130 131 132
  /// 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.
133
  ///
134 135
  /// The [iconSize], [padding], [autofocus], and [alignment] arguments must not
  /// be null (though they each have default values).
136
  ///
Ian Hickson's avatar
Ian Hickson committed
137 138
  /// The [icon] argument must be specified, and is typically either an [Icon]
  /// or an [ImageIcon].
139 140
  const IconButton({
    Key key,
141
    this.iconSize = 24.0,
142
    this.visualDensity,
143 144
    this.padding = const EdgeInsets.all(8.0),
    this.alignment = Alignment.center,
145
    @required this.icon,
146
    this.color,
147 148
    this.focusColor,
    this.hoverColor,
149 150
    this.highlightColor,
    this.splashColor,
151
    this.disabledColor,
152
    @required this.onPressed,
153
    this.focusNode,
154
    this.autofocus = false,
155
    this.tooltip,
156
    this.enableFeedback = true,
157 158 159
  }) : assert(iconSize != null),
       assert(padding != null),
       assert(alignment != null),
160
       assert(autofocus != null),
161 162
       assert(icon != null),
       super(key: key);
163

Adam Barth's avatar
Adam Barth committed
164
  /// The size of the icon inside the button.
165 166
  ///
  /// This property must not be null. It defaults to 24.0.
167 168 169 170 171 172 173
  ///
  /// 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.
174
  final double iconSize;
Adam Barth's avatar
Adam Barth committed
175

176 177 178 179 180 181 182 183 184 185
  /// 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;

186 187
  /// The padding around the button's icon. The entire padded icon will react
  /// to input gestures.
188 189
  ///
  /// This property must not be null. It defaults to 8.0 padding on all sides.
190
  final EdgeInsetsGeometry padding;
191 192

  /// Defines how the icon is positioned within the IconButton.
193
  ///
194
  /// This property must not be null. It defaults to [Alignment.center].
195 196 197 198 199 200 201
  ///
  /// See also:
  ///
  ///  * [Alignment], a class with convenient constants typically used to
  ///    specify an [AlignmentGeometry].
  ///  * [AlignmentDirectional], like [Alignment] for specifying alignments
  ///    relative to text direction.
202
  final AlignmentGeometry alignment;
203

Ian Hickson's avatar
Ian Hickson committed
204 205
  /// The icon to display inside the button.
  ///
206
  /// The [Icon.size] and [Icon.color] of the icon is configured automatically
207
  /// based on the [iconSize] and [color] properties of _this_ widget using an
208 209
  /// [IconTheme] and therefore should not be explicitly given in the icon
  /// widget.
210 211
  ///
  /// This property must not be null.
Ian Hickson's avatar
Ian Hickson committed
212 213 214
  ///
  /// See [Icon], [ImageIcon].
  final Widget icon;
Adam Barth's avatar
Adam Barth committed
215

216 217 218 219 220 221 222 223 224 225
  /// 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;

226
  /// The color to use for the icon inside the button, if the icon is enabled.
Ian Hickson's avatar
Ian Hickson committed
227
  /// Defaults to leaving this up to the [icon] widget.
228 229 230
  ///
  /// The icon is enabled if [onPressed] is not null.
  ///
231
  /// ```dart
232 233 234 235
  /// IconButton(
  ///   color: Colors.blue,
  ///   onPressed: _handleTap,
  ///   icon: Icons.widgets,
236
  /// )
237
  /// ```
Adam Barth's avatar
Adam Barth committed
238
  final Color color;
239

240 241 242 243 244 245 246 247 248 249 250
  /// 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)
251
  /// state. The highlight color is represented as a solid color that is overlaid over the
252 253 254 255 256 257
  /// 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;

258 259 260 261 262 263
  /// 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;

264
  /// The callback that is called when the button is tapped or otherwise activated.
265 266
  ///
  /// If this is set to null, the button will be disabled.
267
  final VoidCallback onPressed;
Adam Barth's avatar
Adam Barth committed
268

269
  /// {@macro flutter.widgets.Focus.focusNode}
270 271
  final FocusNode focusNode;

272 273 274
  /// {@macro flutter.widgets.Focus.autofocus}
  final bool autofocus;

Adam Barth's avatar
Adam Barth committed
275 276 277 278
  /// 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.
Hixie's avatar
Hixie committed
279
  final String tooltip;
280

281 282 283 284 285 286 287 288 289 290
  /// 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;

291
  @override
292
  Widget build(BuildContext context) {
293
    assert(debugCheckHasMaterial(context));
294
    final ThemeData theme = Theme.of(context);
295 296 297 298
    Color currentColor;
    if (onPressed != null)
      currentColor = color;
    else
299
      currentColor = disabledColor ?? theme.disabledColor;
300

301
    final Offset densityAdjustment = (visualDensity ?? theme.visualDensity).baseSizeAdjustment;
302
    Widget result = ConstrainedBox(
303
      constraints: BoxConstraints(minWidth: _kMinButtonSize + densityAdjustment.dx, minHeight: _kMinButtonSize + densityAdjustment.dy),
304 305 306 307 308 309 310 311 312 313 314
      child: Padding(
        padding: padding,
        child: SizedBox(
          height: iconSize,
          width: iconSize,
          child: Align(
            alignment: alignment,
            child: IconTheme.merge(
              data: IconThemeData(
                size: iconSize,
                color: currentColor,
315
              ),
316
              child: icon,
317 318 319 320
            ),
          ),
        ),
      ),
321
    );
322

Hixie's avatar
Hixie committed
323
    if (tooltip != null) {
324
      result = Tooltip(
Hixie's avatar
Hixie committed
325
        message: tooltip,
326
        child: result,
Hixie's avatar
Hixie committed
327 328
      );
    }
329 330 331 332

    return Semantics(
      button: true,
      enabled: onPressed != null,
333
      child: InkResponse(
334
        focusNode: focusNode,
335
        autofocus: autofocus,
336
        canRequestFocus: onPressed != null,
337
        onTap: onPressed,
338
        enableFeedback: enableFeedback,
339 340 341 342 343 344 345 346 347
        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.
348
        ),
349
      ),
Hixie's avatar
Hixie committed
350
    );
351
  }
Hixie's avatar
Hixie committed
352

353
  @override
354 355
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
356 357
    properties.add(DiagnosticsProperty<Widget>('icon', icon, showName: false));
    properties.add(StringProperty('tooltip', tooltip, defaultValue: null, quoted: false));
358
    properties.add(ObjectFlagProperty<VoidCallback>('onPressed', onPressed, ifNull: 'disabled'));
359 360 361 362 363 364
    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));
365 366
    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null));
    properties.add(DiagnosticsProperty<FocusNode>('focusNode', focusNode, defaultValue: null));
Hixie's avatar
Hixie committed
367
  }
368
}