text.dart 14.4 KB
Newer Older
1 2 3 4
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
import 'package:flutter/foundation.dart';
6 7 8 9 10

import 'basic.dart';
import 'framework.dart';
import 'media_query.dart';

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

14 15 16 17
/// The text style to apply to descendant [Text] widgets without explicit style.
class DefaultTextStyle extends InheritedWidget {
  /// Creates a default text style for the given subtree.
  ///
18
  /// Consider using [DefaultTextStyle.merge] to inherit styling information
19
  /// from the current default text style for a given [BuildContext].
20 21 22 23 24 25 26 27
  ///
  /// The [style] and [child] arguments are required and must not be null.
  ///
  /// The [softWrap] and [overflow] arguments must not be null (though they do
  /// have default values).
  ///
  /// The [maxLines] property may be null (and indeed defaults to null), but if
  /// it is not null, it must be greater than zero.
28
  const DefaultTextStyle({
29 30 31
    Key key,
    @required this.style,
    this.textAlign,
32 33
    this.softWrap = true,
    this.overflow = TextOverflow.clip,
34
    this.maxLines,
35
    @required Widget child,
36 37 38
  }) : assert(style != null),
       assert(softWrap != null),
       assert(overflow != null),
39
       assert(maxLines == null || maxLines > 0),
40 41
       assert(child != null),
       super(key: key, child: child);
42 43 44 45

  /// A const-constructible default text style that provides fallback values.
  ///
  /// Returned from [of] when the given [BuildContext] doesn't have an enclosing default text style.
46 47 48
  ///
  /// This constructor creates a [DefaultTextStyle] that lacks a [child], which
  /// means the constructed value cannot be incorporated into the tree.
49 50 51 52
  const DefaultTextStyle.fallback()
    : style = const TextStyle(),
      textAlign = null,
      softWrap = true,
53
      maxLines = null,
54 55
      overflow = TextOverflow.clip;

56 57
  /// Creates a default text style that overrides the text styles in scope at
  /// this point in the widget tree.
58 59
  ///
  /// The given [style] is merged with the [style] from the default text style
60 61 62
  /// for the [BuildContext] where the widget is inserted, and any of the other
  /// arguments that are not null replace the corresponding properties on that
  /// same default text style.
63 64 65 66 67 68 69 70 71
  ///
  /// This constructor cannot be used to override the [maxLines] property of the
  /// ancestor with the value null, since null here is used to mean "defer to
  /// ancestor". To replace a non-null [maxLines] from an ancestor with the null
  /// value (to remove the restriction on number of lines), manually obtain the
  /// ambient [DefaultTextStyle] using [DefaultTextStyle.of], then create a new
  /// [DefaultTextStyle] using the [new DefaultTextStyle] constructor directly.
  /// See the source below for an example of how to do this (since that's
  /// essentially what this constructor does).
72
  static Widget merge({
73 74 75 76 77
    Key key,
    TextStyle style,
    TextAlign textAlign,
    bool softWrap,
    TextOverflow overflow,
78
    int maxLines,
79
    @required Widget child,
80 81
  }) {
    assert(child != null);
82
    return Builder(
83 84
      builder: (BuildContext context) {
        final DefaultTextStyle parent = DefaultTextStyle.of(context);
85
        return DefaultTextStyle(
86 87 88 89 90 91
          key: key,
          style: parent.style.merge(style),
          textAlign: textAlign ?? parent.textAlign,
          softWrap: softWrap ?? parent.softWrap,
          overflow: overflow ?? parent.overflow,
          maxLines: maxLines ?? parent.maxLines,
92
          child: child,
93 94
        );
      },
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
    );
  }

  /// The text style to apply.
  final TextStyle style;

  /// How the text should be aligned horizontally.
  final TextAlign textAlign;

  /// Whether the text should break at soft line breaks.
  ///
  /// If false, the glyphs in the text will be positioned as if there was unlimited horizontal space.
  final bool softWrap;

