icon.dart 11.7 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:ui';

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

10
import 'basic.dart';
11
import 'debug.dart';
12 13
import 'framework.dart';
import 'icon_data.dart';
Adam Barth's avatar
Adam Barth committed
14
import 'icon_theme.dart';
Ian Hickson's avatar
Ian Hickson committed
15
import 'icon_theme_data.dart';
16

17 18
/// A graphical icon widget drawn with a glyph from a font described in
/// an [IconData] such as material's predefined [IconData]s in [Icons].
19
///
20 21
/// Icons are not interactive. For an interactive icon, consider material's
/// [IconButton].
22
///
Ian Hickson's avatar
Ian Hickson committed
23 24 25 26
/// There must be an ambient [Directionality] widget when using [Icon].
/// Typically this is introduced automatically by the [WidgetsApp] or
/// [MaterialApp].
///
27 28 29
/// This widget assumes that the rendered icon is squared. Non-squared icons may
/// render incorrectly.
///
30
/// {@tool snippet}
31
///
32 33 34
/// This example shows how to create a [Row] of [Icon]s in different colors and
/// sizes. The first [Icon] uses a [semanticLabel] to announce in accessibility
/// modes like TalkBack and VoiceOver.
35
///
36
/// ![The following code snippet would generate a row of icons consisting of a pink heart, a green musical note, and a blue umbrella, each progressively bigger than the last.](https://flutter.github.io/assets-for-api-docs/assets/widgets/icon.png)
37
///
38
/// ```dart
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
/// Row(
///   mainAxisAlignment: MainAxisAlignment.spaceAround,
///   children: const <Widget>[
///     Icon(
///       Icons.favorite,
///       color: Colors.pink,
///       size: 24.0,
///       semanticLabel: 'Text to announce in accessibility modes',
///     ),
///     Icon(
///       Icons.audiotrack,
///       color: Colors.green,
///       size: 30.0,
///     ),
///     Icon(
///       Icons.beach_access,
///       color: Colors.blue,
///       size: 36.0,
///     ),
///   ],
59 60 61 62
/// )
/// ```
/// {@end-tool}
///
63 64
/// See also:
///
65
///  * [IconButton], for interactive icons.
66
///  * [Icons], for the list of available Material Icons for use with this class.
67
///  * [IconTheme], which provides ambient configuration for icons.
Ian Hickson's avatar
Ian Hickson committed
68
///  * [ImageIcon], for showing icons from [AssetImage]s or other [ImageProvider]s.
69
class Icon extends StatelessWidget {
70
  /// Creates an icon.
71 72
  const Icon(
    this.icon, {
73
    super.key,
74
    this.size,
75 76 77 78
    this.fill,
    this.weight,
    this.grade,
    this.opticalSize,
79
    this.color,
80
    this.shadows,
81
    this.semanticLabel,
82
    this.textDirection,
83 84 85
  }) : assert(fill == null || (0.0 <= fill && fill <= 1.0)),
       assert(weight == null || (0.0 < weight)),
       assert(opticalSize == null || (0.0 < opticalSize));
86 87 88

  /// The icon to display. The available icons are described in [Icons].
  ///
Ian Hickson's avatar
Ian Hickson committed
89 90
  /// The icon can be null, in which case the widget will render as an empty
  /// space of the specified [size].
91
  final IconData? icon;
92

Adam Barth's avatar
Adam Barth committed
93 94 95
  /// The size of the icon in logical pixels.
  ///
  /// Icons occupy a square with width and height equal to size.
96
  ///
97
  /// Defaults to the nearest [IconTheme]'s [IconThemeData.size].
98 99
  ///
  /// If this [Icon] is being placed inside an [IconButton], then use
100
  /// [IconButton.iconSize] instead, so that the [IconButton] can make the splash
101 102
  /// area the appropriate size as well. The [IconButton] uses an [IconTheme] to
  /// pass down the size to the [Icon].
103
  final double? size;
Adam Barth's avatar
Adam Barth committed
104

105
  /// The fill for drawing the icon.
106
  ///
107 108 109 110
  /// Requires the underlying icon font to support the `FILL` [FontVariation]
  /// axis, otherwise has no effect. Variable font filenames often indicate
  /// the supported axes. Must be between 0.0 (unfilled) and 1.0 (filled),
  /// inclusive.
111
  ///
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
  /// Can be used to convey a state transition for animation or interaction.
  ///
  /// Defaults to nearest [IconTheme]'s [IconThemeData.fill].
  ///
  /// See also:
  ///  * [weight], for controlling stroke weight.
  ///  * [grade], for controlling stroke weight in a more granular way.
  ///  * [opticalSize], for controlling optical size.
  final double? fill;

