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

Flutter中TextFormField高度随窗口自适应的问题求助

解决TextFormField自适应高度的光标与异常问题

问题描述

尝试实现TextFormField高度随窗口高度自适应,通过ConstrainedBox设置了高度约束,但调整contentPadding时遇到两个问题:

  • 将垂直内边距设为double.infinity会抛出异常:
    [ERROR:flutter/flow/layers/transform_layer.cc(23)] TransformLayer is constructed with an invalid matrix.
    
    ══╡ EXCEPTION CAUGHT BY GESTURES LIBRARY ╞══════════════════════════════════════════════════════════
    The following assertion was thrown while handling a pointer data packet:
    'package:flutter/src/material/input_decorator.dart': Failed assertion: line 1636 pos 18:
    'transformed == position - offset': is not true.
    
  • 使用固定大数值作为垂直内边距,视觉显示正常,但光标会渲染在组件上方1000像素处。

解决方案

核心问题是错误地用contentPadding强制拉伸高度——contentPadding的作用是控制输入内容与装饰框的间距,而非调整组件整体高度。正确做法是利用ConstrainedBox的约束,配合TextFormField的专属属性实现自适应高度,同时保证光标位置正常:

修改步骤

  1. 移除超大垂直内边距,改为合理的间距值(如垂直方向16px)
  2. 给TextFormField添加expands: true,让它填充父容器的约束空间
  3. 设置maxLines: null和minLines: 1,允许输入框根据高度自动适配行数(单行输入可保持maxLines:1)
  4. 移除isDense: true,该属性会压缩内部间距,与自适应高度冲突

修改后的完整代码

import 'package:flutter/material.dart';

class HeliosFormTextField extends StatefulWidget {
  const HeliosFormTextField({
    super.key,
    required this.controller,
    required this.labelText,
    required this.labelTextOnError,
    required this.validityCriteria,
    this.keyboardType,
    this.obscureText = false,
    this.clearOnError = true,
  });

  final TextEditingController controller;
  final String labelText;
  final String labelTextOnError;
  final bool Function(String?) validityCriteria;
  final bool obscureText;
  final bool clearOnError;
  final TextInputType? keyboardType;

  @override
  State<HeliosFormTextField> createState() => _HeliosFormTextFieldState();
}

class _HeliosFormTextFieldState extends State<HeliosFormTextField> {
  TextFieldStatus status = TextFieldStatus.none;
  ///Can change behaviour of the widget only if [widget.obscureText] is true
  bool obscureSymbols = true;

  @override
  Widget build(BuildContext context) {
    return ConstrainedBox(
      constraints: BoxConstraints.tightFor(height: MediaQuery.of(context).size.height * 0.065),
      child: TextFormField(
        cursorErrorColor: Theme.of(context).colorScheme.primary,
        obscureText: widget.obscureText ? obscureSymbols : false,
        style: Theme.of(context)
            .textTheme
            .bodyMedium
            ?.copyWith(fontWeight: FontWeight.w500),
        keyboardType: widget.keyboardType,
        autocorrect: false,
        // 新增属性实现自适应高度
        expands: true,
        maxLines: null,
        minLines: 1,
        decoration: InputDecoration(
          labelText: status == TextFieldStatus.error ? widget.labelTextOnError : widget.labelText, 
          labelStyle: status == TextFieldStatus.error ? Theme.of(context).textTheme.bodyMedium?.copyWith(color: Colors.red) : Theme.of(context).textTheme.bodyMedium ,
          border: const OutlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(20)), borderSide: BorderSide.none),
          floatingLabelBehavior: FloatingLabelBehavior.never,
          filled: true,
          fillColor: Theme.of(context).colorScheme.surface,
          errorStyle: const TextStyle(
            height: 0,
            color: Colors.transparent,
          ),
          // 改为合理的内边距
          contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
          suffixIcon: widget.obscureText ? 
             IconButton(
              icon: Icon(obscureSymbols ? Icons.visibility_rounded : Icons.visibility_off_rounded),
              onPressed: () => setState(() {
                obscureSymbols = !obscureSymbols;
              }),
          )
          : null,    
        ),
        controller: widget.controller,
        validator: (value) {
          if (!widget.validityCriteria(value)) {
            if (status != TextFieldStatus.error) {
              setState(() {
                status = TextFieldStatus.error;
              });
              if (widget.clearOnError) widget.controller.clear();
            }
            return '';
          }
          return null;
        },
        onChanged: (value) {
          if (status == TextFieldStatus.error &&
              widget.validityCriteria(value)) {
            setState(() {
              status = TextFieldStatus.none;
            });
          }
        },
      ),
    );
  }
}

enum TextFieldStatus {
  none,
  error,
  selected,
}

额外说明

  • expands: true必须配合maxLines: null使用,否则会触发布局异常
  • 若需单行输入,保持maxLines:1即可,输入框会在ConstrainedBox的高度内自动垂直居中文本与光标
  • 合理的contentPadding可避免文本贴紧输入框边缘,提升视觉体验

内容的提问来源于stack exchange,提问作者Michael Feygenberg

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 06:26:01