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

import 'package:flutter/widgets.dart';

import 'constants.dart';
import 'theme.dart';
9
import 'theme_data.dart';
Adam Barth's avatar
Adam Barth committed
10

11 12 13
// Examples can assume:
// String userAvatarUrl;

Hixie's avatar
Hixie committed
14 15
/// A circle that represents a user.
///
16
/// Typically used with a user's profile image, or, in the absence of
Hixie's avatar
Hixie committed
17 18 19
/// such an image, the user's initials. A given user's initials should
/// always be paired with the same background color, for consistency.
///
20
/// {@tool snippet}
Ian Hickson's avatar
Ian Hickson committed
21
///
22 23 24 25
/// If the avatar is to have an image, the image should be specified in the
/// [backgroundImage] property:
///
/// ```dart
26 27
/// CircleAvatar(
///   backgroundImage: NetworkImage(userAvatarUrl),
28 29
/// )
/// ```
30
/// {@end-tool}
31 32 33
///
/// The image will be cropped to have a circle shape.
///
34
/// {@tool snippet}
35
///
36 37 38 39
/// If the avatar is to just have the user's initials, they are typically
/// provided using a [Text] widget as the [child] and a [backgroundColor]:
///
/// ```dart
40
/// CircleAvatar(
41
///   backgroundColor: Colors.brown.shade800,
42
///   child: Text('AH'),
Ian Hickson's avatar
Ian Hickson committed
43
/// )
44
/// ```
45
/// {@end-tool}
46
///
47
/// See also:
48
///
49
///  * [Chip], for representing users or concepts in long form.
50 51
///  * [ListTile], which can combine an icon (such as a [CircleAvatar]) with
///    some text for a fixed height list entry.
52
///  * <https://material.io/design/components/chips.html#input-chips>
53
class CircleAvatar extends StatelessWidget {
54
  /// Creates a circle that represents a user.
55
  const CircleAvatar({
Adam Barth's avatar
Adam Barth committed
56
    Key key,
Hans Muller's avatar
Hans Muller committed
57
    this.child,
Adam Barth's avatar
Adam Barth committed
58
    this.backgroundColor,
59
    this.backgroundImage,
60
    this.foregroundColor,
61 62 63
    this.radius,
    this.minRadius,
    this.maxRadius,
64 65
  }) : assert(radius == null || (minRadius == null && maxRadius == null)),
       super(key: key);
Adam Barth's avatar
Adam Barth committed
66

67
  /// The widget below this widget in the tree.
68 69 70
  ///
  /// Typically a [Text] widget. If the [CircleAvatar] is to have an image, use
  /// [backgroundImage] instead.
Hans Muller's avatar
Hans Muller committed
71
  final Widget child;
Hixie's avatar
Hixie committed
72 73 74

  /// The color with which to fill the circle. Changing the background
  /// color will cause the avatar to animate to the new color.
75
  ///
76 77 78
  /// If a [backgroundColor] is not specified, the theme's
  /// [ThemeData.primaryColorLight] is used with dark foreground colors, and
  /// [ThemeData.primaryColorDark] with light foreground colors.
Adam Barth's avatar
Adam Barth committed
79
  final Color backgroundColor;
Hixie's avatar
Hixie committed
80

81 82
  /// The default text color for text in the circle.
  ///
83 84 85 86 87
  /// Defaults to the primary text theme color if no [backgroundColor] is
  /// specified.
  ///
  /// Defaults to [ThemeData.primaryColorLight] for dark background colors, and
  /// [ThemeData.primaryColorDark] for light background colors.
88 89
  final Color foregroundColor;

90 91
  /// The background image of the circle. Changing the background
  /// image will cause the avatar to animate to the new image.
92 93
  ///
  /// If the [CircleAvatar] is to have the user's initials, use [child] instead.
94 95
  final ImageProvider backgroundImage;

96
  /// The size of the avatar, expressed as the radius (half the diameter).
97
  ///
98 99 100 101
  /// If [radius] is specified, then neither [minRadius] nor [maxRadius] may be
  /// specified. Specifying [radius] is equivalent to specifying a [minRadius]
  /// and [maxRadius], both with the value of [radius].
  ///
102 103 104 105 106 107
  /// If neither [minRadius] nor [maxRadius] are specified, defaults to 20
  /// logical pixels. This is the appropriate size for use with
  /// [ListTile.leading].
  ///
  /// Changes to the [radius] are animated (including changing from an explicit
  /// [radius] to a [minRadius]/[maxRadius] pair or vice versa).
Hans Muller's avatar
Hans Muller committed
108
  final double radius;
Adam Barth's avatar
Adam Barth committed
109

110 111
  /// The minimum size of the avatar, expressed as the radius (half the
  /// diameter).
112
  ///
113
  /// If [minRadius] is specified, then [radius] must not also be specified.
114 115
  ///
  /// Defaults to zero.
116 117 118 119 120 121 122 123
  ///
  /// Constraint changes are animated, but size changes due to the environment
  /// itself changing are not. For example, changing the [minRadius] from 10 to
  /// 20 when the [CircleAvatar] is in an unconstrained environment will cause
  /// the avatar to animate from a 20 pixel diameter to a 40 pixel diameter.
  /// However, if the [minRadius] is 40 and the [CircleAvatar] has a parent
  /// [SizedBox] whose size changes instantaneously from 20 pixels to 40 pixels,
  /// the size will snap to 40 pixels instantly.
124 125
  final double minRadius;

126 127
  /// The maximum size of the avatar, expressed as the radius (half the
  /// diameter).
128
  ///
129
  /// If [maxRadius] is specified, then [radius] must not also be specified.
130 131
  ///
  /// Defaults to [double.infinity].
132 133 134 135 136 137 138 139
  ///
  /// Constraint changes are animated, but size changes due to the environment
  /// itself changing are not. For example, changing the [maxRadius] from 10 to
  /// 20 when the [CircleAvatar] is in an unconstrained environment will cause
  /// the avatar to animate from a 20 pixel diameter to a 40 pixel diameter.
  /// However, if the [maxRadius] is 40 and the [CircleAvatar] has a parent
  /// [SizedBox] whose size changes instantaneously from 20 pixels to 40 pixels,
  /// the size will snap to 40 pixels instantly.
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
  final double maxRadius;