  /// The stroke weight for drawing the icon.
  ///
  /// Requires the underlying icon font to support the `wght` [FontVariation]
  /// axis, otherwise has no effect. Variable font filenames often indicate
  /// the supported axes. Must be greater than 0.
  ///
  /// Defaults to nearest [IconTheme]'s [IconThemeData.weight].
  ///
  /// See also:
  ///  * [fill], for controlling fill.
  ///  * [grade], for controlling stroke weight in a more granular way.
  ///  * [opticalSize], for controlling optical size.
  ///  * https://fonts.google.com/knowledge/glossary/weight_axis
  final double? weight;

  /// The grade (granular stroke weight) for drawing the icon.
  ///
  /// Requires the underlying icon font to support the `GRAD` [FontVariation]
  /// axis, otherwise has no effect. Variable font filenames often indicate
  /// the supported axes. Can be negative.
  ///
  /// Grade and [weight] both affect a symbol's stroke weight (thickness), but
  /// grade has a smaller impact on the size of the symbol.
  ///
  /// Grade is also available in some text fonts. One can match grade levels
  /// between text and symbols for a harmonious visual effect. For example, if
  /// the text font has a -25 grade value, the symbols can match it with a
  /// suitable value, say -25.
  ///
  /// Defaults to nearest [IconTheme]'s [IconThemeData.grade].
  ///
  /// See also:
  ///  * [fill], for controlling fill.
  ///  * [weight], for controlling stroke weight in a less granular way.
  ///  * [opticalSize], for controlling optical size.
  ///  * https://fonts.google.com/knowledge/glossary/grade_axis
  final double? grade;

  /// The optical size for drawing the icon.
  ///
  /// Requires the underlying icon font to support the `opsz` [FontVariation]
  /// axis, otherwise has no effect. Variable font filenames often indicate
  /// the supported axes. Must be greater than 0.
  ///
  /// For an icon to look the same at different sizes, the stroke weight
  /// (thickness) must change as the icon size scales. Optical size offers a way
  /// to automatically adjust the stroke weight as icon size changes.
  ///
  /// Defaults to nearest [IconTheme]'s [IconThemeData.opticalSize].
171
  ///
172 173 174 175 176 177 178 179
  /// See also:
  ///  * [fill], for controlling fill.
  ///  * [weight], for controlling stroke weight.
  ///  * [grade], for controlling stroke weight in a more granular way.
  ///  * https://fonts.google.com/knowledge/glossary/optical_size_axis
  final double? opticalSize;

