Allow multi-line text fields with no line limit (#10576)
This commit is contained in:
parent
db75aa768c
commit
d74a5883d9
@ -62,6 +62,12 @@ class TextField extends StatefulWidget {
|
||||
/// To remove the decoration entirely (including the extra padding introduced
|
||||
/// by the decoration to save space for the labels), set the [decoration] to
|
||||
/// null.
|
||||
///
|
||||
/// The [maxLines] property can be set to null to remove the restriction on
|
||||
/// the number of lines. By default, it is 1, meaning this is a single-line
|
||||
/// text field. If it is not null, it must be greater than zero.
|
||||
///
|
||||
/// The [keyboardType], [autofocus], and [obscureText] arguments must not be null.
|
||||
const TextField({
|
||||
Key key,
|
||||
this.controller,
|
||||
@ -76,7 +82,11 @@ class TextField extends StatefulWidget {
|
||||
this.onChanged,
|
||||
this.onSubmitted,
|
||||
this.inputFormatters,
|
||||
}) : super(key: key);
|
||||
}) : assert(keyboardType != null),
|
||||
assert(autofocus != null),
|
||||
assert(obscureText != null),
|
||||
assert(maxLines == null || maxLines > 0),
|
||||
super(key: key);
|
||||
|
||||
/// Controls the text being edited.
|
||||
///
|
||||
@ -98,6 +108,8 @@ class TextField extends StatefulWidget {
|
||||
final InputDecoration decoration;
|
||||
|
||||
/// The type of keyboard to use for editing the text.
|
||||
///
|
||||
/// Defaults to [TextInputType.text]. Cannot be null.
|
||||
final TextInputType keyboardType;
|
||||
|
||||
/// The style to use for the text being edited.
|
||||
@ -116,7 +128,7 @@ class TextField extends StatefulWidget {
|
||||
/// If true, the keyboard will open as soon as this text field obtains focus.
|
||||
/// Otherwise, the keyboard is only shown after the user taps the text field.
|
||||
///
|
||||
/// Defaults to false.
|
||||
/// Defaults to false. Cannot be null.
|
||||
// See https://github.com/flutter/flutter/issues/7035 for the rationale for this
|
||||
// keyboard behavior.
|
||||
final bool autofocus;
|
||||
@ -126,13 +138,16 @@ class TextField extends StatefulWidget {
|
||||
/// When this is set to true, all the characters in the text field are
|
||||
/// replaced by U+2022 BULLET characters (•).
|
||||
///
|
||||
/// Defaults to false.
|
||||
/// Defaults to false. Cannot be null.
|
||||
final bool obscureText;
|
||||
|
||||
/// The maximum number of lines for the text to span, wrapping if necessary.
|
||||
///
|
||||
/// If this is 1 (the default), the text will not wrap, but will scroll
|
||||
/// horizontally instead.
|
||||
///
|
||||
/// If this is null, there is no limit to the number of lines. If it is not
|
||||
/// null, the value must be greater than zero.
|
||||
final int maxLines;
|
||||
|
||||
/// Called when the text being edited changes.
|
||||
|
@ -30,7 +30,8 @@ import 'text_field.dart';
|
||||
class TextFormField extends FormField<String> {
|
||||
/// Creates a [FormField] that contains a [TextField].
|
||||
///
|
||||
/// For a documentation about the various parameters, see [TextField].
|
||||
/// For documentation about the various parameters, see the [TextField] class
|
||||
/// and [new TextField], the constructor.
|
||||
TextFormField({
|
||||
Key key,
|
||||
TextEditingController controller,
|
||||
@ -44,7 +45,11 @@ class TextFormField extends FormField<String> {
|
||||
FormFieldSetter<String> onSaved,
|
||||
FormFieldValidator<String> validator,
|
||||
List<TextInputFormatter> inputFormatters,
|
||||
}) : super(
|
||||
}) : assert(keyboardType != null),
|
||||
assert(autofocus != null),
|
||||
assert(obscureText != null),
|
||||
assert(maxLines == null || maxLines > 0),
|
||||
super(
|
||||
key: key,
|
||||
initialValue: controller != null ? controller.value.text : '',
|
||||
onSaved: onSaved,
|
||||
|
@ -35,6 +35,8 @@ class TextPainter {
|
||||
///
|
||||
/// The text argument is optional but [text] must be non-null before calling
|
||||
/// [layout].
|
||||
///
|
||||
/// The [maxLines] property, if non-null, must be greater than zero.
|
||||
TextPainter({
|
||||
TextSpan text,
|
||||
TextAlign textAlign,
|
||||
@ -43,6 +45,7 @@ class TextPainter {
|
||||
String ellipsis,
|
||||
}) : assert(text == null || text.debugAssertIsValid()),
|
||||
assert(textScaleFactor != null),
|
||||
assert(maxLines == null || maxLines > 0),
|
||||
_text = text,
|
||||
_textAlign = textAlign,
|
||||
_textScaleFactor = textScaleFactor,
|
||||
@ -134,7 +137,9 @@ class TextPainter {
|
||||
/// After this is set, you must call [layout] before the next call to [paint].
|
||||
int get maxLines => _maxLines;
|
||||
int _maxLines;
|
||||
/// The value may be null. If it is not null, then it must be greater than zero.
|
||||
set maxLines(int value) {
|
||||
assert(value == null || value > 0);
|
||||
if (_maxLines == value)
|
||||
return;
|
||||
_maxLines = value;
|
||||
|
@ -234,12 +234,18 @@ class TextStyle {
|
||||
}
|
||||
|
||||
/// The style information for paragraphs, encoded for use by `dart:ui`.
|
||||
///
|
||||
/// The `textScaleFactor` argument must not be null. If omitted, it defaults
|
||||
/// to 1.0. The other arguments may be null. The `maxLines` argument, if
|
||||
/// specified and non-null, must be greater than zero.
|
||||
ui.ParagraphStyle getParagraphStyle({
|
||||
TextAlign textAlign,
|
||||
double textScaleFactor: 1.0,
|
||||
String ellipsis,
|
||||
int maxLines,
|
||||
}) {
|
||||
}) {
|
||||
assert(textScaleFactor != null);
|
||||
assert(maxLines == null || maxLines > 0);
|
||||
return new ui.ParagraphStyle(
|
||||
textAlign: textAlign,
|
||||
fontWeight: fontWeight,
|
||||
|
@ -84,6 +84,15 @@ class TextSelectionPoint {
|
||||
/// responsibility of higher layers and not handled by this object.
|
||||
class RenderEditable extends RenderBox {
|
||||
/// Creates a render object that implements the visual aspects of a text field.
|
||||
///
|
||||
/// If [showCursor] is not specified, then it defaults to hiding the cursor.
|
||||
///
|
||||
/// The [maxLines] property can be set to null to remove the restriction on
|
||||
/// the number of lines. By default, it is 1, meaning this is a single-line
|
||||
/// text field. If it is not null, it must be greater than zero.
|
||||
///
|
||||
/// The [offset] is required and must not be null. You can use [new
|
||||
/// ViewportOffset.zero] if you have no need for scrolling.
|
||||
RenderEditable({
|
||||
TextSpan text,
|
||||
TextAlign textAlign,
|
||||
@ -96,7 +105,7 @@ class RenderEditable extends RenderBox {
|
||||
@required ViewportOffset offset,
|
||||
this.onSelectionChanged,
|
||||
this.onCaretChanged,
|
||||
}) : assert(maxLines != null),
|
||||
}) : assert(maxLines == null || maxLines > 0),
|
||||
assert(textScaleFactor != null),
|
||||
assert(offset != null),
|
||||
_textPainter = new TextPainter(text: text, textAlign: textAlign, textScaleFactor: textScaleFactor),
|
||||
@ -180,13 +189,21 @@ class RenderEditable extends RenderBox {
|
||||
}
|
||||
|
||||
/// The maximum number of lines for the text to span, wrapping if necessary.
|
||||
///
|
||||
/// If this is 1 (the default), the text will not wrap, but will extend
|
||||
/// indefinitely instead.
|
||||
///
|
||||
/// If this is null, there is no limit to the number of lines.
|
||||
///
|
||||
/// When this is not null, the intrinsic height of the render object is the
|
||||
/// height of one line of text multiplied by this value. In other words, this
|
||||
/// also controls the height of the actual editing widget.
|
||||
int get maxLines => _maxLines;
|
||||
int _maxLines;
|
||||
/// The value may be null. If it is not null, then it must be greater than zero.
|
||||
set maxLines(int value) {
|
||||
assert(value != null);
|
||||
if (_maxLines == value)
|
||||
assert(value == null || value > 0);
|
||||
if (maxLines == value)
|
||||
return;
|
||||
_maxLines = value;
|
||||
markNeedsTextLayout();
|
||||
@ -261,7 +278,7 @@ class RenderEditable extends RenderBox {
|
||||
super.detach();
|
||||
}
|
||||
|
||||
bool get _isMultiline => maxLines > 1;
|
||||
bool get _isMultiline => maxLines != 1;
|
||||
|
||||
Axis get _viewportAxis => _isMultiline ? Axis.vertical : Axis.horizontal;
|
||||
|
||||
@ -359,14 +376,30 @@ class RenderEditable extends RenderBox {
|
||||
// This does not required the layout to be updated.
|
||||
double get _preferredLineHeight => _textPainter.preferredLineHeight;
|
||||
|
||||
double _preferredHeight(double width) {
|
||||
if (maxLines != null)
|
||||
return _preferredLineHeight * maxLines;
|
||||
if (width == double.INFINITY) {
|
||||
final String text = _textPainter.text.toPlainText();
|
||||
int lines = 1;
|
||||
for (int index = 0; index < text.length; index += 1) {
|
||||
if (text.codeUnitAt(index) == 0x0A) // count explicit line breaks
|
||||
lines += 1;
|
||||
}
|
||||
return _preferredLineHeight * lines;
|
||||
}
|
||||
_layoutText(width);
|
||||
return math.max(_preferredLineHeight, _textPainter.height);
|
||||
}
|
||||
|
||||
@override
|
||||
double computeMinIntrinsicHeight(double width) {
|
||||
return _preferredLineHeight;
|
||||
return _preferredHeight(width);
|
||||
}
|
||||
|
||||
@override
|
||||
double computeMaxIntrinsicHeight(double width) {
|
||||
return _preferredLineHeight * maxLines;
|
||||
return _preferredHeight(width);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -434,7 +467,7 @@ class RenderEditable extends RenderBox {
|
||||
return;
|
||||
final double caretMargin = _kCaretGap + _kCaretWidth;
|
||||
final double availableWidth = math.max(0.0, constraintWidth - caretMargin);
|
||||
final double maxWidth = _maxLines > 1 ? availableWidth : double.INFINITY;
|
||||
final double maxWidth = _isMultiline ? availableWidth : double.INFINITY;
|
||||
_textPainter.layout(minWidth: availableWidth, maxWidth: maxWidth);
|
||||
_textLayoutLastWidth = constraintWidth;
|
||||
}
|
||||
@ -444,9 +477,7 @@ class RenderEditable extends RenderBox {
|
||||
_layoutText(constraints.maxWidth);
|
||||
_caretPrototype = new Rect.fromLTWH(0.0, _kCaretHeightOffset, _kCaretWidth, _preferredLineHeight - 2.0 * _kCaretHeightOffset);
|
||||
_selectionRects = null;
|
||||
size = new Size(constraints.maxWidth, constraints.constrainHeight(
|
||||
_textPainter.height.clamp(_preferredLineHeight, _preferredLineHeight * _maxLines)
|
||||
));
|
||||
size = new Size(constraints.maxWidth, constraints.constrainHeight(_preferredHeight(constraints.maxWidth)));
|
||||
final Size contentSize = new Size(_textPainter.width + _kCaretGap + _kCaretWidth, _textPainter.height);
|
||||
final double _maxScrollExtent = _getMaxScrollExtent(contentSize);
|
||||
_hasVisualOverflow = _maxScrollExtent > 0.0;
|
||||
@ -506,13 +537,13 @@ class RenderEditable extends RenderBox {
|
||||
@override
|
||||
void debugFillDescription(List<String> description) {
|
||||
super.debugFillDescription(description);
|
||||
description.add('cursorColor: $_cursorColor');
|
||||
description.add('showCursor: $_showCursor');
|
||||
description.add('maxLines: $_maxLines');
|
||||
description.add('selectionColor: $_selectionColor');
|
||||
description.add('cursorColor: $cursorColor');
|
||||
description.add('showCursor: $showCursor');
|
||||
description.add('maxLines: $maxLines');
|
||||
description.add('selectionColor: $selectionColor');
|
||||
description.add('textScaleFactor: $textScaleFactor');
|
||||
description.add('selection: $_selection');
|
||||
description.add('offset: $_offset');
|
||||
description.add('selection: $selection');
|
||||
description.add('offset: $offset');
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -31,7 +31,11 @@ const String _kEllipsis = '\u2026';
|
||||
class RenderParagraph extends RenderBox {
|
||||
/// Creates a paragraph render object.
|
||||
///
|
||||
/// The [text], [overflow], and [softWrap] arguments must not be null.
|
||||
/// The [text], [overflow], [softWrap], and [textScaleFactor] arguments must
|
||||
/// not be null.
|
||||
///
|
||||
/// The [maxLines] property may be null (and indeed defaults to null), but if
|
||||
/// it is not null, it must be greater than zero.
|
||||
RenderParagraph(TextSpan text, {
|
||||
TextAlign textAlign,
|
||||
bool softWrap: true,
|
||||
@ -43,6 +47,7 @@ class RenderParagraph extends RenderBox {
|
||||
assert(softWrap != null),
|
||||
assert(overflow != null),
|
||||
assert(textScaleFactor != null),
|
||||
assert(maxLines == null || maxLines > 0),
|
||||
_softWrap = softWrap,
|
||||
_overflow = overflow,
|
||||
_textPainter = new TextPainter(
|
||||
@ -77,7 +82,11 @@ class RenderParagraph extends RenderBox {
|
||||
|
||||
/// 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.
|
||||
/// If false, the glyphs in the text will be positioned as if there was
|
||||
/// unlimited horizontal space.
|
||||
///
|
||||
/// If [softWrap] is false, [overflow] and [textAlign] may have unexpected
|
||||
/// effects.
|
||||
bool get softWrap => _softWrap;
|
||||
bool _softWrap;
|
||||
set softWrap(bool value) {
|
||||
@ -116,9 +125,11 @@ class RenderParagraph extends RenderBox {
|
||||
|
||||
/// 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].
|
||||
/// to [overflow] and [softWrap].
|
||||
int get maxLines => _textPainter.maxLines;
|
||||
/// The value may be null. If it is not null, then it must be greater than zero.
|
||||
set maxLines(int value) {
|
||||
assert(value == null || value > 0);
|
||||
if (_textPainter.maxLines == value)
|
||||
return;
|
||||
_textPainter.maxLines = value;
|
||||
@ -127,8 +138,7 @@ class RenderParagraph extends RenderBox {
|
||||
}
|
||||
|
||||
void _layoutText({ double minWidth: 0.0, double maxWidth: double.INFINITY }) {
|
||||
final bool wrap = _softWrap || (_overflow == TextOverflow.ellipsis && maxLines == null);
|
||||
_textPainter.layout(minWidth: minWidth, maxWidth: wrap ? maxWidth : double.INFINITY);
|
||||
_textPainter.layout(minWidth: minWidth, maxWidth: _softWrap ? maxWidth : double.INFINITY);
|
||||
}
|
||||
|
||||
void _layoutTextWithConstraints(BoxConstraints constraints) {
|
||||
|
@ -3028,7 +3028,11 @@ class Flow extends MultiChildRenderObjectWidget {
|
||||
class RichText extends LeafRenderObjectWidget {
|
||||
/// Creates a paragraph of rich text.
|
||||
///
|
||||
/// The [text], [softWrap], and [overflow] arguments must not be null.
|
||||
/// The [text], [softWrap], [overflow], nad [textScaleFactor] arguments must
|
||||
/// not be null.
|
||||
///
|
||||
/// The [maxLines] property may be null (and indeed defaults to null), but if
|
||||
/// it is not null, it must be greater than zero.
|
||||
const RichText({
|
||||
Key key,
|
||||
@required this.text,
|
||||
@ -3041,6 +3045,7 @@ class RichText extends LeafRenderObjectWidget {
|
||||
assert(softWrap != null),
|
||||
assert(overflow != null),
|
||||
assert(textScaleFactor != null),
|
||||
assert(maxLines == null || maxLines > 0),
|
||||
super(key: key);
|
||||
|
||||
/// The text to display in this widget.
|
||||
@ -3066,6 +3071,9 @@ class RichText extends LeafRenderObjectWidget {
|
||||
/// 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].
|
||||
///
|
||||
/// If this is 1, text will not wrap. Otherwise, text will be wrapped at the
|
||||
/// edge of the box.
|
||||
final int maxLines;
|
||||
|
||||
@override
|
||||
|
@ -128,6 +128,10 @@ class TextEditingController extends ValueNotifier<TextEditingValue> {
|
||||
class EditableText extends StatefulWidget {
|
||||
/// Creates a basic text input control.
|
||||
///
|
||||
/// The [maxLines] property can be set to null to remove the restriction on
|
||||
/// the number of lines. By default, it is 1, meaning this is a single-line
|
||||
/// text field. If it is not null, it must be greater than zero.
|
||||
///
|
||||
/// The [controller], [focusNode], [style], and [cursorColor] arguments must
|
||||
/// not be null.
|
||||
EditableText({
|
||||
@ -152,9 +156,9 @@ class EditableText extends StatefulWidget {
|
||||
assert(obscureText != null),
|
||||
assert(style != null),
|
||||
assert(cursorColor != null),
|
||||
assert(maxLines != null),
|
||||
assert(maxLines == null || maxLines > 0),
|
||||
assert(autofocus != null),
|
||||
inputFormatters = maxLines == 1
|
||||
inputFormatters = maxLines == 1
|
||||
? (
|
||||
<TextInputFormatter>[BlacklistingTextInputFormatter.singleLineFormatter]
|
||||
..addAll(inputFormatters ?? const Iterable<TextInputFormatter>.empty())
|
||||
@ -192,8 +196,12 @@ class EditableText extends StatefulWidget {
|
||||
final Color cursorColor;
|
||||
|
||||
/// The maximum number of lines for the text to span, wrapping if necessary.
|
||||
///
|
||||
/// If this is 1 (the default), the text will not wrap, but will scroll
|
||||
/// horizontally instead.
|
||||
///
|
||||
/// If this is null, there is no limit to the number of lines. If it is not
|
||||
/// null, the value must be greater than zero.
|
||||
final int maxLines;
|
||||
|
||||
/// Whether this input field should focus itself if nothing else is already focused.
|
||||
@ -218,7 +226,7 @@ class EditableText extends StatefulWidget {
|
||||
/// Called when the user indicates that they are done editing the text in the field.
|
||||
final ValueChanged<String> onSubmitted;
|
||||
|
||||
/// Optional input validation and formatting overrides. Formatters are run
|
||||
/// Optional input validation and formatting overrides. Formatters are run
|
||||
/// in the provided order when the text input changes.
|
||||
final List<TextInputFormatter> inputFormatters;
|
||||
|
||||
@ -340,7 +348,7 @@ class EditableTextState extends State<EditableText> implements TextInputClient {
|
||||
}
|
||||
|
||||
bool get _hasFocus => widget.focusNode.hasFocus;
|
||||
bool get _isMultiline => widget.maxLines > 1;
|
||||
bool get _isMultiline => widget.maxLines != 1;
|
||||
|
||||
// Calculate the new scroll offset so the cursor remains visible.
|
||||
double _getScrollOffsetForCaret(Rect caretRect) {
|
||||
@ -417,7 +425,7 @@ class EditableTextState extends State<EditableText> implements TextInputClient {
|
||||
void _handleSelectionChanged(TextSelection selection, RenderEditable renderObject, bool longPress) {
|
||||
widget.controller.selection = selection;
|
||||
|
||||
// Note that this will show the keyboard for all selection changes on the
|
||||
// This will show the keyboard for all selection changes on the
|
||||
// EditableWidget, not just changes triggered by user gestures.
|
||||
requestKeyboard();
|
||||
|
||||
|
@ -14,6 +14,14 @@ class DefaultTextStyle extends InheritedWidget {
|
||||
///
|
||||
/// Consider using [DefaultTextStyle.merge] to inherit styling information
|
||||
/// from the current default text style for a given [BuildContext].
|
||||
///
|
||||
/// 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.
|
||||
const DefaultTextStyle({
|
||||
Key key,
|
||||
@required this.style,
|
||||
@ -25,6 +33,7 @@ class DefaultTextStyle extends InheritedWidget {
|
||||
}) : assert(style != null),
|
||||
assert(softWrap != null),
|
||||
assert(overflow != null),
|
||||
assert(maxLines == null || maxLines > 0),
|
||||
assert(child != null),
|
||||
super(key: key, child: child);
|
||||
|
||||
@ -48,6 +57,15 @@ class DefaultTextStyle extends InheritedWidget {
|
||||
/// 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.
|
||||
///
|
||||
/// 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).
|
||||
static Widget merge({
|
||||
Key key,
|
||||
TextStyle style,
|
||||
@ -91,6 +109,12 @@ class DefaultTextStyle extends InheritedWidget {
|
||||
/// 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].
|
||||
///
|
||||
/// 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].
|
||||
final int maxLines;
|
||||
|
||||
/// The closest instance of this class that encloses the given context.
|
||||
@ -213,9 +237,17 @@ class Text extends StatelessWidget {
|
||||
/// [MediaQuery], or 1.0 if there is no [MediaQuery] in scope.
|
||||
final double textScaleFactor;
|
||||
|
||||
/// An optional maximum number of lines the text is allowed to take up.
|
||||
/// 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].
|
||||
///
|
||||
/// 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].
|
||||
final int maxLines;
|
||||
|
||||
@override
|
||||
@ -232,7 +264,7 @@ class Text extends StatelessWidget {
|
||||
maxLines: maxLines ?? defaultTextStyle.maxLines,
|
||||
text: new TextSpan(
|
||||
style: effectiveTextStyle,
|
||||
text: data
|
||||
text: data,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ void main() {
|
||||
'First line of text is '
|
||||
'Second line goes until '
|
||||
'Third line of stuff ';
|
||||
const String kFourLines =
|
||||
const String kMoreThanFourLines =
|
||||
kThreeLines +
|
||||
'Fourth line won\'t display and ends at';
|
||||
|
||||
@ -462,7 +462,7 @@ void main() {
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(builder(3));
|
||||
await tester.pumpWidget(builder(null));
|
||||
|
||||
RenderBox findInputBox() => tester.renderObject(find.byKey(textFieldKey));
|
||||
|
||||
@ -470,28 +470,44 @@ void main() {
|
||||
final Size emptyInputSize = inputBox.size;
|
||||
|
||||
await tester.enterText(find.byType(TextField), 'No wrapping here.');
|
||||
await tester.pumpWidget(builder(3));
|
||||
await tester.pumpWidget(builder(null));
|
||||
expect(findInputBox(), equals(inputBox));
|
||||
expect(inputBox.size, equals(emptyInputSize));
|
||||
|
||||
await tester.enterText(find.byType(TextField), kThreeLines);
|
||||
await tester.pumpWidget(builder(3));
|
||||
expect(findInputBox(), equals(inputBox));
|
||||
expect(inputBox.size, greaterThan(emptyInputSize));
|
||||
|
||||
final Size threeLineInputSize = inputBox.size;
|
||||
|
||||
await tester.enterText(find.byType(TextField), kThreeLines);
|
||||
await tester.pumpWidget(builder(null));
|
||||
expect(findInputBox(), equals(inputBox));
|
||||
expect(inputBox.size, greaterThan(emptyInputSize));
|
||||
|
||||
await tester.enterText(find.byType(TextField), kThreeLines);
|
||||
await tester.pumpWidget(builder(null));
|
||||
expect(findInputBox(), equals(inputBox));
|
||||
expect(inputBox.size, threeLineInputSize);
|
||||
|
||||
// An extra line won't increase the size because we max at 3.
|
||||
await tester.enterText(find.byType(TextField), kFourLines);
|
||||
await tester.enterText(find.byType(TextField), kMoreThanFourLines);
|
||||
await tester.pumpWidget(builder(3));
|
||||
expect(findInputBox(), equals(inputBox));
|
||||
expect(inputBox.size, threeLineInputSize);
|
||||
|
||||
// But now it will.
|
||||
await tester.enterText(find.byType(TextField), kFourLines);
|
||||
// But now it will... but it will max at four
|
||||
await tester.enterText(find.byType(TextField), kMoreThanFourLines);
|
||||
await tester.pumpWidget(builder(4));
|
||||
expect(findInputBox(), equals(inputBox));
|
||||
expect(inputBox.size, greaterThan(threeLineInputSize));
|
||||
|
||||
final Size fourLineInputSize = inputBox.size;
|
||||
|
||||
// Now it won't max out until the end
|
||||
await tester.pumpWidget(builder(null));
|
||||
expect(findInputBox(), equals(inputBox));
|
||||
expect(inputBox.size, greaterThan(fourLineInputSize));
|
||||
});
|
||||
|
||||
testWidgets('Can drag handles to change selection in multiline', (WidgetTester tester) async {
|
||||
@ -594,7 +610,7 @@ void main() {
|
||||
await tester.pumpWidget(builder());
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
|
||||
await tester.enterText(find.byType(TextField), kFourLines);
|
||||
await tester.enterText(find.byType(TextField), kMoreThanFourLines);
|
||||
|
||||
await tester.pumpWidget(builder());
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
@ -603,8 +619,8 @@ void main() {
|
||||
final RenderBox inputBox = findInputBox();
|
||||
|
||||
// Check that the last line of text is not displayed.
|
||||
final Offset firstPos = textOffsetToPosition(tester, kFourLines.indexOf('First'));
|
||||
final Offset fourthPos = textOffsetToPosition(tester, kFourLines.indexOf('Fourth'));
|
||||
final Offset firstPos = textOffsetToPosition(tester, kMoreThanFourLines.indexOf('First'));
|
||||
final Offset fourthPos = textOffsetToPosition(tester, kMoreThanFourLines.indexOf('Fourth'));
|
||||
expect(firstPos.dx, fourthPos.dx);
|
||||
expect(firstPos.dy, lessThan(fourthPos.dy));
|
||||
expect(inputBox.hitTest(new HitTestResult(), position: inputBox.globalToLocal(firstPos)), isTrue);
|
||||
@ -622,8 +638,8 @@ void main() {
|
||||
await tester.pump();
|
||||
|
||||
// Now the first line is scrolled up, and the fourth line is visible.
|
||||
Offset newFirstPos = textOffsetToPosition(tester, kFourLines.indexOf('First'));
|
||||
Offset newFourthPos = textOffsetToPosition(tester, kFourLines.indexOf('Fourth'));
|
||||
Offset newFirstPos = textOffsetToPosition(tester, kMoreThanFourLines.indexOf('First'));
|
||||
Offset newFourthPos = textOffsetToPosition(tester, kMoreThanFourLines.indexOf('Fourth'));
|
||||
|
||||
expect(newFirstPos.dy, lessThan(firstPos.dy));
|
||||
expect(inputBox.hitTest(new HitTestResult(), position: inputBox.globalToLocal(newFirstPos)), isFalse);
|
||||
@ -633,7 +649,7 @@ void main() {
|
||||
|
||||
// Long press the 'i' in 'Fourth line' to select the word.
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
final Offset untilPos = textOffsetToPosition(tester, kFourLines.indexOf('Fourth line')+8);
|
||||
final Offset untilPos = textOffsetToPosition(tester, kMoreThanFourLines.indexOf('Fourth line')+8);
|
||||
gesture = await tester.startGesture(untilPos, pointer: 7);
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
await gesture.up();
|
||||
@ -645,7 +661,7 @@ void main() {
|
||||
|
||||
// Drag the left handle to the first line, just after 'First'.
|
||||
final Offset handlePos = endpoints[0].point + const Offset(-1.0, 1.0);
|
||||
final Offset newHandlePos = textOffsetToPosition(tester, kFourLines.indexOf('First') + 5);
|
||||
final Offset newHandlePos = textOffsetToPosition(tester, kMoreThanFourLines.indexOf('First') + 5);
|
||||
gesture = await tester.startGesture(handlePos, pointer: 7);
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
await gesture.moveTo(newHandlePos + const Offset(0.0, -10.0));
|
||||
@ -655,8 +671,8 @@ void main() {
|
||||
|
||||
// The text should have scrolled up with the handle to keep the active
|
||||
// cursor visible, back to its original position.
|
||||
newFirstPos = textOffsetToPosition(tester, kFourLines.indexOf('First'));
|
||||
newFourthPos = textOffsetToPosition(tester, kFourLines.indexOf('Fourth'));
|
||||
newFirstPos = textOffsetToPosition(tester, kMoreThanFourLines.indexOf('First'));
|
||||
newFourthPos = textOffsetToPosition(tester, kMoreThanFourLines.indexOf('Fourth'));
|
||||
expect(newFirstPos.dy, firstPos.dy);
|
||||
expect(inputBox.hitTest(new HitTestResult(), position: inputBox.globalToLocal(newFirstPos)), isTrue);
|
||||
expect(inputBox.hitTest(new HitTestResult(), position: inputBox.globalToLocal(newFourthPos)), isFalse);
|
||||
|
@ -79,12 +79,13 @@ void main() {
|
||||
const TextSpan(
|
||||
text: 'This\n' // 4 characters * 10px font size = 40px width on the first line
|
||||
'is a wrapping test. It should wrap at manual newlines, and if softWrap is true, also at spaces.',
|
||||
style: const TextStyle(fontFamily: 'Ahem', fontSize: 10.0)),
|
||||
style: const TextStyle(fontFamily: 'Ahem', fontSize: 10.0),
|
||||
),
|
||||
maxLines: 1,
|
||||
softWrap: true,
|
||||
);
|
||||
|
||||
void relayoutWith({int maxLines, bool softWrap, TextOverflow overflow}) {
|
||||
void relayoutWith({ int maxLines, bool softWrap, TextOverflow overflow }) {
|
||||
paragraph
|
||||
..maxLines = maxLines
|
||||
..softWrap = softWrap
|
||||
@ -147,5 +148,34 @@ void main() {
|
||||
relayoutWith(maxLines: 100, softWrap: true, overflow: TextOverflow.fade);
|
||||
expect(paragraph.debugHasOverflowShader, isFalse);
|
||||
});
|
||||
|
||||
|
||||
test('maxLines', () {
|
||||
final RenderParagraph paragraph = new RenderParagraph(
|
||||
const TextSpan(
|
||||
text: 'How do you write like you\'re running out of time? Write day and night like you\'re running out of time?',
|
||||
// 0123456789 0123456789 012 345 0123456 012345 01234 012345678 012345678 0123 012 345 0123456 012345 01234
|
||||
// 0 1 2 3 4 5 6 7 8 9 10 11 12
|
||||
style: const TextStyle(fontFamily: 'Ahem', fontSize: 10.0),
|
||||
),
|
||||
);
|
||||
layout(paragraph, constraints: const BoxConstraints(maxWidth: 100.0));
|
||||
void layoutAt(int maxLines) {
|
||||
paragraph.maxLines = maxLines;
|
||||
pumpFrame();
|
||||
}
|
||||
|
||||
layoutAt(null);
|
||||
expect(paragraph.size.height, 130.0);
|
||||
|
||||
layoutAt(1);
|
||||
expect(paragraph.size.height, 10.0);
|
||||
|
||||
layoutAt(2);
|
||||
expect(paragraph.size.height, 20.0);
|
||||
|
||||
layoutAt(3);
|
||||
expect(paragraph.size.height, 30.0);
|
||||
});
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user