190 lines
5.2 KiB
Dart
190 lines
5.2 KiB
Dart
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<String>? validator,
|
|
ValueChanged<String>? 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<String>? validator;
|
|
final ValueChanged<String>? 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)),
|
|
],
|
|
);
|
|
}
|
|
}
|