import 'package:flutter/material.dart'; class CustomInput { static Widget buildInput( TextEditingController? controller, { String? title, String? hint, bool obscure = false, Widget? suffix, TextInputType? keyboardType, FocusNode? focusNode, FormFieldValidator? validator, ValueChanged? onChanged, String? initialValue, bool labelInline = false, double? labelWidth, }) { return _CustomInputWidget( externalController: controller, title: title, hint: hint, obscure: obscure, suffix: suffix, keyboardType: keyboardType, focusNode: focusNode, validator: validator, onChanged: onChanged, initialValue: initialValue, labelInline: labelInline, labelWidth: labelWidth, ); } } class _CustomInputWidget extends StatefulWidget { const _CustomInputWidget({ Key? key, this.externalController, this.title, this.hint, this.obscure = false, this.suffix, this.keyboardType, this.focusNode, this.validator, this.onChanged, this.initialValue, this.labelInline = false, this.labelWidth, }) : super(key: key); final TextEditingController? externalController; final String? title; final String? hint; final bool obscure; final Widget? suffix; final TextInputType? keyboardType; final FocusNode? focusNode; final FormFieldValidator? validator; final ValueChanged? onChanged; final String? initialValue; // new final bool labelInline; final double? labelWidth; @override State<_CustomInputWidget> createState() => _CustomInputWidgetState(); } class _CustomInputWidgetState extends State<_CustomInputWidget> { late final TextEditingController _controller; late final bool _controllerIsExternal; bool _showClear = false; @override void initState() { super.initState(); _controllerIsExternal = widget.externalController != null; _controller = widget.externalController ?? TextEditingController(text: widget.initialValue ?? ''); _showClear = _controller.text.isNotEmpty; _controller.addListener(_onTextChange); } void _onTextChange() { final has = _controller.text.isNotEmpty; if (has != _showClear) { setState(() { _showClear = has; }); } if (widget.onChanged != null) widget.onChanged!(_controller.text); } @override void dispose() { _controller.removeListener(_onTextChange); if (!_controllerIsExternal) { _controller.dispose(); } super.dispose(); } void _clearText() { _controller.clear(); // listener will trigger onChanged } Widget _buildTextField({EdgeInsetsGeometry? contentPadding}) { return SizedBox( height: 40, child: TextFormField( controller: _controller, obscureText: widget.obscure, keyboardType: widget.keyboardType, validator: widget.validator, focusNode: widget.focusNode, // 保证文字垂直居中 textAlignVertical: TextAlignVertical.center, decoration: InputDecoration( hintText: widget.hint, hintStyle: const TextStyle(color: Color(0xFFB6BAC9), fontSize: 16), suffixIcon: widget.suffix ?? (_showClear ? IconButton( icon: const Icon(Icons.cancel, size: 20, color: Colors.grey), onPressed: _clearText, ) : null), isDense: true, contentPadding: contentPadding ?? const EdgeInsets.symmetric(horizontal: 0, vertical: 10), border: InputBorder.none, ), style: const TextStyle(color: Colors.black87, fontSize: 16), ), ); } @override Widget build(BuildContext context) { final hasTitle = (widget.title ?? '').isNotEmpty; if (widget.labelInline && hasTitle) { final double labelW = widget.labelWidth ?? 100.0; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ SizedBox( width: labelW, child: Text( widget.title!, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500), ), ), Expanded( child: Container( child: _buildTextField(contentPadding: const EdgeInsets.symmetric(horizontal: 8, vertical: 10)), ), ), ], ), const SizedBox(height: 10), const Divider(height: 0.5, thickness: 1, color: Color(0xFFBDBDBD)), ], ); } // 默认竖排(标题在上) return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (hasTitle) Padding( padding: const EdgeInsets.only(bottom: 6), child: Text( widget.title!, style: const TextStyle(fontSize: 17, fontWeight: FontWeight.w500), ), ), _buildTextField(), const SizedBox(height: 10), const Divider(height: 0.5, thickness: 1, color: Color(0xFFBDBDBD)), ], ); } }