  /// How visual overflow should be handled.
  final TextOverflow overflow;

112 113 114
  /// An optional maximum number of lines for the text to span, wrapping if necessary.
  /// If the text exceeds the given number of lines, it will be truncated according
  /// to [overflow].
115 116 117 118 119 120
  ///
  /// If this is 1, text will not wrap. Otherwise, text will be wrapped at the
  /// edge of the box.
  ///
  /// If this is non-null, it will override even explicit null values of
  /// [Text.maxLines].
121 122
  final int maxLines;

123 124 125 126
  /// The closest instance of this class that encloses the given context.
  ///
  /// If no such instance exists, returns an instance created by
  /// [DefaultTextStyle.fallback], which contains fallback values.
127 128 129 130 131 132
  ///
  /// Typical usage is as follows:
  ///
  /// ```dart
  /// DefaultTextStyle style = DefaultTextStyle.of(context);
  /// ```
133 134 135 136 137
  static DefaultTextStyle of(BuildContext context) {
    return context.inheritFromWidgetOfExactType(DefaultTextStyle) ?? const DefaultTextStyle.fallback();
  }

  @override
138 139 140 141 142 143
  bool updateShouldNotify(DefaultTextStyle oldWidget) {
    return style != oldWidget.style ||
        textAlign != oldWidget.textAlign ||
        softWrap != oldWidget.softWrap ||
        overflow != oldWidget.overflow ||
        maxLines != oldWidget.maxLines;
144
  }
145 146

  @override
147 148 149
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    style?.debugFillProperties(properties);
150 151 152 153
    properties.add(EnumProperty<TextAlign>('textAlign', textAlign, defaultValue: null));
    properties.add(FlagProperty('softWrap', value: softWrap, ifTrue: 'wrapping at box width', ifFalse: 'no wrapping except at line break characters', showName: true));
    properties.add(EnumProperty<TextOverflow>('overflow', overflow, defaultValue: null));
    properties.add(IntProperty('maxLines', maxLines, defaultValue: null));
154 155 156 157 158 159 160 161 162 163 164
  }
}

