blob: 0acfe03210c3b93133cfc76656d5ee0879e16c2e [file] [log] [blame]
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:ui' as ui show BoxHeightStyle, BoxWidthStyle;
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'colors.dart';
import 'icons.dart';
import 'text_selection.dart';
import 'theme.dart';
export 'package:flutter/services.dart' show TextInputType, TextInputAction, TextCapitalization, SmartQuotesType, SmartDashesType;
// Value inspected from Xcode 11 & iOS 13.0 Simulator.
const BorderSide _kDefaultRoundedBorderSide = BorderSide(
color: CupertinoDynamicColor.withBrightness(
color: Color(0x33000000),
darkColor: Color(0x33FFFFFF),
style: BorderStyle.solid,
width: 0.0,
const Border _kDefaultRoundedBorder = Border(
top: _kDefaultRoundedBorderSide,
bottom: _kDefaultRoundedBorderSide,
left: _kDefaultRoundedBorderSide,
right: _kDefaultRoundedBorderSide,
const BoxDecoration _kDefaultRoundedBorderDecoration = BoxDecoration(
color: CupertinoDynamicColor.withBrightness(
color: CupertinoColors.white,
border: _kDefaultRoundedBorder,
borderRadius: BorderRadius.all(Radius.circular(5.0)),
const Color _kDisabledBackground = CupertinoDynamicColor.withBrightness(
color: Color(0xFFFAFAFA),
darkColor: Color(0xFF050505),
// Value inspected from Xcode 11 & iOS 13.0 Simulator.
// Note it may not be consistent with
const CupertinoDynamicColor _kClearButtonColor = CupertinoDynamicColor.withBrightness(
color: Color(0xFF636366),
darkColor: Color(0xFFAEAEB2),
// An eyeballed value that moves the cursor slightly left of where it is
// rendered for text on Android so it's positioning more accurately matches the
// native iOS text cursor positioning.
// This value is in device pixels, not logical pixels as is typically used
// throughout the codebase.
const int _iOSHorizontalCursorOffsetPixels = -2;
/// Visibility of text field overlays based on the state of the current text entry.
/// Used to toggle the visibility behavior of the optional decorating widgets
/// surrounding the [EditableText] such as the clear text button.
enum OverlayVisibilityMode {
/// Overlay will never appear regardless of the text entry state.
/// Overlay will only appear when the current text entry is not empty.
/// This includes prefilled text that the user did not type in manually. But
/// does not include text in placeholders.
/// Overlay will only appear when the current text entry is empty.
/// This also includes not having prefilled text that the user did not type
/// in manually. Texts in placeholders are ignored.
/// Always show the overlay regardless of the text entry state.
class _CupertinoTextFieldSelectionGestureDetectorBuilder extends TextSelectionGestureDetectorBuilder {
@required _CupertinoTextFieldState state,
}) : _state = state,
super(delegate: state);
final _CupertinoTextFieldState _state;
void onSingleTapUp(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 clear button widget recognizes the up event,
// then do not handle it.
if (_state._clearGlobalKey.currentContext != null) {
final RenderBox renderBox = _state._clearGlobalKey.currentContext.findRenderObject() as RenderBox;
final Offset localOffset = renderBox.globalToLocal(details.globalPosition);
if (renderBox.hitTest(BoxHitTestResult(), position: localOffset)) {
if (_state.widget.onTap != null)
void onDragSelectionEnd(DragEndDetails details) {
/// An iOS-style text field.
/// A text field lets the user enter text, either with a hardware keyboard or with
/// an onscreen keyboard.
/// This widget corresponds to both a `UITextField` and an editable `UITextView`
/// on iOS.
/// The text field calls the [onChanged] callback whenever the user changes the
/// text in the field. If the user indicates that they are done typing in the
/// field (e.g., by pressing a button on the soft keyboard), the text field
/// calls the [onSubmitted] callback.
/// To control the text that is displayed in the text field, use the
/// [controller]. For example, to set the initial value of the text field, use
/// a [controller] that already contains some text such as:
/// {@tool snippet}
/// ```dart
/// class MyPrefilledText extends StatefulWidget {
/// @override
/// _MyPrefilledTextState createState() => _MyPrefilledTextState();
/// }
/// class _MyPrefilledTextState extends State<MyPrefilledText> {
/// TextEditingController _textController;
/// @override
/// void initState() {
/// super.initState();
/// _textController = TextEditingController(text: 'initial text');
/// }
/// @override
/// Widget build(BuildContext context) {
/// return CupertinoTextField(controller: _textController);
/// }
/// }
/// ```
/// {@end-tool}
/// The [controller] can also control the selection and composing region (and to
/// observe changes to the text, selection, and composing region).
/// The text field has an overridable [decoration] that, by default, draws a
/// rounded rectangle border around the text field. If you set the [decoration]
/// property to null, the decoration will be removed entirely.
/// Remember to call [TextEditingController.dispose] when it is no longer
/// needed. This will ensure we discard any resources used by the object.
/// See also:
/// * <>
/// * [TextField], an alternative text field widget that follows the Material
/// Design UI conventions.
/// * [EditableText], which is the raw text editing control at the heart of a
/// [TextField].
/// * Learn how to use a [TextEditingController] in one of our [cookbook recipes](
class CupertinoTextField extends StatefulWidget {
/// Creates an iOS-style text field.
/// To provide a prefilled text entry, pass in a [TextEditingController] with
/// an initial value to the [controller] parameter.
/// To provide a hint placeholder text that appears when the text entry is
/// empty, pass a [String] to the [placeholder] parameter.
/// The [maxLines] property can be set to null to remove the restriction on
/// the number of lines. In this mode, the intrinsic height of the widget will
/// grow as the number of lines of text grows. By default, it is `1`, meaning
/// this is a single-line text field and will scroll horizontally when
/// overflown. [maxLines] must not be zero.
/// The text cursor is not shown if [showCursor] is false or if [showCursor]
/// is null (the default) and [readOnly] is true.
/// If specified, the [maxLength] property must be greater than zero.
/// The [selectionHeightStyle] and [selectionWidthStyle] properties allow
/// changing the shape of the selection highlighting. These properties default
/// to [ui.BoxHeightStyle.tight] and [ui.BoxWidthStyle.tight] respectively and
/// must not be null.
/// The [autocorrect], [autofocus], [clearButtonMode], [dragStartBehavior],
/// [expands], [maxLengthEnforced], [obscureText], [prefixMode], [readOnly],
/// [scrollPadding], [suffixMode], [textAlign], [selectionHeightStyle],
/// [selectionWidthStyle], and [enableSuggestions] properties must not be null.
/// See also:
/// * [minLines]
/// * [expands], to allow the widget to size itself to its parent's height.
/// * [maxLength], which discusses the precise meaning of "number of
/// characters" and how it may differ from the intuitive meaning.
const CupertinoTextField({
Key key,
this.decoration = _kDefaultRoundedBorderDecoration,
this.padding = const EdgeInsets.all(6.0),
this.placeholderStyle = const TextStyle(
fontWeight: FontWeight.w400,
color: CupertinoColors.placeholderText,
this.prefixMode = OverlayVisibilityMode.always,
this.suffixMode = OverlayVisibilityMode.always,
this.clearButtonMode = OverlayVisibilityMode.never,
TextInputType keyboardType,
this.textCapitalization = TextCapitalization.none,,
this.textAlign = TextAlign.start,
this.readOnly = false,
ToolbarOptions toolbarOptions,
this.autofocus = false,
this.obscureText = false,
this.autocorrect = true,
SmartDashesType smartDashesType,
SmartQuotesType smartQuotesType,
this.enableSuggestions = true,
this.maxLines = 1,
this.expands = false,
this.maxLengthEnforced = true,
this.cursorWidth = 2.0,
this.cursorRadius = const Radius.circular(2.0),
this.selectionHeightStyle = ui.BoxHeightStyle.tight,
this.selectionWidthStyle = ui.BoxWidthStyle.tight,
this.scrollPadding = const EdgeInsets.all(20.0),
this.dragStartBehavior = DragStartBehavior.start,
this.enableInteractiveSelection = true,
}) : assert(textAlign != null),
assert(readOnly != null),
assert(autofocus != null),
assert(obscureText != null),
assert(autocorrect != null),
smartDashesType = smartDashesType ?? (obscureText ? SmartDashesType.disabled : SmartDashesType.enabled),
smartQuotesType = smartQuotesType ?? (obscureText ? SmartQuotesType.disabled : SmartQuotesType.enabled),
assert(enableSuggestions != null),
assert(maxLengthEnforced != null),
assert(scrollPadding != null),
assert(dragStartBehavior != null),
assert(selectionHeightStyle != null),
assert(selectionWidthStyle != null),
assert(maxLines == null || maxLines > 0),
assert(minLines == null || minLines > 0),
(maxLines == null) || (minLines == null) || (maxLines >= minLines),
"minLines can't be greater than maxLines",
assert(expands != null),
!expands || (maxLines == null && minLines == null),
'minLines and maxLines must be null when expands is true.',
assert(!obscureText || maxLines == 1, 'Obscured fields cannot be multiline.'),
assert(maxLength == null || maxLength > 0),
assert(clearButtonMode != null),
assert(prefixMode != null),
assert(suffixMode != null),
keyboardType = keyboardType ?? (maxLines == 1 ? TextInputType.text : TextInputType.multiline),
toolbarOptions = toolbarOptions ?? (obscureText ?
const ToolbarOptions(
selectAll: true,
paste: true,
) :
const ToolbarOptions(
copy: true,
cut: true,
selectAll: true,
paste: true,
super(key: key);
/// Controls the text being edited.
/// If null, this widget will create its own [TextEditingController].
final TextEditingController controller;
/// {@macro flutter.widgets.Focus.focusNode}
final FocusNode focusNode;
/// Controls the [BoxDecoration] of the box behind the text input.
/// Defaults to having a rounded rectangle grey border and can be null to have
/// no box decoration.
final BoxDecoration decoration;
/// Padding around the text entry area between the [prefix] and [suffix]
/// or the clear button when [clearButtonMode] is not never.
/// Defaults to a padding of 6 pixels on all sides and can be null.
final EdgeInsetsGeometry padding;
/// A lighter colored placeholder hint that appears on the first line of the
/// text field when the text entry is empty.
/// Defaults to having no placeholder text.
/// The text style of the placeholder text matches that of the text field's
/// main text entry except a lighter font weight and a grey font color.
final String placeholder;
/// The style to use for the placeholder text.
/// The [placeholderStyle] is merged with the [style] [TextStyle] when applied
/// to the [placeholder] text. To avoid merging with [style], specify
/// [TextStyle.inherit] as false.
/// Defaults to the [style] property with w300 font weight and grey color.
/// If specifically set to null, placeholder's style will be the same as [style].
final TextStyle placeholderStyle;
/// An optional [Widget] to display before the text.
final Widget prefix;
/// Controls the visibility of the [prefix] widget based on the state of
/// text entry when the [prefix] argument is not null.
/// Defaults to [OverlayVisibilityMode.always] and cannot be null.
/// Has no effect when [prefix] is null.
final OverlayVisibilityMode prefixMode;
/// An optional [Widget] to display after the text.
final Widget suffix;
/// Controls the visibility of the [suffix] widget based on the state of
/// text entry when the [suffix] argument is not null.
/// Defaults to [OverlayVisibilityMode.always] and cannot be null.
/// Has no effect when [suffix] is null.
final OverlayVisibilityMode suffixMode;
/// Show an iOS-style clear button to clear the current text entry.
/// Can be made to appear depending on various text states of the
/// [TextEditingController].
/// Will only appear if no [suffix] widget is appearing.
/// Defaults to never appearing and cannot be null.
final OverlayVisibilityMode clearButtonMode;
/// {@macro flutter.widgets.editableText.keyboardType}
final TextInputType keyboardType;
/// The type of action button to use for the keyboard.
/// Defaults to [TextInputAction.newline] if [keyboardType] is
/// [TextInputType.multiline] and [TextInputAction.done] otherwise.
final TextInputAction textInputAction;
/// {@macro flutter.widgets.editableText.textCapitalization}
final TextCapitalization textCapitalization;
/// The style to use for the text being edited.
/// Also serves as a base for the [placeholder] text's style.
/// Defaults to the standard iOS font style from [CupertinoTheme] if null.
final TextStyle style;
/// {@macro flutter.widgets.editableText.strutStyle}
final StrutStyle strutStyle;
/// {@macro flutter.widgets.editableText.textAlign}
final TextAlign textAlign;
/// Configuration of toolbar options.
/// If not set, select all and paste will default to be enabled. Copy and cut
/// will be disabled if [obscureText] is true. If [readOnly] is true,
/// paste and cut will be disabled regardless.
final ToolbarOptions toolbarOptions;
/// {@macro flutter.widgets.inputDecorator.textAlignVertical}
final TextAlignVertical textAlignVertical;
/// {@macro flutter.widgets.editableText.readOnly}
final bool readOnly;
/// {@macro flutter.widgets.editableText.showCursor}
final bool showCursor;
/// {@macro flutter.widgets.editableText.autofocus}
final bool autofocus;
/// {@macro flutter.widgets.editableText.obscureText}
final bool obscureText;
/// {@macro flutter.widgets.editableText.autocorrect}
final bool autocorrect;
/// {@macro}
final SmartDashesType smartDashesType;
/// {@macro}
final SmartQuotesType smartQuotesType;
/// {@macro}
final bool enableSuggestions;
/// {@macro flutter.widgets.editableText.maxLines}
final int maxLines;
/// {@macro flutter.widgets.editableText.minLines}
final int minLines;
/// {@macro flutter.widgets.editableText.expands}
final bool expands;
/// The maximum number of characters (Unicode scalar values) to allow in the
/// text field.
/// If set, a character counter will be displayed below the
/// field, showing how many characters have been entered and how many are
/// allowed. After [maxLength] characters have been input, additional input
/// is ignored, unless [maxLengthEnforced] is set to false. The TextField
/// enforces the length with a [LengthLimitingTextInputFormatter], which is
/// evaluated after the supplied [inputFormatters], if any.
/// This value must be either null or greater than zero. If set to null
/// (the default), there is no limit to the number of characters allowed.
/// Whitespace characters (e.g. newline, space, tab) are included in the
/// character count.
/// ## Limitations
/// The CupertinoTextField does not currently count Unicode grapheme clusters
/// (i.e. characters visible to the user), it counts Unicode scalar values,
/// which leaves out a number of useful possible characters (like many emoji
/// and composed characters), so this will be inaccurate in the presence of
/// those characters. If you expect to encounter these kinds of characters, be
/// generous in the maxLength used.
/// For instance, the character "ö" can be represented as '\u{006F}\u{0308}',
/// which is the letter "o" followed by a composed diaeresis "¨", or it can
/// be represented as '\u{00F6}', which is the Unicode scalar value "LATIN
/// SMALL LETTER O WITH DIAERESIS". In the first case, the text field will
/// count two characters, and the second case will be counted as one
/// character, even though the user can see no difference in the input.
/// Similarly, some emoji are represented by multiple scalar values. The
/// Unicode "THUMBS UP SIGN + MEDIUM SKIN TONE MODIFIER", "👍🏽", should be
/// counted as a single character, but because it is a combination of two
/// Unicode scalar values, '\u{1F44D}\u{1F3FD}', it is counted as two
/// characters.
/// See also:
/// * [LengthLimitingTextInputFormatter] for more information on how it
/// counts characters, and how it may differ from the intuitive meaning.
final int maxLength;
/// If true, prevents the field from allowing more than [maxLength]
/// characters.
/// If [maxLength] is set, [maxLengthEnforced] indicates whether or not to
/// enforce the limit, or merely provide a character counter and warning when
/// [maxLength] is exceeded.
final bool maxLengthEnforced;
/// {@macro flutter.widgets.editableText.onChanged}
final ValueChanged<String> onChanged;
/// {@macro flutter.widgets.editableText.onEditingComplete}
final VoidCallback onEditingComplete;
/// {@macro flutter.widgets.editableText.onSubmitted}
/// See also:
/// * [EditableText.onSubmitted] for an example of how to handle moving to
/// the next/previous field when using [] and
/// [TextInputAction.previous] for [textInputAction].
final ValueChanged<String> onSubmitted;
/// {@macro flutter.widgets.editableText.inputFormatters}
final List<TextInputFormatter> inputFormatters;
/// Disables the text field when false.
/// Text fields in disabled states have a light grey background and don't
/// respond to touch events including the [prefix], [suffix] and the clear
/// button.
final bool enabled;
/// {@macro flutter.widgets.editableText.cursorWidth}
final double cursorWidth;
/// {@macro flutter.widgets.editableText.cursorRadius}
final Radius cursorRadius;
/// The color to use when painting the cursor.
/// Defaults to the [CupertinoThemeData.primaryColor] of the ambient theme,
/// which itself defaults to [CupertinoColors.activeBlue] in the light theme
/// and [CupertinoColors.activeOrange] in the dark theme.
final Color cursorColor;
/// Controls how tall the selection highlight boxes are computed to be.
/// See [ui.BoxHeightStyle] for details on available styles.
final ui.BoxHeightStyle selectionHeightStyle;
/// Controls how wide the selection highlight boxes are computed to be.
/// See [ui.BoxWidthStyle] for details on available styles.
final ui.BoxWidthStyle selectionWidthStyle;
/// The appearance of the keyboard.
/// This setting is only honored on iOS devices.
/// If null, defaults to [Brightness.light].
final Brightness keyboardAppearance;
/// {@macro flutter.widgets.editableText.scrollPadding}
final EdgeInsets scrollPadding;
/// {@macro flutter.widgets.editableText.enableInteractiveSelection}
final bool enableInteractiveSelection;
/// {@macro flutter.widgets.scrollable.dragStartBehavior}
final DragStartBehavior dragStartBehavior;
/// {@macro flutter.widgets.editableText.scrollController}
final ScrollController scrollController;
/// {@macro flutter.widgets.editableText.scrollPhysics}
final ScrollPhysics scrollPhysics;
/// {@macro flutter.rendering.editable.selectionEnabled}
bool get selectionEnabled => enableInteractiveSelection;
/// {@macro flutter.material.textfield.onTap}
final GestureTapCallback onTap;
_CupertinoTextFieldState createState() => _CupertinoTextFieldState();
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
properties.add(DiagnosticsProperty<TextEditingController>('controller', controller, defaultValue: null));
properties.add(DiagnosticsProperty<FocusNode>('focusNode', focusNode, defaultValue: null));
properties.add(DiagnosticsProperty<BoxDecoration>('decoration', decoration));
properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding));
properties.add(StringProperty('placeholder', placeholder));
properties.add(DiagnosticsProperty<TextStyle>('placeholderStyle', placeholderStyle));
properties.add(DiagnosticsProperty<OverlayVisibilityMode>('prefix', prefix == null ? null : prefixMode));
properties.add(DiagnosticsProperty<OverlayVisibilityMode>('suffix', suffix == null ? null : suffixMode));
properties.add(DiagnosticsProperty<OverlayVisibilityMode>('clearButtonMode', clearButtonMode));
properties.add(DiagnosticsProperty<TextInputType>('keyboardType', keyboardType, defaultValue: TextInputType.text));
properties.add(DiagnosticsProperty<TextStyle>('style', style, defaultValue: null));
properties.add(DiagnosticsProperty<bool>('autofocus', autofocus, defaultValue: false));
properties.add(DiagnosticsProperty<bool>('obscureText', obscureText, defaultValue: false));
properties.add(DiagnosticsProperty<bool>('autocorrect', autocorrect, defaultValue: true));
properties.add(EnumProperty<SmartDashesType>('smartDashesType', smartDashesType, defaultValue: obscureText ? SmartDashesType.disabled : SmartDashesType.enabled));
properties.add(EnumProperty<SmartQuotesType>('smartQuotesType', smartQuotesType, defaultValue: obscureText ? SmartQuotesType.disabled : SmartQuotesType.enabled));
properties.add(DiagnosticsProperty<bool>('enableSuggestions', enableSuggestions, defaultValue: true));
properties.add(IntProperty('maxLines', maxLines, defaultValue: 1));
properties.add(IntProperty('minLines', minLines, defaultValue: null));
properties.add(DiagnosticsProperty<bool>('expands', expands, defaultValue: false));
properties.add(IntProperty('maxLength', maxLength, defaultValue: null));
properties.add(FlagProperty('maxLengthEnforced', value: maxLengthEnforced, ifTrue: 'max length enforced'));
properties.add(createCupertinoColorProperty('cursorColor', cursorColor, defaultValue: null));
properties.add(FlagProperty('selectionEnabled', value: selectionEnabled, defaultValue: true, ifFalse: 'selection disabled'));
properties.add(DiagnosticsProperty<ScrollController>('scrollController', scrollController, 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 implements TextSelectionGestureDetectorBuilderDelegate {
final GlobalKey _clearGlobalKey = GlobalKey();
TextEditingController _controller;
TextEditingController get _effectiveController => widget.controller ?? _controller;
FocusNode _focusNode;
FocusNode get _effectiveFocusNode => widget.focusNode ?? (_focusNode ??= FocusNode());
bool _showSelectionHandles = false;
_CupertinoTextFieldSelectionGestureDetectorBuilder _selectionGestureDetectorBuilder;
// API for TextSelectionGestureDetectorBuilderDelegate.
bool get forcePressEnabled => true;
final GlobalKey<EditableTextState> editableTextKey = GlobalKey<EditableTextState>();
bool get selectionEnabled => widget.selectionEnabled;
// End of API for TextSelectionGestureDetectorBuilderDelegate.
void initState() {
_selectionGestureDetectorBuilder = _CupertinoTextFieldSelectionGestureDetectorBuilder(state: this);
if (widget.controller == null) {
_controller = TextEditingController();
void didUpdateWidget(CupertinoTextField oldWidget) {
if (widget.controller == null && oldWidget.controller != null) {
_controller = TextEditingController.fromValue(oldWidget.controller.value);
} else if (widget.controller != null && oldWidget.controller == null) {
_controller = null;
final bool isEnabled = widget.enabled ?? true;
final bool wasEnabled = oldWidget.enabled ?? true;
if (wasEnabled && !isEnabled) {
void dispose() {
EditableTextState get _editableText => editableTextKey.currentState;
void _requestKeyboard() {
bool _shouldShowSelectionHandles(SelectionChangedCause cause) {
// When the text field is activated by something that doesn't trigger the
// selection overlay, we shouldn't show the handles either.
if (!_selectionGestureDetectorBuilder.shouldShowSelectionToolbar)
return false;
// On iOS, we don't show handles when the selection is collapsed.
if (_effectiveController.selection.isCollapsed)
return false;
if (cause == SelectionChangedCause.keyboard)
return false;
if (_effectiveController.text.isNotEmpty)
return true;
return false;
void _handleSelectionChanged(TextSelection selection, SelectionChangedCause cause) {
if (cause == SelectionChangedCause.longPress) {
final bool willShowSelectionHandles = _shouldShowSelectionHandles(cause);
if (willShowSelectionHandles != _showSelectionHandles) {
setState(() {
_showSelectionHandles = willShowSelectionHandles;
bool get wantKeepAlive => _controller?.text?.isNotEmpty == true;
bool _shouldShowAttachment({
OverlayVisibilityMode attachment,
bool hasText,
}) {
switch (attachment) {
case OverlayVisibilityMode.never:
return false;
case OverlayVisibilityMode.always:
return true;
case OverlayVisibilityMode.editing:
return hasText;
case OverlayVisibilityMode.notEditing:
return !hasText;
return null;
bool _showPrefixWidget(TextEditingValue text) {
return widget.prefix != null && _shouldShowAttachment(
attachment: widget.prefixMode,
hasText: text.text.isNotEmpty,
bool _showSuffixWidget(TextEditingValue text) {
return widget.suffix != null && _shouldShowAttachment(
attachment: widget.suffixMode,
hasText: text.text.isNotEmpty,
bool _showClearButton(TextEditingValue text) {
return _shouldShowAttachment(
attachment: widget.clearButtonMode,
hasText: text.text.isNotEmpty,
// 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 ? :;
Widget _addTextDependentAttachments(Widget editableText, TextStyle textStyle, TextStyle placeholderStyle) {
assert(editableText != null);
assert(textStyle != null);
assert(placeholderStyle != null);
// If there are no surrounding widgets, just return the core editable text
// part.
if (!_hasDecoration) {
return editableText;
// Otherwise, listen to the current state of the text entry.
return ValueListenableBuilder<TextEditingValue>(
valueListenable: _effectiveController,
child: editableText,
builder: (BuildContext context, TextEditingValue text, Widget child) {
return Row(children: <Widget>[
// Insert a prefix at the front if the prefix visibility mode matches
// the current text state.
if (_showPrefixWidget(text)) widget.prefix,
// In the middle part, stack the placeholder on top of the main EditableText
// if needed.
child: Stack(
children: <Widget>[
if (widget.placeholder != null && text.text.isEmpty)
width: double.infinity,
child: Padding(
padding: widget.padding,
child: Text(
maxLines: widget.maxLines,
overflow: TextOverflow.ellipsis,
style: placeholderStyle,
textAlign: widget.textAlign,
// First add the explicit suffix if the suffix visibility mode matches.
if (_showSuffixWidget(text))
// Otherwise, try to show a clear button if its visibility mode matches.
else if (_showClearButton(text))
key: _clearGlobalKey,
onTap: widget.enabled ?? true ? () {
// Special handle onChanged for ClearButton
// Also call onChanged when the clear button is tapped.
final bool textChanged = _effectiveController.text.isNotEmpty;
if (widget.onChanged != null && textChanged)
} : null,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 6.0),
child: Icon(
size: 18.0,
color: CupertinoDynamicColor.resolve(_kClearButtonColor, context),
Widget build(BuildContext context) {; // See AutomaticKeepAliveClientMixin.
final TextEditingController controller = _effectiveController;
final List<TextInputFormatter> formatters = widget.inputFormatters ?? <TextInputFormatter>[];
final bool enabled = widget.enabled ?? true;
final Offset cursorOffset = Offset(_iOSHorizontalCursorOffsetPixels / MediaQuery.of(context).devicePixelRatio, 0);
if (widget.maxLength != null && widget.maxLengthEnforced) {
final CupertinoThemeData themeData = CupertinoTheme.of(context);
final TextStyle resolvedStyle =
color: CupertinoDynamicColor.resolve(, context),
backgroundColor: CupertinoDynamicColor.resolve(, context),
final TextStyle textStyle = themeData.textTheme.textStyle.merge(resolvedStyle);
final TextStyle resolvedPlaceholderStyle = widget.placeholderStyle?.copyWith(
color: CupertinoDynamicColor.resolve(widget.placeholderStyle?.color, context),
backgroundColor: CupertinoDynamicColor.resolve(widget.placeholderStyle?.backgroundColor, context),
final TextStyle placeholderStyle = textStyle.merge(resolvedPlaceholderStyle);
final Brightness keyboardAppearance = widget.keyboardAppearance ?? CupertinoTheme.brightnessOf(context);
final Color cursorColor = CupertinoDynamicColor.resolve(widget.cursorColor, context) ?? themeData.primaryColor;
final Color disabledColor = CupertinoDynamicColor.resolve(_kDisabledBackground, context);
final Color decorationColor = CupertinoDynamicColor.resolve(widget.decoration?.color, context);
final BoxBorder border = widget.decoration?.border;
Border resolvedBorder = border as Border;
if (border is Border) {
BorderSide resolveBorderSide(BorderSide side) {
return side == BorderSide.none
? side
: side.copyWith(color: CupertinoDynamicColor.resolve(side.color, context));
resolvedBorder = border == null || border.runtimeType != Border
? border
: Border(
top: resolveBorderSide(,
left: resolveBorderSide(border.left),
bottom: resolveBorderSide(border.bottom),
right: resolveBorderSide(border.right),
final BoxDecoration effectiveDecoration = widget.decoration?.copyWith(
border: resolvedBorder,
color: enabled ? decorationColor : (decorationColor ?? disabledColor),
final Widget paddedEditable = Padding(
padding: widget.padding,
child: RepaintBoundary(
child: EditableText(
key: editableTextKey,
controller: controller,
readOnly: widget.readOnly,
toolbarOptions: widget.toolbarOptions,
showCursor: widget.showCursor,
showSelectionHandles: _showSelectionHandles,
focusNode: _effectiveFocusNode,
keyboardType: widget.keyboardType,
textInputAction: widget.textInputAction,
textCapitalization: widget.textCapitalization,
style: textStyle,
strutStyle: widget.strutStyle,
textAlign: widget.textAlign,
autofocus: widget.autofocus,
obscureText: widget.obscureText,
autocorrect: widget.autocorrect,
smartDashesType: widget.smartDashesType,
smartQuotesType: widget.smartQuotesType,
enableSuggestions: widget.enableSuggestions,
maxLines: widget.maxLines,
minLines: widget.minLines,
expands: widget.expands,
selectionColor: CupertinoTheme.of(context).primaryColor.withOpacity(0.2),
selectionControls: widget.selectionEnabled
? cupertinoTextSelectionControls : null,
onChanged: widget.onChanged,
onSelectionChanged: _handleSelectionChanged,
onEditingComplete: widget.onEditingComplete,
onSubmitted: widget.onSubmitted,
inputFormatters: formatters,
rendererIgnoresPointer: true,
cursorWidth: widget.cursorWidth,
cursorRadius: widget.cursorRadius,
cursorColor: cursorColor,
cursorOpacityAnimates: true,
cursorOffset: cursorOffset,
paintCursorAboveText: true,
backgroundCursorColor: CupertinoDynamicColor.resolve(CupertinoColors.inactiveGray, context),
selectionHeightStyle: widget.selectionHeightStyle,
selectionWidthStyle: widget.selectionWidthStyle,
scrollPadding: widget.scrollPadding,
keyboardAppearance: keyboardAppearance,
dragStartBehavior: widget.dragStartBehavior,
scrollController: widget.scrollController,
scrollPhysics: widget.scrollPhysics,
enableInteractiveSelection: widget.enableInteractiveSelection,
return Semantics(
enabled: enabled,
onTap: !enabled ? null : () {
if (!controller.selection.isValid) {
controller.selection = TextSelection.collapsed(offset: controller.text.length);
child: IgnorePointer(
ignoring: !enabled,
child: Container(
decoration: effectiveDecoration,
child: _selectionGestureDetectorBuilder.buildGestureDetector(
behavior: HitTestBehavior.translucent,
child: Align(
alignment: Alignment(-1.0, _textAlignVertical.y),
widthFactor: 1.0,
heightFactor: 1.0,
child: _addTextDependentAttachments(paddedEditable, textStyle, placeholderStyle),