text_form_field.dart 8.41 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
/// For a documentation about the various parameters, see [TextField].
///
34
/// {@tool sample}
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
///
/// Creates a [TextFormField] with an [InputDecoration] and validator function.
///
/// ```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;
///   },
/// )
/// ```
54
/// {@end-tool}
55 56 57
///
/// See also:
///
58
///  * <https://material.io/design/components/text-fields.html>
59 60 61 62
///  * [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.
63
class TextFormField extends FormField<String> {
64 65
  /// Creates a [FormField] that contains a [TextField].
  ///
66 67 68 69
  /// 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
  /// to [initalValue] or the empty string.
70
  ///
71 72
  /// For documentation about the various parameters, see the [TextField] class
  /// and [new TextField], the constructor.
73 74
  TextFormField({
    Key key,
75
    this.controller,
76
    String initialValue,
77
    FocusNode focusNode,
78
    InputDecoration decoration = const InputDecoration(),
79
    TextInputType keyboardType,
80
    TextCapitalization textCapitalization = TextCapitalization.none,
81
    TextInputAction textInputAction,
82
    TextStyle style,
83
    StrutStyle strutStyle,
84
    TextDirection textDirection,
85 86 87 88 89 90 91
    TextAlign textAlign = TextAlign.start,
    bool autofocus = false,
    bool obscureText = false,
    bool autocorrect = true,
    bool autovalidate = false,
    bool maxLengthEnforced = true,
    int maxLines = 1,
92 93
    int minLines,
    bool expands = false,
94
    int maxLength,
95
    VoidCallback onEditingComplete,
96
    ValueChanged<String> onFieldSubmitted,
97 98
    FormFieldSetter<String> onSaved,
    FormFieldValidator<String> validator,
99
    List<TextInputFormatter> inputFormatters,
100
    bool enabled = true,
101 102 103
    double cursorWidth = 2.0,
    Radius cursorRadius,
    Color cursorColor,
104
    Brightness keyboardAppearance,
105
    EdgeInsets scrollPadding = const EdgeInsets.all(20.0),
106
    bool enableInteractiveSelection = true,
107
    InputCounterWidgetBuilder buildCounter,
108
  }) : assert(initialValue == null || controller == null),
109
       assert(textAlign != null),
110 111
       assert(autofocus != null),
       assert(obscureText != null),
112
       assert(autocorrect != null),
113
       assert(autovalidate != null),
114
       assert(maxLengthEnforced != null),
115
       assert(scrollPadding != null),
116
       assert(maxLines == null || maxLines > 0),
117 118 119 120 121 122 123 124 125 126
       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.',
       ),
127
       assert(maxLength == null || maxLength > 0),
128
       assert(enableInteractiveSelection != null),
129
       super(
130
    key: key,
131
    initialValue: controller != null ? controller.text : (initialValue ?? ''),
132 133
    onSaved: onSaved,
    validator: validator,
134
    autovalidate: autovalidate,
135
    enabled: enabled,
136
    builder: (FormFieldState<String> field) {
137
      final _TextFormFieldState state = field;
138 139
      final InputDecoration effectiveDecoration = (decoration ?? const InputDecoration())
        .applyDefaults(Theme.of(field.context).inputDecorationTheme);
140
      return TextField(
141
        controller: state._effectiveController,
142
        focusNode: focusNode,
143
        decoration: effectiveDecoration.copyWith(errorText: field.errorText),
144
        keyboardType: keyboardType,
145
        textInputAction: textInputAction,
146
        style: style,
147
        strutStyle: strutStyle,
148
        textAlign: textAlign,
149
        textDirection: textDirection,
150
        textCapitalization: textCapitalization,
151 152
        autofocus: autofocus,
        obscureText: obscureText,
153
        autocorrect: autocorrect,
154
        maxLengthEnforced: maxLengthEnforced,
155
        maxLines: maxLines,
156 157
        minLines: minLines,
        expands: expands,
158
        maxLength: maxLength,
159
        onChanged: field.didChange,
160
        onEditingComplete: onEditingComplete,
161
        onSubmitted: onFieldSubmitted,
162
        inputFormatters: inputFormatters,
163
        enabled: enabled,
164 165 166
        cursorWidth: cursorWidth,
        cursorRadius: cursorRadius,
        cursorColor: cursorColor,
167
        scrollPadding: scrollPadding,
168
        keyboardAppearance: keyboardAppearance,
169
        enableInteractiveSelection: enableInteractiveSelection,
170
        buildCounter: buildCounter,
171 172 173
      );
    },
  );
174 175 176

  /// Controls the text being edited.
  ///
177 178
  /// If null, this widget will create its own [TextEditingController] and
  /// initialize its [TextEditingController.text] with [initialValue].
179 180 181
  final TextEditingController controller;

  @override
182
  _TextFormFieldState createState() => _TextFormFieldState();
183 184 185 186 187 188 189 190 191 192 193 194 195 196
}

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) {
197
      _controller = TextEditingController(text: widget.initialValue);
198 199 200 201 202 203 204 205 206 207 208 209 210
    } 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)
211
        _controller = TextEditingController.fromValue(oldWidget.controller.value);
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242
      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)
243
      didChange(_effectiveController.text);
244
  }
245
}