如何向 TextField 小部件添加清除按钮

有没有一个正确的方法来添加一个明确的按钮到 TextField

就像这张材料设计指南中的图片一样:

enter image description here

我所发现的是在 InputDecorationsuffixIcon中设置一个清晰的 IconButton。这是正确的方法吗?

170151 次浏览

试试这个

final TextEditingController _controller = new TextEditingController();
new Stack(
alignment: const Alignment(1.0, 1.0),
children: <Widget>[
new TextField(controller: _controller,),
new FlatButton(
onPressed: () {
_controller.clear();
},
child: new Icon(Icons.clear))
]
)
        Container(
margin: EdgeInsets.only(left: 16.0),
child: TextFormField(
controller: _username,
decoration: InputDecoration(
hintText: '请输入工号',
filled: true,
prefixIcon: Icon(
Icons.account_box,
size: 28.0,
),
suffixIcon: IconButton(
icon: Icon(Icons.remove),
onPressed: () {
debugPrint('222');
})),
),
),

使用图标按钮

在@Vilokan 实验室的回答中,还有一个问题的答案稍微扩展了一下,这个问题并没有真正解决我的问题,因为 FlatButton 的最小宽度是88.0,所以这个清除按钮根本就没有和 TextField 对齐。

所以我继续创建我自己的按钮类,并使用 Stack 应用它,下面是我的过程:

按钮类别:

class CircleIconButton extends StatelessWidget {
final double size;
final Function onPressed;
final IconData icon;


CircleIconButton({this.size = 30.0, this.icon = Icons.clear, this.onPressed});


@override
Widget build(BuildContext context) {
return InkWell(
onTap: this.onPressed,
child: SizedBox(
width: size,
height: size,
child: Stack(
alignment: Alignment(0.0, 0.0), // all centered
children: <Widget>[
Container(
width: size,
height: size,
decoration: BoxDecoration(
shape: BoxShape.circle, color: Colors.grey[300]),
),
Icon(
icon,
size: size * 0.6, // 60% width for icon
)
],
)));
}
}

然后像 InputDecoration那样应用到你的 TextField:

var myTextField = TextField(
controller: _textController,
decoration: InputDecoration(
hintText: "Caption",
suffixIcon: CircleIconButton(
onPressed: () {
this.setState(() {
_textController.clear();
});
},
)),
},
);

为了得到这个:

未标记状态

enter image description here

突出显示/选定状态

enter image description here

注意,当您使用 suffixIcon时,这种颜色是免费的。


注意,你也可以像这样在你的 TextField 中堆栈它,但是当你使用 suffixIcon时你不会得到自动着色:

var myTextFieldView = Stack(
alignment: Alignment(1.0,0.0), // right & center
children: <Widget>[
TextField(
controller: _textController,
decoration: InputDecoration(hintText: "Caption"),
),
Positioned(
child: CircleIconButton(
onPressed: () {
this.setState(() {
_textController.clear();
});
},
),
),
],
);

要在文本字段中添加图标。必须在输入修饰中使用后缀图标或前缀图标。

TextFormField(
autofocus: false,
obscureText: true,
decoration: InputDecoration(
labelText: 'Password',
suffixIcon: Icon(
Icons.clear,
size: 20.0,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(0.0)),
),
hintText: 'Enter Password',
contentPadding: EdgeInsets.all(10.0),
),
);
IconButton(
icon: Icon(Icons.clear_all),
tooltip: 'Close',
onPressed: () {
},
)

使用图标和清除按钮搜索 TextField

import 'package:flutter/material.dart';


class SearchTextField extends StatefulWidget{
@override
State<StatefulWidget> createState() {
// TODO: implement createState
return new SearchTextFieldState();
}
}