/// A run of text with a single style.
///
/// The [Text] widget displays a string of text with single style. The string
/// might break across multiple lines or might all be displayed on the same line
/// depending on the layout constraints.
///
/// The [style] argument is optional. When omitted, the text will use the style
/// from the closest enclosing [DefaultTextStyle]. If the given style's
165 166 167 168
/// [TextStyle.inherit] property is true (the default), the given style will
/// be merged with the closest enclosing [DefaultTextStyle]. This merging
/// behavior is useful, for example, to make the text bold while using the
/// default font family and size.
169
///
170
/// {@tool sample}
171 172
///
/// ```dart
173
/// Text(
174
///   'Hello, $_name! How are you?',
175 176
///   textAlign: TextAlign.center,
///   overflow: TextOverflow.ellipsis,
177
///   style: TextStyle(fontWeight: FontWeight.bold),
178 179
/// )
/// ```
180
/// {@end-tool}
181
///
182 183 184 185 186
/// Using the [Text.rich] constructor, the [Text] widget can
/// display a paragraph with differently styled [TextSpan]s. The sample
/// that follows displays "Hello beautiful world" with different styles
/// for each word.
///
187
/// {@tool sample}
188 189 190
///
/// ```dart
/// const Text.rich(
191
///   TextSpan(
192
///     text: 'Hello', // default text style
193 194 195
///     children: <TextSpan>[
///       TextSpan(text: ' beautiful ', style: TextStyle(fontStyle: FontStyle.italic)),
///       TextSpan(text: 'world', style: TextStyle(fontWeight: FontWeight.bold)),
196 197
///     ],
///   ),
198
/// )
199
/// ```
200
/// {@end-tool}
201
///
202 203 204 205 206 207 208 209 210 211 212 213 214
/// ## Interactivity
///
/// To make [Text] react to touch events, wrap it in a [GestureDetector] widget
/// with a [GestureDetector.onTap] handler.
///
/// In a material design application, consider using a [FlatButton] instead, or
/// if that isn't appropriate, at least using an [InkWell] instead of
/// [GestureDetector].
///
/// To make sections of the text interactive, use [RichText] and specify a
/// [TapGestureRecognizer] as the [TextSpan.recognizer] of the relevant part of
/// the text.
///
215 216
/// See also:
///
217 218
///  * [RichText], which gives you more control over the text styles.
///  * [DefaultTextStyle], which sets default styles for [Text] widgets.
219 220 221 222 223
class Text extends StatelessWidget {
  /// Creates a text widget.
  ///
  /// If the [style] argument is null, the text will use the style from the
  /// closest enclosing [DefaultTextStyle].
224
  const Text(this.data, {
225 226 227
    Key key,
    this.style,
    this.textAlign,
Ian Hickson's avatar
Ian Hickson committed
228
    this.textDirection,
229
    this.locale,
230 231
    this.softWrap,
    this.overflow,
232 233
    this.textScaleFactor,
    this.maxLines,
234
    this.semanticsLabel,
235
  }) : assert(data != null),
236
       textSpan = null,
237
       super(key: key);
238

239 240 241 242 243 244
  /// Creates a text widget with a [TextSpan].
  const Text.rich(this.textSpan, {
    Key key,
    this.style,
    this.textAlign,
    this.textDirection,
245
    this.locale,
246 247 248 249
    this.softWrap,
    this.overflow,
    this.textScaleFactor,
    this.maxLines,
250
    this.semanticsLabel,
251 252 253 254
  }): assert(textSpan != null),
      data = null,
      super(key: key);

255
  /// The text to display.
256 257
  ///
  /// This will be null if a [textSpan] is provided instead.
258 259
  final String data;

260 261 262 263 264
  /// The text to display as a [TextSpan].
  ///
  /// This will be null if [data] is provided instead.
  final TextSpan textSpan;

265 266 267 268 269 270 271 272 273 274
  /// If non-null, the style to use for this text.
  ///
  /// If the style's "inherit" property is true, the style will be merged with
  /// the closest enclosing [DefaultTextStyle]. Otherwise, the style will
  /// replace the closest enclosing [DefaultTextStyle].
  final TextStyle style;

  /// How the text should be aligned horizontally.
  final TextAlign textAlign;

Ian Hickson's avatar
Ian Hickson committed
275 276 277 278 279 280 281 282 283
  /// The directionality of the text.
  ///
  /// This decides how [textAlign] values like [TextAlign.start] and
  /// [TextAlign.end] are interpreted.
  ///
  /// This is also used to disambiguate how to render bidirectional text. For
  /// example, if the [data] is an English phrase followed by a Hebrew phrase,
  /// in a [TextDirection.ltr] context the English phrase will be on the left
  /// and the Hebrew phrase to its right, while in a [TextDirection.rtl]
284
  /// context, the English phrase will be on the right and the Hebrew phrase on
Ian Hickson's avatar
Ian Hickson committed
285 286 287 288 289
  /// its left.
  ///
  /// Defaults to the ambient [Directionality], if any.
  final TextDirection textDirection;

290 291 292 293 294 295 296 297 298
  /// Used to select a font when the same Unicode character can
  /// be rendered differently, depending on the locale.
  ///
  /// It's rarely necessary to set this property. By default its value
  /// is inherited from the enclosing app with `Localizations.localeOf(context)`.
  ///
  /// See [RenderParagraph.locale] for more information.
  final Locale locale;

299 300 301 302 303 304 305 306 307 308 309 310 311
  /// Whether the text should break at soft line breaks.
  ///
  /// If false, the glyphs in the text will be positioned as if there was unlimited horizontal space.
  final bool softWrap;

  /// How visual overflow should be handled.
  final TextOverflow overflow;