  /// The color to use when drawing the icon.
180
  ///
181
  /// Defaults to the nearest [IconTheme]'s [IconThemeData.color].
182
  ///
183 184 185
  /// The color (whether specified explicitly here or obtained from the
  /// [IconTheme]) will be further adjusted by the nearest [IconTheme]'s
  /// [IconThemeData.opacity].
186
  ///
187
  /// {@tool snippet}
188
  /// Typically, a Material Design color will be used, as follows:
189 190
  ///
  /// ```dart
191
  /// Icon(
192
  ///   Icons.widgets,
193 194
  ///   color: Colors.blue.shade400,
  /// )
195
  /// ```
196
  /// {@end-tool}
197
  final Color? color;
198

199 200 201 202 203 204 205 206 207 208 209
  /// A list of [Shadow]s that will be painted underneath the icon.
  ///
  /// Multiple shadows are supported to replicate lighting from multiple light
  /// sources.
  ///
  /// Shadows must be in the same order for [Icon] to be considered as
  /// equivalent as order produces differing transparency.
  ///
  /// Defaults to the nearest [IconTheme]'s [IconThemeData.shadows].
  final List<Shadow>? shadows;

210 211
  /// Semantic label for the icon.
  ///
212
  /// Announced in accessibility modes (e.g TalkBack/VoiceOver).
213 214
  /// This label does not show in the UI.
  ///
215 216
  ///  * [SemanticsProperties.label], which is set to [semanticLabel] in the
  ///    underlying	 [Semantics] widget.
217
  final String? semanticLabel;
218

219 220 221 222 223 224 225 226 227 228 229 230 231
  /// The text direction to use for rendering the icon.
  ///
  /// If this is null, the ambient [Directionality] is used instead.
  ///
  /// Some icons follow the reading direction. For example, "back" buttons point
  /// left in left-to-right environments and right in right-to-left
  /// environments. Such icons have their [IconData.matchTextDirection] field
  /// set to true, and the [Icon] widget uses the [textDirection] to determine
  /// the orientation in which to draw the icon.
  ///
  /// This property has no effect if the [icon]'s [IconData.matchTextDirection]
  /// field is false, but for consistency a text direction value must always be
  /// specified, either directly using this property or using [Directionality].
232
  final TextDirection? textDirection;
233

234
  @override
235
  Widget build(BuildContext context) {
236
    assert(this.textDirection != null || debugCheckHasDirectionality(context));
237
    final TextDirection textDirection = this.textDirection ?? Directionality.of(context);
Ian Hickson's avatar
Ian Hickson committed
238

239
    final IconThemeData iconTheme = IconTheme.of(context);
Ian Hickson's avatar
Ian Hickson committed
240

241
    final double? iconSize = size ?? iconTheme.size;
242

243 244 245 246 247 248 249 250
    final double? iconFill = fill ?? iconTheme.fill;

    final double? iconWeight = weight ?? iconTheme.weight;

    final double? iconGrade = grade ?? iconTheme.grade;

    final double? iconOpticalSize = opticalSize ?? iconTheme.opticalSize;

251 252
    final List<Shadow>? iconShadows = shadows ?? iconTheme.shadows;

253
    if (icon == null) {
254
      return Semantics(
255
        label: semanticLabel,
256
        child: SizedBox(width: iconSize, height: iconSize),
257
      );
258
    }
Hans Muller's avatar
Hans Muller committed
259

260 261
    final double iconOpacity = iconTheme.opacity ?? 1.0;
    Color iconColor = color ?? iconTheme.color!;
262
    if (iconOpacity != 1.0) {
263
      iconColor = iconColor.withOpacity(iconColor.opacity * iconOpacity);
264
    }
265

266
    Widget iconWidget = RichText(
267
      overflow: TextOverflow.visible, // Never clip.
268
      textDirection: textDirection, // Since we already fetched it for the assert...
269
      text: TextSpan(
270
        text: String.fromCharCode(icon!.codePoint),
271
        style: TextStyle(
272
          fontVariations: <FontVariation>[
273 274 275 276
            if (iconFill != null) FontVariation('FILL', iconFill),
            if (iconWeight != null) FontVariation('wght', iconWeight),
            if (iconGrade != null) FontVariation('GRAD', iconGrade),
            if (iconOpticalSize != null) FontVariation('opsz', iconOpticalSize),
277
          ],
278 279 280
          inherit: false,
          color: iconColor,
          fontSize: iconSize,
281 282
          fontFamily: icon!.fontFamily,
          package: icon!.fontPackage,
283
          shadows: iconShadows,
284 285 286 287
        ),
      ),
    );

288
    if (icon!.matchTextDirection) {
289 290
      switch (textDirection) {
        case TextDirection.rtl:
291 292
          iconWidget = Transform(
            transform: Matrix4.identity()..scale(-1.0, 1.0, 1.0),
293 294 295 296 297 298 299 300 301 302
            alignment: Alignment.center,
            transformHitTests: false,
            child: iconWidget,
          );
          break;
        case TextDirection.ltr:
          break;
      }
    }

303
    return Semantics(
304
      label: semanticLabel,
305 306
      child: ExcludeSemantics(
        child: SizedBox(
307 308
          width: iconSize,
          height: iconSize,
309
          child: Center(
310
            child: iconWidget,
Ian Hickson's avatar
Ian Hickson committed
311 312 313
          ),
        ),
      ),
314 315
    );
  }
Hixie's avatar
Hixie committed
316

317
  @override
318 319
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
320
    properties.add(IconDataProperty('icon', icon, ifNull: '<empty>', showName: false));
321
    properties.add(DoubleProperty('size', size, defaultValue: null));
322 323 324 325
    properties.add(DoubleProperty('fill', fill, defaultValue: null));
    properties.add(DoubleProperty('weight', weight, defaultValue: null));
    properties.add(DoubleProperty('grade', grade, defaultValue: null));
    properties.add(DoubleProperty('opticalSize', opticalSize, defaultValue: null));
326
    properties.add(ColorProperty('color', color, defaultValue: null));
327
    properties.add(IterableProperty<Shadow>('shadows', shadows, defaultValue: null));
328 329
    properties.add(StringProperty('semanticLabel', semanticLabel, defaultValue: null));
    properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
Hixie's avatar
Hixie committed
330
  }
331
}