class SearchTextFieldState extends State<SearchTextField>{
final TextEditingController _textController = new TextEditingController();


@override
Widget build(BuildContext context) {
// TODO: implement build
return new Row(children: <Widget>[
new Icon(Icons.search, color: _textController.text.length>0?Colors.lightBlueAccent:Colors.grey,),
new SizedBox(width: 10.0,),
new Expanded(child: new Stack(
alignment: const Alignment(1.0, 1.0),
children: <Widget>[
new TextField(
decoration: InputDecoration(hintText: 'Search'),
onChanged: (text){
setState(() {
print(text);
});
},
controller: _textController,),
_textController.text.length>0?new IconButton(icon: new Icon(Icons.clear), onPressed: () {
setState(() {
_textController.clear();
});
}):new Container(height: 0.0,)
]
),),
],);
}
}

search_1

search_2

TextFormField(
controller:_controller
decoration: InputDecoration(
suffixIcon: IconButton(
onPressed: (){
_controller.clear();
},
icon: Icon(
Icons.keyboard,
color: Colors.blue,
),
),


),
)

下面是我的代码的一个片段。

它的作用: 只显示清除按钮,如果文本字段值不是空的

class _MyTextFieldState extends State<MyTextField> {
TextEditingController _textController;
bool _wasEmpty;


@override
void initState() {
super.initState();
_textController = TextEditingController(text: widget.initialValue);
_wasEmpty = _textController.text.isEmpty;
_textController.addListener(() {
if (_wasEmpty != _textController.text.isEmpty) {
setState(() => {_wasEmpty = _textController.text.isEmpty});
}
});
}


@override
void dispose() {
_textController.dispose();
super.dispose();
}


@override
Widget build(BuildContext context) {
return TextFormField(
controller: _textController,
decoration: InputDecoration(
labelText: widget.label,
suffixIcon: _textController.text.isNotEmpty
? Padding(
padding: const EdgeInsetsDirectional.only(start: 12.0),
child: IconButton(
iconSize: 16.0,
icon: Icon(Icons.cancel, color: Colors.grey,),
onPressed: () {
setState(() {
_textController.clear();
});
},
),
)
: null,
),);
...

产出:

enter image description here

创建一个变量

var _controller = TextEditingController();

还有你的 TextField:

TextField(
controller: _controller,
decoration: InputDecoration(
hintText: 'Enter a message',
suffixIcon: IconButton(
onPressed: _controller.clear,
icon: Icon(Icons.clear),
),
),
)

如果你想要一个可以随时使用的 Widget,你只需要把它放在一个文件中,然后通过插入 ClearableTextField ()就可以在任何地方使用一个可重用的元素,使用这段代码:

import 'package:flutter/material.dart';


class ClearableTexfield extends StatefulWidget {
ClearableTexfield({
Key key,
this.controller,
this.hintText = 'Enter text'
}) : super(key: key);


final TextEditingController controller;
final String hintText;


@override
State<StatefulWidget> createState() {
return _ClearableTextfieldState();
}
}


class _ClearableTextfieldState extends State<ClearableTexfield> {
bool _showClearButton = false;


@override
void initState() {
super.initState();
widget.controller.addListener(() {
setState(() {
_showClearButton = widget.controller.text.length > 0;
});
});
}


@override
Widget build(BuildContext context) {
return TextField(
controller: widget.controller,
decoration: InputDecoration(
hintText: widget.hintText,
suffixIcon: _getClearButton(),
),
);
}


Widget _getClearButton() {
if (!_showClearButton) {
return null;
}


return IconButton(
onPressed: () => widget.controller.clear(),
icon: Icon(Icons.clear),
);
}
}

详情请参阅以下网页:

Https://www.flutterclutter.dev/flutter/tutorials/text-field-with-clear-button/2020/104/

它也构建在 IconButton 之上,但是具有文本字段 只有当里面有文本时才显示清除按钮的优点。

看起来像这样:

enter image description here

这里有一个使用 TextEditingController 和 StatelessWidget 的例子(提供者推动更新)。 我把控制器放在静态区域。

class _SearchBar extends StatelessWidget {
static var _controller = TextEditingController();


@override
Widget build(BuildContext context) {
var dictionary = Provider.of<Dictionary>(context);


return TextField(
controller: _controller,
autofocus: true,
onChanged: (text) {
dictionary.lookupWord = text;
},
style: TextStyle(fontSize: 20.0),
decoration: InputDecoration(
border: InputBorder.none,
hintText: 'Search',
suffix: GestureDetector(
onTap: () {
dictionary.lookupWord = '';
_controller.clear();
},
child: Text('x'),
)));
}
}
TextField(
decoration: InputDecoration(
suffixIcon: IconButton(
icon: Icon(
Icons.cancel,
),
onPressed: () {
_controllerx.text = '';
}
),
)
)

TextEditingController用于检查 Text 的当前状态,我们可以根据 Text 的可用性决定是否显示取消图标。

  var _usernameController = TextEditingController();


@override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Center(
child: TextField(
controller: _usernameController,
onChanged: (text) {
setState(() {});
},
decoration: InputDecoration(
labelText: 'Username',
suffixIcon: _usernameController.text.length > 0
? IconButton(
onPressed: () {
_usernameController.clear();
setState(() {});
},
icon: Icon(Icons.cancel, color: Colors.grey))
: null),
),
),
),
);
}

