Skip to content

Commit 9f344b6

Browse files
gspencergoogHixie
authored andcommitted
Adds prefix and suffix support to TextField, per Material Design spec. (flutter#10675)
* Prefix and Suffix support for TextFields * Adding Tests * Removing spurious newline. * Fixing a small problem with the test * Review Changes
1 parent befe019 commit 9f344b6

File tree

3 files changed

+348
-19
lines changed

3 files changed

+348
-19
lines changed

examples/flutter_gallery/lib/demo/material/text_form_field_demo.dart

+11
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ class TextFormFieldDemoState extends State<TextFormFieldDemo> {
129129
icon: const Icon(Icons.phone),
130130
hintText: 'Where can we reach you?',
131131
labelText: 'Phone Number *',
132+
prefixText: '+1'
132133
),
133134
keyboardType: TextInputType.phone,
134135
onSaved: (String value) { person.phoneNumber = value; },
@@ -147,6 +148,16 @@ class TextFormFieldDemoState extends State<TextFormFieldDemo> {
147148
),
148149
maxLines: 3,
149150
),
151+
new TextFormField(
152+
keyboardType: TextInputType.number,
153+
decoration: const InputDecoration(
154+
labelText: 'Salary',
155+
prefixText: '\$',
156+
suffixText: 'USD',
157+
suffixStyle: const TextStyle(color: Colors.green)
158+
),
159+
maxLines: 1,
160+
),
150161
new Row(
151162
crossAxisAlignment: CrossAxisAlignment.start,
152163
children: <Widget>[

packages/flutter/lib/src/material/input_decorator.dart

+85-9
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ class InputDecoration {
3737
this.errorStyle,
3838
this.isDense: false,
3939
this.hideDivider: false,
40+
this.prefixText,
41+
this.prefixStyle,
42+
this.suffixText,
43+
this.suffixStyle,
4044
}) : isCollapsed = false;
4145

4246
/// Creates a decoration that is the same size as the input field.
@@ -55,7 +59,11 @@ class InputDecoration {
5559
errorStyle = null,
5660
isDense = false,
5761
isCollapsed = true,
58-
hideDivider = true;
62+
hideDivider = true,
63+
prefixText = null,
64+
prefixStyle = null,
65+
suffixText = null,
66+
suffixStyle = null;
5967

6068
/// An icon to show before the input field.
6169
///
@@ -108,7 +116,7 @@ class InputDecoration {
108116
/// If non-null the divider, that appears below the input field is red.
109117
final String errorText;
110118

111-
/// The style to use for the [errorText.
119+
/// The style to use for the [errorText].
112120
///
113121
/// If null, defaults of a value derived from the base [TextStyle] for the
114122
/// input field and the current [Theme].
@@ -133,6 +141,28 @@ class InputDecoration {
133141
/// Defaults to false.
134142
final bool hideDivider;
135143

144+
/// Optional text prefix to place on the line before the input.
145+
///
146+
/// Uses the [prefixStyle]. Uses [hintStyle] if [prefixStyle] isn't
147+
/// specified. Prefix is not returned as part of the input.
148+
final String prefixText;
149+
150+
/// The style to use for the [prefixText].
151+
///
152+
/// If null, defaults to the [hintStyle].
153+
final TextStyle prefixStyle;
154+
155+
/// Optional text suffix to place on the line after the input.
156+
///
157+
/// Uses the [suffixStyle]. Uses [hintStyle] if [suffixStyle] isn't
158+
/// specified. Suffix is not returned as part of the input.
159+
final String suffixText;
160+
161+
/// The style to use for the [suffixText].
162+
///
163+
/// If null, defaults to the [hintStyle].
164+
final TextStyle suffixStyle;
165+
136166
/// Creates a copy of this input decoration but with the given fields replaced
137167
/// with the new values.
138168
///
@@ -147,6 +177,10 @@ class InputDecoration {
147177
TextStyle errorStyle,
148178
bool isDense,
149179
bool hideDivider,
180+
String prefixText,
181+
TextStyle prefixStyle,
182+
String suffixText,
183+
TextStyle suffixStyle,
150184
}) {
151185
return new InputDecoration(
152186
icon: icon ?? this.icon,
@@ -158,6 +192,10 @@ class InputDecoration {
158192
errorStyle: errorStyle ?? this.errorStyle,
159193
isDense: isDense ?? this.isDense,
160194
hideDivider: hideDivider ?? this.hideDivider,
195+
prefixText: prefixText ?? this.prefixText,
196+
prefixStyle: prefixStyle ?? this.prefixStyle,
197+
suffixText: suffixText ?? this.suffixText,
198+
suffixStyle: suffixStyle ?? this.suffixStyle,
161199
);
162200
}
163201

@@ -177,7 +215,11 @@ class InputDecoration {
177215
&& typedOther.errorStyle == errorStyle
178216
&& typedOther.isDense == isDense
179217
&& typedOther.isCollapsed == isCollapsed
180-
&& typedOther.hideDivider == hideDivider;
218+
&& typedOther.hideDivider == hideDivider
219+
&& typedOther.prefixText == prefixText
220+
&& typedOther.prefixStyle == prefixStyle
221+
&& typedOther.suffixText == suffixText
222+
&& typedOther.suffixStyle == suffixStyle;
181223
}
182224

183225
@override
@@ -193,6 +235,10 @@ class InputDecoration {
193235
isDense,
194236
isCollapsed,
195237
hideDivider,
238+
prefixText,
239+
prefixStyle,
240+
suffixText,
241+
suffixStyle,
196242
);
197243
}
198244

@@ -213,6 +259,14 @@ class InputDecoration {
213259
description.add('isCollapsed: $isCollapsed');
214260
if (hideDivider)
215261
description.add('hideDivider: $hideDivider');
262+
if (prefixText != null)
263+
description.add('prefixText: $prefixText');
264+
if (prefixStyle != null)
265+
description.add('prefixStyle: $prefixStyle');
266+
if (suffixText != null)
267+
description.add('suffixText: $suffixText');
268+
if (suffixStyle != null)
269+
description.add('suffixStyle: $suffixStyle');
216270
return 'InputDecoration(${description.join(', ')})';
217271
}
218272
}
@@ -293,7 +347,7 @@ class InputDecorator extends StatelessWidget {
293347
return themeData.hintColor;
294348
}
295349

296-
Widget _buildContent(Color borderColor, double topPadding, bool isDense) {
350+
Widget _buildContent(Color borderColor, double topPadding, bool isDense, Widget inputChild) {
297351
final double bottomPadding = isDense ? 8.0 : 1.0;
298352
const double bottomBorder = 2.0;
299353
final double bottomHeight = isDense ? 14.0 : 18.0;
@@ -305,7 +359,7 @@ class InputDecorator extends StatelessWidget {
305359
return new Container(
306360
margin: margin + const EdgeInsets.only(bottom: bottomBorder),
307361
padding: padding,
308-
child: child,
362+
child: inputChild,
309363
);
310364
}
311365

@@ -322,7 +376,7 @@ class InputDecorator extends StatelessWidget {
322376
),
323377
),
324378
),
325-
child: child,
379+
child: inputChild,
326380
);
327381
}
328382

@@ -348,7 +402,7 @@ class InputDecorator extends StatelessWidget {
348402

349403
final List<Widget> stackChildren = <Widget>[];
350404

351-
// If we're not focused, there's not value, and labelText was provided,
405+
// If we're not focused, there's no value, and labelText was provided,
352406
// then the label appears where the hint would. And we will not show
353407
// the hintText.
354408
final bool hasInlineLabel = !isFocused && labelText != null && isEmpty;
@@ -402,11 +456,33 @@ class InputDecorator extends StatelessWidget {
402456
);
403457
}
404458

459+
Widget inputChild;
460+
if (!hasInlineLabel && (!isEmpty || hintText == null) &&
461+
(decoration?.prefixText != null || decoration?.suffixText != null)) {
462+
final List<Widget> rowContents = <Widget>[];
463+
if (decoration.prefixText != null) {
464+
rowContents.add(
465+
new Text(decoration.prefixText,
466+
style: decoration.prefixStyle ?? hintStyle)
467+
);
468+
}
469+
rowContents.add(new Expanded(child: child));
470+
if (decoration.suffixText != null) {
471+
rowContents.add(
472+
new Text(decoration.suffixText,
473+
style: decoration.suffixStyle ?? hintStyle)
474+
);
475+
}
476+
inputChild = new Row(children: rowContents);
477+
} else {
478+
inputChild = child;
479+
}
480+
405481
if (isCollapsed) {
406-
stackChildren.add(child);
482+
stackChildren.add(inputChild);
407483
} else {
408484
final Color borderColor = errorText == null ? activeColor : themeData.errorColor;
409-
stackChildren.add(_buildContent(borderColor, topPadding, isDense));
485+
stackChildren.add(_buildContent(borderColor, topPadding, isDense, inputChild));
410486
}
411487

412488
if (!isDense && errorText != null) {

0 commit comments

Comments
 (0)