当我的
TextField
小部件处于活动状态时,如何使我的 TextField
标签浮动到顶部但保持在 TextField
边框内(而不是在其边缘)?
我想要的示例:
成为
使用
inputDecorationTheme
上的 ThemeData
可以吗?或者我必须制作一个包装器小部件才能完成此任务?
非常愿意从应用程序范围内控制它
ThemeData
不幸的是,我不知道如何使用默认工具来做到这一点,但我用另一种方式做到这一点,可能会对你有所帮助。
FocusNode
和小部件内部的边框颜色创建变量:// Use it to change color for border when textFiled in focus
FocusNode _focusNode = FocusNode();
// Color for border
Color _borderColor = Colors.grey;
initState
内部为textField
创建监听器,如果textField
将成为焦点,则将边框颜色更改为橙色,否则更改为灰色:@override
void initState() {
super.initState();
// Change color for border if focus was changed
_focusNode.addListener(() {
setState(() {
_borderColor = _focusNode.hasFocus ? Colors.orange : Colors.grey;
});
});
}
Container
边框的textField
,添加focusNode
并将装饰设置为textField
:Container(
decoration: BoxDecoration(
border: Border.all(color: _borderColor),
borderRadius: BorderRadius.circular(4),
),
child: TextField(
focusNode: _focusNode,
style: TextStyle(color: Colors.grey),
keyboardType: TextInputType.number,
decoration: InputDecoration(
contentPadding: EdgeInsets.zero,
border: InputBorder.none,
labelText: "Amount",
prefixIconConstraints: BoxConstraints(minWidth: 0, minHeight: 0),
prefixIcon: Padding(
padding: EdgeInsets.symmetric(vertical: 18, horizontal: 8),
child: Text("₦", style: TextStyle(fontSize: 16, color: Colors.grey)),
),
),
),
),
dispose
询问focusNode
:@override
void dispose() {
_focusNode.dispose();
super.dispose();
}
完整代码:
class TextFieldDesignPage extends StatefulWidget {
TextFieldDesignPage({Key? key}) : super(key: key);
@override
_TextFieldDesignPageState createState() => _TextFieldDesignPageState();
}
class _TextFieldDesignPageState extends State<TextFieldDesignPage> {
// Use it to change color for border when textFiled in focus
FocusNode _focusNode = FocusNode();
// Color for border
Color _borderColor = Colors.grey;
@override
void initState() {
super.initState();
// Change color for border if focus was changed
_focusNode.addListener(() {
setState(() {
_borderColor = _focusNode.hasFocus ? Colors.orange : Colors.grey;
});
});
}
@override
void dispose() {
_focusNode.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
margin: EdgeInsets.all(8),
decoration: BoxDecoration(
border: Border.all(color: _borderColor),
borderRadius: BorderRadius.circular(4),
),
child: TextField(
focusNode: _focusNode,
style: TextStyle(color: Colors.grey),
keyboardType: TextInputType.number,
decoration: InputDecoration(
contentPadding: EdgeInsets.zero,
border: InputBorder.none,
labelText: "Amount",
prefixIconConstraints: BoxConstraints(minWidth: 0, minHeight: 0),
prefixIcon: Padding(
padding: EdgeInsets.symmetric(vertical: 18, horizontal: 8),
child: Text("₦", style: TextStyle(fontSize: 16, color: Colors.grey)),
),
),
),
),
),
);
}
}
结果:
class MyInputTextField extends StatefulWidget {
final String? title;
final String? helperText;
final bool isSecure;
final int maxLength;
final String? hint;
final TextInputType? inputType;
final String? initValue;
final Color? backColor;
final Widget? suffix;
final Widget? prefix;
final TextEditingController? textEditingController;
final String? Function(String? value)? validator;
final Function(String)? onTextChanged;
final Function(String)? onSaved;
List<TextInputFormatter>? inputFormatters;
static const int MAX_LENGTH = 500;
MyInputTextField({
this.title,
this.hint,
this.helperText,
this.inputType,
this.initValue = "",
this.isSecure = false,
this.textEditingController,
this.validator,
this.maxLength = MAX_LENGTH,
this.onTextChanged,
this.onSaved,
this.inputFormatters,
this.backColor,
this.suffix,
this.prefix,
});
@override
_MyInputTextFieldState createState() => _MyInputTextFieldState();
}
class _MyInputTextFieldState extends State<MyInputTextField> {
late bool _passwordVisibility;
late ThemeData theme;
FocusNode _focusNode = FocusNode();
Color _borderColor = getColors().primaryVariant;
double _borderSize = 1;
@override
void initState() {
super.initState();
_passwordVisibility = !widget.isSecure;
widget.textEditingController?.text = widget.initValue ?? "";
_focusNode.addListener(() {
setState(() {
_borderSize = _focusNode.hasFocus ? 1.7 : 1;
});
});
}
@override
void didChangeDependencies() {
theme = Theme.of(context);
super.didChangeDependencies();
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
Container(
height: 55,
decoration: BoxDecoration(
border: Border.all(color: _borderColor, width: _borderSize),
borderRadius: BorderRadius.circular(AppDimens.radiusSmall),
),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 4),
child: TextFormField(
focusNode: _focusNode,
controller: widget.textEditingController,
autocorrect: false,
obscureText: !_passwordVisibility,
keyboardType: widget.inputType,
cursorColor: Colors.white,
validator: (value) {
String? f = widget.validator?.call(value);
setState(() {
_borderColor = f != null ? getColors().redError : getColors().primaryVariant;
});
return f;
},
style: theme.textTheme.bodyText1,
maxLength: widget.maxLength,
inputFormatters: widget.inputFormatters,
maxLines: 1,
onChanged: (text) {
widget.onTextChanged?.call(text);
},
decoration: InputDecoration(
counterText: "",
hintStyle: theme.textTheme.subtitle1,
floatingLabelStyle: theme.textTheme.headline6?.copyWith(color: getColors().textSubtitle),
labelText: widget.title,
helperText: widget.helperText,
suffixIcon: getSuffixIcon(),
prefixIcon: widget.prefix,
contentPadding: EdgeInsets.zero,
border: InputBorder.none,
),
),
)
],
);
}
Widget? getSuffixIcon() {
return widget.isSecure ? getPasswordSuffixIcon() : widget.suffix;
}
Widget? getPasswordSuffixIcon() {
return IconButton(
hoverColor: Colors.transparent,
focusColor: Colors.transparent,
splashColor: Colors.transparent,
padding: EdgeInsets.zero,
icon: _passwordVisibility ? Icon(AppIcons.password_eye) : Icon(AppIcons.password_eye_blind),
color: Colors.white,
onPressed: () {
setState(() {
_passwordVisibility = !_passwordVisibility;
});
},
);
}
@override
void dispose() {
_focusNode.dispose();
super.dispose();
}
}
对于那些可能遇到类似问题的人,这是我的解决方法。我创建了
InputBorder
的实现(类似于 OutlineInputBorder
或 UnderlineInputBorder
)。主要区别在于它将标签定位在边框内,消除了标签边框上的任何间隙。
TextField(
decoration: InputDecoration(
labelText: 'Floating label',
helperText: 'Helper text works fine',
contentPadding: EdgeInsets.all(12.0),
border: OutlinedInputBorder(),
),
);
我希望这个解决方案很方便,因为它不会引入任何额外的包装器,并且可以非常普遍地使用。例如,您可以直接在主题中使用此
InputBorder
实现。
这是自定义边框的代码。
import 'dart:math' as math;
import 'dart:ui';
import 'package:flutter/material.dart';
class OutlinedInputBorder extends InputBorder {
const OutlinedInputBorder({
super.borderSide = const BorderSide(),
this.borderRadius = const BorderRadius.all(Radius.circular(4.0)),
});
final BorderRadius borderRadius;
@override
bool get isOutline => false;
@override
OutlinedInputBorder copyWith({
BorderSide? borderSide,
BorderRadius? borderRadius,
}) {
return OutlinedInputBorder(
borderSide: borderSide ?? this.borderSide,
borderRadius: borderRadius ?? this.borderRadius,
);
}
@override
EdgeInsetsGeometry get dimensions {
return EdgeInsets.all(borderSide.width);
}
@override
OutlinedInputBorder scale(double t) {
return OutlinedInputBorder(
borderSide: borderSide.scale(t),
borderRadius: borderRadius * t,
);
}
@override
ShapeBorder? lerpFrom(ShapeBorder? a, double t) {
if (a is OutlinedInputBorder) {
final OutlinedInputBorder outline = a;
return OutlinedInputBorder(
borderRadius: BorderRadius.lerp(outline.borderRadius, borderRadius, t)!,
borderSide: BorderSide.lerp(outline.borderSide, borderSide, t),
);
}
return super.lerpFrom(a, t);
}
@override
ShapeBorder? lerpTo(ShapeBorder? b, double t) {
if (b is OutlinedInputBorder) {
final OutlinedInputBorder outline = b;
return OutlinedInputBorder(
borderRadius: BorderRadius.lerp(borderRadius, outline.borderRadius, t)!,
borderSide: BorderSide.lerp(borderSide, outline.borderSide, t),
);
}
return super.lerpTo(b, t);
}
@override
Path getInnerPath(Rect rect, {TextDirection? textDirection}) {
return Path()
..addRRect(borderRadius
.resolve(textDirection)
.toRRect(rect)
.deflate(borderSide.width));
}
@override
Path getOuterPath(Rect rect, {TextDirection? textDirection}) {
return Path()..addRRect(borderRadius.resolve(textDirection).toRRect(rect));
}
@override
void paintInterior(Canvas canvas, Rect rect, Paint paint,
{TextDirection? textDirection}) {
canvas.drawRRect(borderRadius.resolve(textDirection).toRRect(rect), paint);
}
@override
bool get preferPaintInterior => true;
@override
void paint(
Canvas canvas,
Rect rect, {
double? gapStart,
double gapExtent = 0.0,
double gapPercentage = 0.0,
TextDirection? textDirection,
}) {
final Paint paint = borderSide.toPaint();
final RRect outer = borderRadius.toRRect(rect);
final RRect center = outer.deflate(borderSide.width / 2.0);
canvas.drawRRect(center, paint);
}
@override
bool operator ==(Object other) {
if (identical(this, other)) {
return true;
}
if (other.runtimeType != runtimeType) {
return false;
}
return other is OutlinedInputBorder &&
other.borderSide == borderSide &&
other.borderRadius == borderRadius;
}
@override
int get hashCode => Object.hash(borderSide, borderRadius);
}
这还有一个要点。
公平地说,与OutlineInputBorder
实现相比,差异相对较少,并且大部分代码都是简单复制的。主要变化是
isOutline
getter 返回
false
(类似于
UnderlineInputBorder
),因为该值决定标签的位置。请记住,该值已考虑到其他一些次要参数,因此您可能需要检查
InputDecorator
如何处理它,以确保我的解决方案满足您的需求。例如,您可能想要指定自定义
contentPadding
值。我还修改了在画布上绘制边框的逻辑,以避免为标签添加任何间隙,但据我了解,这里不应该出现任何意外问题。