产出:

enter image description here

(2021年8月19日)基于 Flutter 团队编写的代码的鲁棒解决方案

这里是一个完全可重复使用的 ClearableTextFormField与最大配置,这个清晰的文本字段在这里的大部分代码是来自 于2021年4月1日提交的 Flutter 团队的内置 TextFormFieldClearableTextFormField接受与内置 TextFormField相同的参数,并且工作方式类似。

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


// A [TextFormField] with a clear button
class ClearableTextFormField extends FormField<String> {
/// Creates a [FormField] that contains a [TextField].
///
/// 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 [initialValue] or the empty string.
///
/// For documentation about the various parameters, see the [TextField] class
/// and [new TextField], the constructor.
ClearableTextFormField({
Key? key,
this.controller,
String? initialValue,
FocusNode? focusNode,
InputDecoration decoration = const InputDecoration(),
TextInputType? keyboardType,
TextCapitalization textCapitalization = TextCapitalization.none,
TextInputAction? textInputAction,
TextStyle? style,
StrutStyle? strutStyle,
TextDirection? textDirection,
TextAlign textAlign = TextAlign.start,
TextAlignVertical? textAlignVertical,
bool autofocus = false,
bool readOnly = false,
ToolbarOptions? toolbarOptions,
bool? showCursor,
String obscuringCharacter = '•',
bool obscureText = false,
bool autocorrect = true,
SmartDashesType? smartDashesType,
SmartQuotesType? smartQuotesType,
bool enableSuggestions = true,
MaxLengthEnforcement? maxLengthEnforcement,
int? maxLines = 1,
int? minLines,
bool expands = false,
int? maxLength,
ValueChanged<String>? onChanged,
GestureTapCallback? onTap,
VoidCallback? onEditingComplete,
ValueChanged<String>? onFieldSubmitted,
FormFieldSetter<String>? onSaved,
FormFieldValidator<String>? validator,
List<TextInputFormatter>? inputFormatters,
bool? enabled,
double cursorWidth = 2.0,
double? cursorHeight,
Radius? cursorRadius,
Color? cursorColor,
Brightness? keyboardAppearance,
EdgeInsets scrollPadding = const EdgeInsets.all(20.0),
bool enableInteractiveSelection = true,
TextSelectionControls? selectionControls,
InputCounterWidgetBuilder? buildCounter,
ScrollPhysics? scrollPhysics,
Iterable<String>? autofillHints,
AutovalidateMode? autovalidateMode,
ScrollController? scrollController,


// Features
this.resetIcon = const Icon(Icons.close),
})  : assert(initialValue == null || controller == null),
assert(obscuringCharacter.length == 1),
assert(
maxLengthEnforcement == null,
'maxLengthEnforced is deprecated, use only maxLengthEnforcement',
),
assert(maxLines == null || maxLines > 0),
assert(minLines == null || minLines > 0),
assert(
(maxLines == null) || (minLines == null) || (maxLines >= minLines),
"minLines can't be greater than maxLines",
),
assert(
!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),
super(
key: key,
initialValue:
controller != null ? controller.text : (initialValue ?? ''),
onSaved: onSaved,
validator: validator,
enabled: enabled ?? true,
autovalidateMode: autovalidateMode ?? AutovalidateMode.disabled,
builder: (FormFieldState<String> field) {
final _ClearableTextFormFieldState state =
field as _ClearableTextFormFieldState;
final InputDecoration effectiveDecoration = decoration
.applyDefaults(Theme.of(field.context).inputDecorationTheme);
void onChangedHandler(String value) {
field.didChange(value);
if (onChanged != null) onChanged(value);
}


return Focus(
onFocusChange: (hasFocus) => state.setHasFocus(hasFocus),
child: TextField(
controller: state._effectiveController,
focusNode: focusNode,
decoration: effectiveDecoration.copyWith(
errorText: field.errorText,
suffixIcon:
((field.value?.length ?? -1) > 0 && state.hasFocus)
? IconButton(
icon: resetIcon,
onPressed: () => state.clear(),
color: Theme.of(state.context).hintColor,
)
: null,
),
keyboardType: keyboardType,
textInputAction: textInputAction,
style: style,
strutStyle: strutStyle,
textAlign: textAlign,
textAlignVertical: textAlignVertical,
textDirection: textDirection,
textCapitalization: textCapitalization,
autofocus: autofocus,
toolbarOptions: toolbarOptions,
readOnly: readOnly,
showCursor: showCursor,
obscuringCharacter: obscuringCharacter,
obscureText: obscureText,
autocorrect: autocorrect,
smartDashesType: smartDashesType ??
(obscureText
? SmartDashesType.disabled
: SmartDashesType.enabled),
smartQuotesType: smartQuotesType ??
(obscureText
? SmartQuotesType.disabled
: SmartQuotesType.enabled),
enableSuggestions: enableSuggestions,
maxLengthEnforcement: maxLengthEnforcement,
maxLines: maxLines,
minLines: minLines,
expands: expands,
maxLength: maxLength,
onChanged: onChangedHandler,
onTap: onTap,
onEditingComplete: onEditingComplete,
onSubmitted: onFieldSubmitted,
inputFormatters: inputFormatters,
enabled: enabled ?? true,
cursorWidth: cursorWidth,
cursorHeight: cursorHeight,
cursorRadius: cursorRadius,
cursorColor: cursorColor,
scrollPadding: scrollPadding,
scrollPhysics: scrollPhysics,
keyboardAppearance: keyboardAppearance,
enableInteractiveSelection: enableInteractiveSelection,
selectionControls: selectionControls,
buildCounter: buildCounter,
autofillHints: autofillHints,
scrollController: scrollController,
),
);
},
);


/// Controls the text being edited.
///
/// If null, this widget will create its own [TextEditingController] and
/// initialize its [TextEditingController.text] with [initialValue].
final TextEditingController? controller;
final Icon resetIcon;


@override
_ClearableTextFormFieldState createState() => _ClearableTextFormFieldState();
}


