Unverified Commit 2d2bb6bf authored by Justin McCandless's avatar Justin McCandless Committed by GitHub

CupertinoTextField vertical alignment (#34723)

CupertinoTextField now supports vertical alignment via the textAlignVertical parameter.
parent 4cd12fc8
...@@ -174,6 +174,7 @@ class CupertinoTextField extends StatefulWidget { ...@@ -174,6 +174,7 @@ class CupertinoTextField extends StatefulWidget {
this.style, this.style,
this.strutStyle, this.strutStyle,
this.textAlign = TextAlign.start, this.textAlign = TextAlign.start,
this.textAlignVertical,
this.readOnly = false, this.readOnly = false,
this.showCursor, this.showCursor,
this.autofocus = false, this.autofocus = false,
...@@ -324,6 +325,9 @@ class CupertinoTextField extends StatefulWidget { ...@@ -324,6 +325,9 @@ class CupertinoTextField extends StatefulWidget {
/// {@macro flutter.widgets.editableText.textAlign} /// {@macro flutter.widgets.editableText.textAlign}
final TextAlign textAlign; final TextAlign textAlign;
/// {@macro flutter.material.inputDecorator.textAlignVertical}
final TextAlignVertical textAlignVertical;
/// {@macro flutter.widgets.editableText.readOnly} /// {@macro flutter.widgets.editableText.readOnly}
final bool readOnly; final bool readOnly;
...@@ -491,10 +495,13 @@ class CupertinoTextField extends StatefulWidget { ...@@ -491,10 +495,13 @@ class CupertinoTextField extends StatefulWidget {
properties.add(FlagProperty('selectionEnabled', value: selectionEnabled, defaultValue: true, ifFalse: 'selection disabled')); properties.add(FlagProperty('selectionEnabled', value: selectionEnabled, defaultValue: true, ifFalse: 'selection disabled'));
properties.add(DiagnosticsProperty<ScrollController>('scrollController', scrollController, defaultValue: null)); properties.add(DiagnosticsProperty<ScrollController>('scrollController', scrollController, defaultValue: null));
properties.add(DiagnosticsProperty<ScrollPhysics>('scrollPhysics', scrollPhysics, defaultValue: null)); properties.add(DiagnosticsProperty<ScrollPhysics>('scrollPhysics', scrollPhysics, defaultValue: null));
properties.add(EnumProperty<TextAlign>('textAlign', textAlign, defaultValue: TextAlign.start));
properties.add(DiagnosticsProperty<TextAlignVertical>('textAlignVertical', textAlignVertical, defaultValue: null));
} }
} }
class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticKeepAliveClientMixin { class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticKeepAliveClientMixin {
final GlobalKey _clearGlobalKey = GlobalKey();
final GlobalKey<EditableTextState> _editableTextKey = GlobalKey<EditableTextState>(); final GlobalKey<EditableTextState> _editableTextKey = GlobalKey<EditableTextState>();
TextEditingController _controller; TextEditingController _controller;
...@@ -584,6 +591,18 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK ...@@ -584,6 +591,18 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
} }
void _handleSingleTapUp(TapUpDetails details) { void _handleSingleTapUp(TapUpDetails details) {
// Because TextSelectionGestureDetector listens to taps that happen on
// widgets in front of it, tapping the clear button will also trigger
// this handler. If the the clear button widget recognizes the up event,
// then do not handle it.
if (_clearGlobalKey.currentContext != null) {
final RenderBox renderBox = _clearGlobalKey.currentContext.findRenderObject();
final Offset localOffset = renderBox.globalToLocal(details.globalPosition);
if(renderBox.hitTest(BoxHitTestResult(), position: localOffset)) {
return;
}
}
if (widget.selectionEnabled) { if (widget.selectionEnabled) {
_renderEditable.selectWordEdge(cause: SelectionChangedCause.tap); _renderEditable.selectWordEdge(cause: SelectionChangedCause.tap);
} }
...@@ -719,16 +738,31 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK ...@@ -719,16 +738,31 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
); );
} }
// True if any surrounding decoration widgets will be shown.
bool get _hasDecoration {
return widget.placeholder != null ||
widget.clearButtonMode != OverlayVisibilityMode.never ||
widget.prefix != null ||
widget.suffix != null;
}
// Provide default behavior if widget.textAlignVertical is not set.
// CupertinoTextField has top alignment by default, unless it has decoration
// like a prefix or suffix, in which case it's aligned to the center.
TextAlignVertical get _textAlignVertical {
if (widget.textAlignVertical != null) {
return widget.textAlignVertical;
}
return _hasDecoration ? TextAlignVertical.center : TextAlignVertical.top;
}
Widget _addTextDependentAttachments(Widget editableText, TextStyle textStyle, TextStyle placeholderStyle) { Widget _addTextDependentAttachments(Widget editableText, TextStyle textStyle, TextStyle placeholderStyle) {
assert(editableText != null); assert(editableText != null);
assert(textStyle != null); assert(textStyle != null);
assert(placeholderStyle != null); assert(placeholderStyle != null);
// If there are no surrounding widgets, just return the core editable text // If there are no surrounding widgets, just return the core editable text
// part. // part.
if (widget.placeholder == null && if (!_hasDecoration) {
widget.clearButtonMode == OverlayVisibilityMode.never &&
widget.prefix == null &&
widget.suffix == null) {
return editableText; return editableText;
} }
...@@ -776,6 +810,7 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK ...@@ -776,6 +810,7 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
} else if (_showClearButton(text)) { } else if (_showClearButton(text)) {
rowChildren.add( rowChildren.add(
GestureDetector( GestureDetector(
key: _clearGlobalKey,
onTap: widget.enabled ?? true ? () { onTap: widget.enabled ?? true ? () {
// Special handle onChanged for ClearButton // Special handle onChanged for ClearButton
// Also call onChanged when the clear button is tapped. // Also call onChanged when the clear button is tapped.
...@@ -825,20 +860,7 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK ...@@ -825,20 +860,7 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
? widget.decoration ? widget.decoration
: widget.decoration?.copyWith(color: widget.decoration?.color ?? disabledColor); : widget.decoration?.copyWith(color: widget.decoration?.color ?? disabledColor);
final Widget paddedEditable = TextSelectionGestureDetector( final Widget paddedEditable = Padding(
onTapDown: _handleTapDown,
onForcePressStart: _handleForcePressStarted,
onForcePressEnd: _handleForcePressEnded,
onSingleTapUp: _handleSingleTapUp,
onSingleLongTapStart: _handleSingleLongTapStart,
onSingleLongTapMoveUpdate: _handleSingleLongTapMoveUpdate,
onSingleLongTapEnd: _handleSingleLongTapEnd,
onDoubleTapDown: _handleDoubleTapDown,
onDragSelectionStart: _handleMouseDragSelectionStart,
onDragSelectionUpdate: _handleMouseDragSelectionUpdate,
onDragSelectionEnd: _handleMouseDragSelectionEnd,
behavior: HitTestBehavior.translucent,
child: Padding(
padding: widget.padding, padding: widget.padding,
child: RepaintBoundary( child: RepaintBoundary(
child: EditableText( child: EditableText(
...@@ -884,7 +906,6 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK ...@@ -884,7 +906,6 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
enableInteractiveSelection: widget.enableInteractiveSelection, enableInteractiveSelection: widget.enableInteractiveSelection,
), ),
), ),
),
); );
return Semantics( return Semantics(
...@@ -898,9 +919,28 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK ...@@ -898,9 +919,28 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
ignoring: !enabled, ignoring: !enabled,
child: Container( child: Container(
decoration: effectiveDecoration, decoration: effectiveDecoration,
child: TextSelectionGestureDetector(
onTapDown: _handleTapDown,
onForcePressStart: _handleForcePressStarted,
onForcePressEnd: _handleForcePressEnded,
onSingleTapUp: _handleSingleTapUp,
onSingleLongTapStart: _handleSingleLongTapStart,
onSingleLongTapMoveUpdate: _handleSingleLongTapMoveUpdate,
onSingleLongTapEnd: _handleSingleLongTapEnd,
onDoubleTapDown: _handleDoubleTapDown,
onDragSelectionStart: _handleMouseDragSelectionStart,
onDragSelectionUpdate: _handleMouseDragSelectionUpdate,
onDragSelectionEnd: _handleMouseDragSelectionEnd,
behavior: HitTestBehavior.translucent,
child: Align(
alignment: Alignment(-1.0, _textAlignVertical.y),
widthFactor: 1.0,
heightFactor: 1.0,
child: _addTextDependentAttachments(paddedEditable, textStyle, placeholderStyle), child: _addTextDependentAttachments(paddedEditable, textStyle, placeholderStyle),
), ),
), ),
),
),
); );
} }
} }
...@@ -3576,42 +3576,3 @@ class InputDecorationTheme extends Diagnosticable { ...@@ -3576,42 +3576,3 @@ class InputDecorationTheme extends Diagnosticable {
properties.add(DiagnosticsProperty<bool>('alignLabelWithHint', alignLabelWithHint, defaultValue: defaultTheme.alignLabelWithHint)); properties.add(DiagnosticsProperty<bool>('alignLabelWithHint', alignLabelWithHint, defaultValue: defaultTheme.alignLabelWithHint));
} }
} }
/// The vertical alignment of text within an input.
///
/// A single [y] value that can range from -1.0 to 1.0. -1.0 aligns to the top
/// of the input so that the top of the first line of text fits within the input
/// and its padding. 0.0 aligns to the center of the input. 1.0 aligns so that
/// the bottom of the last line of text aligns with the bottom interior edge of
/// the input.
///
/// See also:
///
/// * [TextField.textAlignVertical], which is passed on to the [InputDecorator].
/// * [InputDecorator.textAlignVertical], which defines the alignment of
/// prefix, input, and suffix, within the [InputDecorator].
class TextAlignVertical {
/// Construct TextAlignVertical from any given y value.
const TextAlignVertical({
@required this.y,
}) : assert(y != null),
assert(y >= -1.0 && y <= 1.0);
/// A value ranging from -1.0 to 1.0 that defines the topmost and bottommost
/// locations of the top and bottom of the input text box.
final double y;
/// Aligns a TextField's input Text with the topmost location within the
/// TextField.
static const TextAlignVertical top = TextAlignVertical(y: -1.0);
/// Aligns a TextField's input Text to the center of the TextField.
static const TextAlignVertical center = TextAlignVertical(y: 0.0);
/// Aligns a TextField's input Text with the bottommost location within the
/// TextField.
static const TextAlignVertical bottom = TextAlignVertical(y: 1.0);
@override
String toString() {
return '$runtimeType(y: $y)';
}
}
...@@ -633,3 +633,44 @@ class _MixedAlignment extends AlignmentGeometry { ...@@ -633,3 +633,44 @@ class _MixedAlignment extends AlignmentGeometry {
return null; return null;
} }
} }
/// The vertical alignment of text within an input box.
///
/// A single [y] value that can range from -1.0 to 1.0. -1.0 aligns to the top
/// of an input box so that the top of the first line of text fits within the
/// box and its padding. 0.0 aligns to the center of the box. 1.0 aligns so that
/// the bottom of the last line of text aligns with the bottom interior edge of
/// the input box.
///
/// See also:
///
/// * [TextField.textAlignVertical], which is passed on to the [InputDecorator].
/// * [CupertinoTextField.textAlignVertical], which behaves in the same way as
/// the parameter in TextField.
/// * [InputDecorator.textAlignVertical], which defines the alignment of
/// prefix, input, and suffix within an [InputDecorator].
class TextAlignVertical {
/// Creates a TextAlignVertical from any y value between -1.0 and 1.0.
const TextAlignVertical({
@required this.y,
}) : assert(y != null),
assert(y >= -1.0 && y <= 1.0);
/// A value ranging from -1.0 to 1.0 that defines the topmost and bottommost
/// locations of the top and bottom of the input box.
final double y;
/// Aligns a TextField's input Text with the topmost location within a
/// TextField's input box.
static const TextAlignVertical top = TextAlignVertical(y: -1.0);
/// Aligns a TextField's input Text to the center of the TextField.
static const TextAlignVertical center = TextAlignVertical(y: 0.0);
/// Aligns a TextField's input Text with the bottommost location within a
/// TextField.
static const TextAlignVertical bottom = TextAlignVertical(y: 1.0);
@override
String toString() {
return '$runtimeType(y: $y)';
}
}
...@@ -1252,7 +1252,7 @@ class RenderEditable extends RenderBox { ...@@ -1252,7 +1252,7 @@ class RenderEditable extends RenderBox {
double get preferredLineHeight => _textPainter.preferredLineHeight; double get preferredLineHeight => _textPainter.preferredLineHeight;
double _preferredHeight(double width) { double _preferredHeight(double width) {
// Lock height to maxLines if needed // Lock height to maxLines if needed.
final bool lockedMax = maxLines != null && minLines == null; final bool lockedMax = maxLines != null && minLines == null;
final bool lockedBoth = minLines != null && minLines == maxLines; final bool lockedBoth = minLines != null && minLines == maxLines;
final bool singleLine = maxLines == 1; final bool singleLine = maxLines == 1;
...@@ -1260,7 +1260,7 @@ class RenderEditable extends RenderBox { ...@@ -1260,7 +1260,7 @@ class RenderEditable extends RenderBox {
return preferredLineHeight * maxLines; return preferredLineHeight * maxLines;
} }
// Clamp height to minLines or maxLines if needed // Clamp height to minLines or maxLines if needed.
final bool minLimited = minLines != null && minLines > 1; final bool minLimited = minLines != null && minLines > 1;
final bool maxLimited = maxLines != null; final bool maxLimited = maxLines != null;
if (minLimited || maxLimited) { if (minLimited || maxLimited) {
...@@ -1273,7 +1273,7 @@ class RenderEditable extends RenderBox { ...@@ -1273,7 +1273,7 @@ class RenderEditable extends RenderBox {
} }
} }
// Set the height based on the content // Set the height based on the content.
if (width == double.infinity) { if (width == double.infinity) {
final String text = _textPainter.text.toPlainText(); final String text = _textPainter.text.toPlainText();
int lines = 1; int lines = 1;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment