text_form_field.dart 9.88 KB
Newer Older
1 2 3 4 5 6 7 8 9
// Copyright 2015 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.

import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';

import 'input_decorator.dart';
import 'text_field.dart';
10
import 'theme.dart';
11 12 13

/// A [FormField] that contains a [TextField].
///
14
/// This is a convenience widget that wraps a [TextField] widget in a
15 16 17 18 19 20 21
/// [FormField].
///
/// A [Form] ancestor is not required. The [Form] simply makes it easier to
/// save, reset, or validate multiple fields at once. To use without a [Form],
/// pass a [GlobalKey] to the constructor and use [GlobalKey.currentState] to
/// save or reset the form field.
///
22 23 24 25 26 27 28 29 30
/// When a [controller] is specified, its [TextEditingController.text]
/// defines the [initialValue]. If this [FormField] is part of a scrolling
/// container that lazily constructs its children, like a [ListView] or a
/// [CustomScrollView], then a [controller] should be specified.
/// The controller's lifetime should be managed by a stateful widget ancestor
/// of the scrolling container.
///
/// If a [controller] is not specified, [initialValue] can be used to give
/// the automatically generated controller an initial value.
31
///
32 33 34
/// Remember to [dispose] of the [TextEditingController] when it is no longer needed.
/// This will ensure we discard any resources used by the object.
///
35 36
/// For a documentation about the various parameters, see [TextField].
///
37
/// {@tool sample}
38 39 40
///
/// Creates a [TextFormField] with an [InputDecoration] and validator function.
///
41 42 43 44
/// ![If the user enters valid text, the TextField appears normally without any warnings to the user](https://flutter.github.io/assets-for-api-docs/assets/material/text_form_field.png)
///
/// ![If the user enters invalid text, the error message returned from the validator function is displayed in dark red underneath the input](https://flutter.github.io/assets-for-api-docs/assets/material/text_form_field_error.png)
///
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
/// ```dart
/// TextFormField(
///   decoration: const InputDecoration(
///     icon: Icon(Icons.person),
///     hintText: 'What do people call you?',
///     labelText: 'Name *',
///   ),
///   onSaved: (String value) {
///     // This optional block of code can be used to run
///     // code when the user saves the form.
///   },
///   validator: (String value) {
///     return value.contains('@') ? 'Do not use the @ char.' : null;
///   },
/// )
/// ```
61
/// {@end-tool}
62 63 64
///
/// See also:
///
65
///  * <https://material.io/design/components/text-fields.html>
66 67 68 69
///  * [TextField], which is the underlying text field without the [Form]
///    integration.
///  * [InputDecorator], which shows the labels and other visual elements that
///    surround the actual text editing widget.
70
///  * Learn how to use a [TextEditingController] in one of our [cookbook recipe]s.(https://flutter.dev/docs/cookbook/forms/text-field-changes#2-use-a-texteditingcontroller)
71
class TextFormField extends FormField<String> {
72 73
  /// Creates a [FormField] that contains a [TextField].
  ///
74 75 76
  /// When a [controller] is specified, [initialValue] must be null (the
  /// default). If [controller] is null, then a [TextEditingController]
  /// will be constructed automatically and its `text` will be initialized
77
  /// to [initialValue] or the empty string.
78
  ///
79 80
  /// For documentation about the various parameters, see the [TextField] class
  /// and [new TextField], the constructor.
81 82
  TextFormField({
    Key key,
83
    this.controller,
84
    String initialValue,
85
    FocusNode focusNode,
86
    InputDecoration decoration = const InputDecoration(),
87
    TextInputType keyboardType,
88
    TextCapitalization textCapitalization = TextCapitalization.none,
89
    TextInputAction textInputAction,
90
    TextStyle style,
91
    StrutStyle strutStyle,
92
    TextDirection textDirection,
93
    TextAlign textAlign = TextAlign.start,
94
    TextAlignVertical textAlignVertical,
95
    bool autofocus = false,
96
    bool readOnly = false,
97
    ToolbarOptions toolbarOptions,
98
    bool showCursor,
99 100
    bool obscureText = false,
    bool autocorrect = true,
101
    bool enableSuggestions = true,
102 103 104
    bool autovalidate = false,
    bool maxLengthEnforced = true,
    int maxLines = 1,
105 106
    int minLines,
    bool expands = false,
107
    int maxLength,
108
    ValueChanged<String> onChanged,
109
    GestureTapCallback onTap,
110
    VoidCallback onEditingComplete,
111
    ValueChanged<String> onFieldSubmitted,
112 113
    FormFieldSetter<String> onSaved,
    FormFieldValidator<String> validator,
114
    List<TextInputFormatter> inputFormatters,
115
    bool enabled = true,
116 117 118
    double cursorWidth = 2.0,
    Radius cursorRadius,
    Color cursorColor,
119
    Brightness keyboardAppearance,
120
    EdgeInsets scrollPadding = const EdgeInsets.all(20.0),
121
    bool enableInteractiveSelection = true,
122
    InputCounterWidgetBuilder buildCounter,
123
  }) : assert(initialValue == null || controller == null),
124
       assert(textAlign != null),
125
       assert(autofocus != null),
126
       assert(readOnly != null),
127
       assert(obscureText != null),
128
       assert(autocorrect != null),
129
       assert(enableSuggestions != null),
130
       assert(autovalidate != null),
131
       assert(maxLengthEnforced != null),
132
       assert(scrollPadding != null),
133
       assert(maxLines == null || maxLines > 0),
134 135 136 137 138 139 140 141 142 143
       assert(minLines == null || minLines > 0),
       assert(
         (maxLines == null) || (minLines == null) || (maxLines >= minLines),
         'minLines can\'t be greater than maxLines',
       ),
       assert(expands != null),
       assert(
         !expands || (maxLines == null && minLines == null),
         'minLines and maxLines must be null when expands is true.',
       ),
144
       assert(!obscureText || maxLines == 1, 'Obscured fields cannot be multiline.'),
145
       assert(maxLength == null || maxLength > 0),
146
       assert(enableInteractiveSelection != null),
147
       super(
148
    key: key,
149
    initialValue: controller != null ? controller.text : (initialValue ?? ''),
150 151
    onSaved: onSaved,
    validator: validator,
152
    autovalidate: autovalidate,
153
    enabled: enabled,
154
    builder: (FormFieldState<String> field) {
155
      final _TextFormFieldState state = field;
156 157
      final InputDecoration effectiveDecoration = (decoration ?? const InputDecoration())
        .applyDefaults(Theme.of(field.context).inputDecorationTheme);
158 159 160 161 162 163
      void onChangedHandler(String value) {
        if (onChanged != null) {
          onChanged(value);
        }
        field.didChange(value);
      }
164
      return TextField(
165
        controller: state._effectiveController,
166
        focusNode: focusNode,
167
        decoration: effectiveDecoration.copyWith(errorText: field.errorText),
168
        keyboardType: keyboardType,
169
        textInputAction: textInputAction,
170
        style: style,
171
        strutStyle: strutStyle,
172
        textAlign: textAlign,
173
        textAlignVertical: textAlignVertical,
174
        textDirection: textDirection,
175
        textCapitalization: textCapitalization,
176
        autofocus: autofocus,
177
        toolbarOptions: toolbarOptions,
178 179
        readOnly: readOnly,
        showCursor: showCursor,
180
        obscureText: obscureText,
181
        autocorrect: autocorrect,
182
        enableSuggestions: enableSuggestions,
183
        maxLengthEnforced: maxLengthEnforced,
184
        maxLines: maxLines,
185 186
        minLines: minLines,
        expands: expands,
187
        maxLength: maxLength,
188
        onChanged: onChangedHandler,
189
        onTap: onTap,
190
        onEditingComplete: onEditingComplete,
191
        onSubmitted: onFieldSubmitted,
192
        inputFormatters: inputFormatters,
193
        enabled: enabled,
194 195 196
        cursorWidth: cursorWidth,
        cursorRadius: cursorRadius,
        cursorColor: cursorColor,
197
        scrollPadding: scrollPadding,
198
        keyboardAppearance: keyboardAppearance,
199
        enableInteractiveSelection: enableInteractiveSelection,
200
        buildCounter: buildCounter,
201 202 203
      );
    },
  );
204 205 206

  /// Controls the text being edited.
  ///
207 208
  /// If null, this widget will create its own [TextEditingController] and
  /// initialize its [TextEditingController.text] with [initialValue].
209 210 211
  final TextEditingController controller;

  @override
212
  _TextFormFieldState createState() => _TextFormFieldState();
213 214 215 216 217 218 219 220 221 222 223 224 225 226
}

class _TextFormFieldState extends FormFieldState<String> {
  TextEditingController _controller;

  TextEditingController get _effectiveController => widget.controller ?? _controller;

  @override
  TextFormField get widget => super.widget;

  @override
  void initState() {
    super.initState();
    if (widget.controller == null) {
227
      _controller = TextEditingController(text: widget.initialValue);
228 229 230 231 232 233 234 235 236 237 238 239 240
    } else {
      widget.controller.addListener(_handleControllerChanged);
    }
  }

  @override
  void didUpdateWidget(TextFormField oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.controller != oldWidget.controller) {
      oldWidget.controller?.removeListener(_handleControllerChanged);
      widget.controller?.addListener(_handleControllerChanged);

      if (oldWidget.controller != null && widget.controller == null)
241
        _controller = TextEditingController.fromValue(oldWidget.controller.value);
242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272
      if (widget.controller != null) {
        setValue(widget.controller.text);
        if (oldWidget.controller == null)
          _controller = null;
      }
    }
  }

  @override
  void dispose() {
    widget.controller?.removeListener(_handleControllerChanged);
    super.dispose();
  }

  @override
  void reset() {
    super.reset();
    setState(() {
      _effectiveController.text = widget.initialValue;
    });
  }

  void _handleControllerChanged() {
    // Suppress changes that originated from within this class.
    //
    // In the case where a controller has been passed in to this widget, we
    // register this change listener. In these cases, we'll also receive change
    // notifications for changes originating from within this class -- for
    // example, the reset() method. In such cases, the FormField value will
    // already have been set.
    if (_effectiveController.text != value)
273
      didChange(_effectiveController.text);
274
  }
275
}