  // The default radius if nothing is specified.
  static const double _defaultRadius = 20.0;

  // The default min if only the max is specified.
  static const double _defaultMinRadius = 0.0;

  // The default max if only the min is specified.
  static const double _defaultMaxRadius = double.infinity;

  double get _minDiameter {
    if (radius == null && minRadius == null && maxRadius == null) {
      return _defaultRadius * 2.0;
    }
    return 2.0 * (radius ?? minRadius ?? _defaultMinRadius);
  }

  double get _maxDiameter {
    if (radius == null && minRadius == null && maxRadius == null) {
      return _defaultRadius * 2.0;
    }
    return 2.0 * (radius ?? maxRadius ?? _defaultMaxRadius);
  }

165
  @override
Adam Barth's avatar
Adam Barth committed
166
  Widget build(BuildContext context) {
167
    assert(debugCheckHasMediaQuery(context));
Hans Muller's avatar
Hans Muller committed
168
    final ThemeData theme = Theme.of(context);
169
    TextStyle textStyle = theme.primaryTextTheme.subhead.copyWith(color: foregroundColor);
170 171 172 173 174 175 176 177 178 179 180
    Color effectiveBackgroundColor = backgroundColor;
    if (effectiveBackgroundColor == null) {
      switch (ThemeData.estimateBrightnessForColor(textStyle.color)) {
        case Brightness.dark:
          effectiveBackgroundColor = theme.primaryColorLight;
          break;
        case Brightness.light:
          effectiveBackgroundColor = theme.primaryColorDark;
          break;
      }
    } else if (foregroundColor == null) {
181 182
      switch (ThemeData.estimateBrightnessForColor(backgroundColor)) {
        case Brightness.dark:
183
          textStyle = textStyle.copyWith(color: theme.primaryColorLight);
184 185
          break;
        case Brightness.light:
186
          textStyle = textStyle.copyWith(color: theme.primaryColorDark);
187 188 189
          break;
      }
    }
190 191
    final double minDiameter = _minDiameter;
    final double maxDiameter = _maxDiameter;
192 193
    return AnimatedContainer(
      constraints: BoxConstraints(
194 195 196 197 198
        minHeight: minDiameter,
        minWidth: minDiameter,
        maxWidth: maxDiameter,
        maxHeight: maxDiameter,
      ),
Adam Barth's avatar
Adam Barth committed
199
      duration: kThemeChangeDuration,
200
      decoration: BoxDecoration(
201
        color: effectiveBackgroundColor,
202
        image: backgroundImage != null
203
          ? DecorationImage(image: backgroundImage, fit: BoxFit.cover)
204
          : null,
205
        shape: BoxShape.circle,
Adam Barth's avatar
Adam Barth committed
206
      ),
207 208
      child: child == null
          ? null
209 210
          : Center(
              child: MediaQuery(
211 212 213
                // Need to ignore the ambient textScaleFactor here so that the
                // text doesn't escape the avatar when the textScaleFactor is large.
                data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0),
214
                child: IconTheme(
215
                  data: theme.iconTheme.copyWith(color: textStyle.color),
216
                  child: DefaultTextStyle(
217 218 219 220 221 222
                    style: textStyle,
                    child: child,
                  ),
                ),
              ),
            ),
Adam Barth's avatar
Adam Barth committed
223 224 225
    );
  }
}