class _ClearableTextFormFieldState extends FormFieldState<String> {
TextEditingController? _controller;
bool hasFocus = false;


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


@override
ClearableTextFormField get widget => super.widget as ClearableTextFormField;


@override
void initState() {
super.initState();
if (widget.controller == null)
_controller = TextEditingController(text: widget.initialValue);
else
widget.controller!.addListener(_handleControllerChanged);
}


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


if (oldWidget.controller != null && widget.controller == null)
_controller =
TextEditingController.fromValue(oldWidget.controller!.value);
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 didChange(String? value) {
super.didChange(value);


if (_effectiveController.text != value)
_effectiveController.text = value ?? '';
}


@override
void reset() {
// setState will be called in the superclass, so even though state is being
// manipulated, no setState call is needed here.
_effectiveController.text = widget.initialValue ?? '';
super.reset();
}


void setHasFocus(bool b) => setState(() => hasFocus = b);


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)
didChange(_effectiveController.text);
}


/// Invoked by the clear suffix icon to clear everything in the [FormField]
void clear() {
WidgetsBinding.instance!.addPostFrameCallback((_) {
_effectiveController.clear();
didChange(null);
});
}
}

阅读有关 飞镖语文档中的断言语句的更多信息。

下面是添加到内置 TextFormField的附加代码的说明