  /// The number of font pixels for each logical pixel.
  ///
  /// For example, if the text scale factor is 1.5, text will be 50% larger than
  /// the specified font size.
  ///
312
  /// The value given to the constructor as textScaleFactor. If null, will
313
  /// use the [MediaQueryData.textScaleFactor] obtained from the ambient
314
  /// [MediaQuery], or 1.0 if there is no [MediaQuery] in scope.
315 316
  final double textScaleFactor;

317
  /// An optional maximum number of lines for the text to span, wrapping if necessary.
318 319
  /// If the text exceeds the given number of lines, it will be truncated according
  /// to [overflow].
320 321 322 323 324 325 326 327
  ///
  /// If this is 1, text will not wrap. Otherwise, text will be wrapped at the
  /// edge of the box.
  ///
  /// If this is null, but there is an ambient [DefaultTextStyle] that specifies
  /// an explicit number for its [DefaultTextStyle.maxLines], then the
  /// [DefaultTextStyle] value will take precedence. You can use a [RichText]
  /// widget directly to entirely override the [DefaultTextStyle].
328 329
  final int maxLines;

330 331 332 333 334
  /// An alternative semantics label for this text.
  ///
  /// If present, the semantics of this widget will contain this value instead
  /// of the actual text.
  ///
335
  /// This is useful for replacing abbreviations or shorthands with the full
336 337 338
  /// text value:
  ///
  /// ```dart
339
  /// Text(r'$$', semanticsLabel: 'Double dollars')
340 341 342 343
  ///
  /// ```
  final String semanticsLabel;

344 345
  @override
  Widget build(BuildContext context) {
346
    final DefaultTextStyle defaultTextStyle = DefaultTextStyle.of(context);
347 348 349
    TextStyle effectiveTextStyle = style;
    if (style == null || style.inherit)
      effectiveTextStyle = defaultTextStyle.style.merge(style);
350 351
    if (MediaQuery.boldTextOverride(context))
      effectiveTextStyle = effectiveTextStyle.merge(const TextStyle(fontWeight: FontWeight.bold));
352
    Widget result = RichText(
Ian Hickson's avatar
Ian Hickson committed
353 354
      textAlign: textAlign ?? defaultTextStyle.textAlign ?? TextAlign.start,
      textDirection: textDirection, // RichText uses Directionality.of to obtain a default if this is null.
355
      locale: locale, // RichText uses Localizations.localeOf to obtain a default if this is null
356 357
      softWrap: softWrap ?? defaultTextStyle.softWrap,
      overflow: overflow ?? defaultTextStyle.overflow,
358
      textScaleFactor: textScaleFactor ?? MediaQuery.textScaleFactorOf(context),
359
      maxLines: maxLines ?? defaultTextStyle.maxLines,
360
      text: TextSpan(
361
        style: effectiveTextStyle,
362
        text: data,
363 364
        children: textSpan != null ? <TextSpan>[textSpan] : null,
      ),
365
    );
366
    if (semanticsLabel != null) {
367
      result = Semantics(
368 369
        textDirection: textDirection,
        label: semanticsLabel,
370
        child: ExcludeSemantics(
371 372 373 374 375
          child: result,
        )
      );
    }
    return result;
376 377 378
  }

  @override
379 380
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
381
    properties.add(StringProperty('data', data, showName: false));
382
    if (textSpan != null) {
383
      properties.add(textSpan.toDiagnosticsNode(name: 'textSpan', style: DiagnosticsTreeStyle.transition));
384
    }
385
    style?.debugFillProperties(properties);
386 387 388 389 390 391 392
    properties.add(EnumProperty<TextAlign>('textAlign', textAlign, defaultValue: null));
    properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
    properties.add(DiagnosticsProperty<Locale>('locale', locale, defaultValue: null));
    properties.add(FlagProperty('softWrap', value: softWrap, ifTrue: 'wrapping at box width', ifFalse: 'no wrapping except at line break characters', showName: true));
    properties.add(EnumProperty<TextOverflow>('overflow', overflow, defaultValue: null));
    properties.add(DoubleProperty('textScaleFactor', textScaleFactor, defaultValue: null));
    properties.add(IntProperty('maxLines', maxLines, defaultValue: null));
393
    if (semanticsLabel != null) {
394
      properties.add(StringProperty('semanticsLabel', semanticsLabel));
395
    }
396 397
  }
}