Flutter实现标签位于TextFormField下方的圆角输入框及底部校验提示
Flutter实现标签在输入框下方的圆角TextFormField
要实现标签位于输入框下方、输入框为圆角且错误提示也在下方的效果,我们需要自定义布局拆分默认TextFormField的元素,以下是具体实现方案:
核心思路
默认TextFormField的标签(labelText)嵌入边框内,因此我们需要:
- 用垂直布局(
Column)将输入框、标签、错误提示分开排列 - 给输入框设置圆角边框
- 通过表单状态获取错误信息并显示在标签下方
完整实现代码
基础示例(带表单校验)
import 'package:flutter/material.dart'; class CustomFormPage extends StatefulWidget { const CustomFormPage({super.key}); @override State<CustomFormPage> createState() => _CustomFormPageState(); } class _CustomFormPageState extends State<CustomFormPage> { final _formKey = GlobalKey<FormState>(); final _usernameController = TextEditingController(); @override void dispose() { _usernameController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text("自定义输入框")), body: Padding( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 30), child: Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 自定义输入框组合 Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 圆角输入框 TextFormField( controller: _usernameController, decoration: InputDecoration( border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), // 设置圆角 borderSide: const BorderSide(color: Colors.grey), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: const BorderSide(color: Colors.blue, width: 1.5), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: const BorderSide(color: Colors.grey), ), contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), hintText: "请输入用户名", // 输入框内提示文字 hintStyle: const TextStyle(color: Colors.grey[400]), ), validator: (value) { if (value == null || value.isEmpty) { return "用户名不能为空"; } if (value.length < 6) { return "用户名长度不能少于6位"; } return null; }, ), const SizedBox(height: 8), // 输入框与标签的间距 // 下方标签 const Text( "用户名", style: TextStyle( fontSize: 14, color: Colors.grey[700], ), ), // 错误提示 Builder( builder: (context) { // 获取当前输入框的表单状态 final fieldState = Form.of(context)?.fields.firstWhere( (field) => field.widget is TextFormField && (field.widget as TextFormField).controller == _usernameController, orElse: () => null, ); if (fieldState?.hasError == true) { return Padding( padding: const EdgeInsets.only(top: 4), child: Text( fieldState!.errorText!, style: const TextStyle( fontSize: 12, color: Colors.redAccent, ), ), ); } return const SizedBox.shrink(); }, ), ], ), const SizedBox(height: 30), // 提交按钮 SizedBox( width: double.infinity, child: ElevatedButton( onPressed: () { if (_formKey.currentState!.validate()) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text("校验通过")), ); } }, style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 14), borderRadius: BorderRadius.circular(12), ), child: const Text("提交", style: TextStyle(fontSize: 16)), ), ), ], ), ), ), ); } }
可复用组件封装
如果需要在多个地方使用该样式,可以封装成独立组件:
class LabelBelowRoundTextField extends StatelessWidget { final String label; final String hintText; final TextEditingController controller; final String? Function(String?)? validator; final TextInputType? keyboardType; const LabelBelowRoundTextField({ super.key, required this.label, required this.hintText, required this.controller, this.validator, this.keyboardType, }); @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ TextFormField( controller: controller, keyboardType: keyboardType, decoration: InputDecoration( border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: const BorderSide(color: Colors.grey), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: const BorderSide(color: Colors.blue, width: 1.5), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: const BorderSide(color: Colors.grey), ), contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), hintText: hintText, hintStyle: const TextStyle(color: Colors.grey[400]), ), validator: validator, ), const SizedBox(height: 8), Text( label, style: TextStyle( fontSize: 14, color: Colors.grey[700], ), ), Builder( builder: (context) { final fieldState = Form.of(context)?.fields.firstWhere( (field) => field.widget is TextFormField && (field.widget as TextFormField).controller == controller, orElse: () => null, ); if (fieldState?.hasError == true) { return Padding( padding: const EdgeInsets.only(top: 4), child: Text( fieldState!.errorText!, style: const TextStyle( fontSize: 12, color: Colors.redAccent, ), ), ); } return const SizedBox.shrink(); }, ), ], ); } }
使用该组件时只需传入对应参数:
LabelBelowRoundTextField( label: "手机号", hintText: "请输入你的手机号", controller: _phoneController, keyboardType: TextInputType.phone, validator: (value) { if (value == null || value.isEmpty) { return "手机号不能为空"; } if (!RegExp(r'^1[3-9]\d{9}$').hasMatch(value)) { return "请输入正确的手机号格式"; } return null; }, )
关键细节说明
- 圆角边框:通过
OutlineInputBorder的borderRadius属性设置圆角,同时分别定义border、focusedBorder、enabledBorder三种状态下的边框样式,保证交互一致性。 - 标签布局:使用独立的
Text组件放在输入框下方,通过SizedBox控制间距,自由调整标签的字体、颜色。 - 错误提示:借助
Builder获取当前表单上下文,找到对应输入框的状态,当存在错误时显示红色提示文本,无错误时隐藏。
内容的提问来源于stack exchange,提问作者Manish sahu
相关产品推荐
相关产品推荐

