You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;
  },
)

关键细节说明

  1. 圆角边框:通过OutlineInputBorder的borderRadius属性设置圆角,同时分别定义border、focusedBorder、enabledBorder三种状态下的边框样式,保证交互一致性。
  2. 标签布局:使用独立的Text组件放在输入框下方,通过SizedBox控制间距,自由调整标签的字体、颜色。
  3. 错误提示:借助Builder获取当前表单上下文,找到对应输入框的状态,当存在错误时显示红色提示文本,无错误时隐藏。

内容的提问来源于stack exchange,提问作者Manish sahu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 00:43:28