ClearableTextFormField的可选 resetIcon参数

_ClearableTextFormFieldState中的附加代码:

/// Invoked by the clear suffix icon to clear everything in the [FormField]
void clear() {
WidgetsBinding.instance!.addPostFrameCallback((_) {
_effectiveController.clear();
didChange(null);
});
}

addPostFrameCallback()确保只有在 Widget 构建完成时(当完全构建并在屏幕上呈现 TextFormField时)才调用传入函数。你可以在 这根线中读到更多关于它的信息。

下面的 state 和 setState 允许您跟踪 TextField中的焦点变化(当字段被聚焦或不聚焦时) :

bool hasFocus = false;
void setHasFocus(bool b) => setState(() => hasFocus = b);

扩展 FormField<String>(super()部分)的 builder方法内的附加代码:

当字段的值为空或 null时,隐藏 clear 按钮:

suffixIcon:
((field.value?.length ?? -1) > 0 && state.hasFocus)
? IconButton(
icon: resetIcon,
onPressed: () => state.clear(),
color: Theme.of(state.context).hintColor,
)
: null,

重建 TextField的焦点和不焦点,以显示和隐藏清除按钮:

Focus(
onFocusChange: (hasFocus) => state.setHasFocus(hasFocus),
child: TextField(// more code here...),
)

也可以使用 TextFormField。 首先创建表单密钥

Form(key: _formKeylogin,
child: Column(
children: [
Container(
margin: EdgeInsets.all(20.0),
child: TextFormField(
                       

style: TextStyle(color: WHITE),
textInputAction: TextInputAction.next,
onChanged: (companyName) {
},
                     

validator: (companyName) {
if (companyName.isEmpty) {
return 'Please enter company Name';
} else {
return null;
}
},
),

在 OnTap 或 onPress 方法中。

_formKeylogin.currentState.reset();

清除按钮时,输入不是空的,并有焦点。

创建一个控制器变量:

//Clear inputs
final _nameInputcontroller = TextEditingController();

创建一个 FocusNode:

late FocusNode focusNodeNameInput;


@override
void initState() {
super.initState();


//FocusNode for all inputs
focusNodeNameInput = FocusNode();
focusNodeNameInput.addListener(() {
setState(() {});
});
}


@override
void dispose() {
// Clean up the focus node when the Form is disposed.
focusNodeNameInput.dispose();


super.dispose();
}

还有 TextFormField:

TextFormField(
controller: _nameInputcontroller,
focusNode: focusNodeNameInput,
decoration: InputDecoration(
labelText: LocaleKeys.name.tr(),
labelStyle: TextStyle(
color: focusNodeNameInput.hasFocus ? AppColors.MAIN_COLOR : null,
),
focusedBorder: const UnderlineInputBorder(
borderSide: BorderSide(
color: AppColors.MAIN_COLOR,
),
),
suffixIcon: _nameInputcontroller.text.isNotEmpty || focusNodeNameInput.hasFocus
? IconButton(
onPressed: _nameInputcontroller.clear,
icon: const Icon(
Icons.clear,
color: AppColors.MAIN_COLOR,
),
) : null,
),
),