| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290 |
- import 'package:flutter/material.dart';
- import 'package:flutter/services.dart';
- import 'package:flutter_screenutil/flutter_screenutil.dart';
- import 'package:get/get.dart';
- import 'package:nomo/config/theme/theme_extensions/theme_extension.dart';
- import '../../config/theme/dark_theme_colors.dart';
- import 'click_opacity.dart';
- class IXTextField extends StatefulWidget {
- final String hintText;
- final IconData? prefixIcon;
- final bool isPassword;
- final TextEditingController controller;
- final FocusNode? focusNode;
- final TextInputType keyboardType;
- final TextInputAction textInputAction;
- final Function(String)? onChanged;
- final bool showClearButton;
- final String? errorText;
- final bool enabled;
- final bool isEmail;
- final String? tipText;
- final bool Function(String)? validator;
- const IXTextField({
- super.key,
- required this.hintText,
- this.prefixIcon,
- this.isPassword = false,
- required this.controller,
- this.focusNode,
- this.keyboardType = TextInputType.text,
- this.textInputAction = TextInputAction.done,
- this.onChanged,
- this.showClearButton = true,
- this.errorText,
- this.enabled = true,
- this.isEmail = false,
- this.tipText,
- this.validator,
- });
- @override
- State<IXTextField> createState() => _IXTextFieldState();
- }
- class _IXTextFieldState extends State<IXTextField> {
- bool _obscureText = true;
- bool _hasFocus = false;
- bool _hasError = false;
- @override
- void initState() {
- super.initState();
- widget.focusNode?.addListener(_onFocusChange);
- // 初始验证
- if (widget.controller.text.isNotEmpty) {
- _validateInput(widget.controller.text);
- }
- }
- // 添加 didUpdateWidget 监听属性变化
- @override
- void didUpdateWidget(IXTextField oldWidget) {
- super.didUpdateWidget(oldWidget);
- if (widget.controller.text.isNotEmpty) {
- _validateInput(widget.controller.text);
- }
- }
- @override
- void dispose() {
- widget.focusNode?.removeListener(_onFocusChange);
- super.dispose();
- }
- void _onFocusChange() {
- setState(() {
- _hasFocus = widget.focusNode?.hasFocus ?? false;
- // 当失去焦点时进行验证
- if (!_hasFocus && widget.controller.text.isNotEmpty) {
- _validateInput(widget.controller.text);
- }
- });
- }
- void _validateInput(String value) {
- if (widget.validator != null) {
- // 使用自定义验证器
- setState(() {
- _hasError = !widget.validator!(value);
- });
- } else if (widget.isEmail) {
- // 默认电子邮件验证
- final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
- setState(() {
- _hasError = !emailRegex.hasMatch(value);
- });
- } else {
- setState(() {});
- }
- }
- @override
- Widget build(BuildContext context) {
- // 确定边框颜色
- Color borderColor = Get.reactiveTheme.dividerColor;
- Color bgColor = Get.reactiveTheme.highlightColor;
- if (_hasFocus) {
- borderColor = Get.reactiveTheme.shadowColor;
- bgColor = Get.reactiveTheme.highlightColor;
- }
- return Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Container(
- decoration: BoxDecoration(
- color: bgColor,
- borderRadius: BorderRadius.circular(12.r),
- border: Border.all(color: borderColor, width: 1.0),
- ),
- alignment: Alignment.center,
- height: 50.w,
- child: TextField(
- controller: widget.controller,
- focusNode: widget.focusNode,
- obscureText: widget.isPassword && _obscureText,
- keyboardType: widget.keyboardType,
- cursorHeight: 14,
- enableSuggestions: false,
- autocorrect: false,
- textInputAction: widget.textInputAction,
- inputFormatters: widget.isPassword
- ? [LengthLimitingTextInputFormatter(20)]
- : [],
- onTap: () {
- // 不要先unfocus
- if (widget.focusNode != null) {
- widget.focusNode!.requestFocus();
- Future.delayed(const Duration(milliseconds: 150), () {
- // 每次需要点击两次才能显示键盘
- SystemChannels.textInput.invokeMethod('TextInput.show');
- });
- }
- },
- onChanged: (value) {
- if (widget.onChanged != null) {
- widget.onChanged!(value);
- }
- // 实时验证
- if (value.isNotEmpty) {
- _validateInput(value);
- } else {
- setState(() {
- _hasError = false;
- });
- }
- },
- enabled: widget.enabled,
- style: TextStyle(
- color: Get.reactiveTheme.textTheme.bodyLarge!.color,
- fontSize: 14,
- height: 1.6,
- fontWeight: FontWeight.w400,
- ),
- decoration: InputDecoration(
- hintText: widget.hintText,
- hintStyle: TextStyle(
- color: Get.reactiveTheme.hintColor,
- fontSize: 14,
- height: 1.6,
- fontWeight: FontWeight.w400,
- ),
- prefixIcon: widget.prefixIcon != null
- ? Icon(
- widget.prefixIcon,
- color: Get.reactiveTheme.hintColor,
- size: 20,
- )
- : null,
- suffixIcon: _buildSuffixIcon(),
- border: InputBorder.none,
- contentPadding: const EdgeInsets.symmetric(
- vertical: 12,
- horizontal: 16,
- ),
- isDense: true,
- ),
- cursorColor: Get.reactiveTheme.shadowColor,
- ),
- ),
- if (!_hasError && _hasFocus && widget.tipText != null)
- Padding(
- padding: EdgeInsets.only(top: 10.w),
- child: Text(
- widget.tipText!,
- style: TextStyle(
- color: Get.reactiveTheme.shadowColor,
- fontSize: 13.sp,
- height: 1.4,
- fontWeight: FontWeight.w400,
- ),
- ),
- ),
- if (widget.errorText != null && _hasError)
- Padding(
- padding: EdgeInsets.only(top: 10.w),
- child: Text(
- widget.errorText!,
- style: TextStyle(
- color: DarkThemeColors.errorColor,
- fontSize: 13.sp,
- height: 1.4,
- fontWeight: FontWeight.w400,
- ),
- ),
- ),
- ],
- );
- }
- Widget? _buildSuffixIcon() {
- if (widget.isPassword) {
- return Row(
- mainAxisSize: MainAxisSize.min,
- mainAxisAlignment: MainAxisAlignment.end,
- children: [
- if (widget.showClearButton && widget.controller.text.isNotEmpty)
- ClickOpacity(
- child: Icon(
- Icons.clear,
- color: Get.reactiveTheme.hintColor,
- size: 20,
- ),
- onTap: () {
- widget.controller.clear();
- if (widget.onChanged != null) {
- widget.onChanged!('');
- }
- setState(() {
- _hasError = false;
- });
- },
- ),
- 8.horizontalSpace,
- ClickOpacity(
- child: Icon(
- _obscureText ? Icons.visibility_off : Icons.visibility,
- color: Get.reactiveTheme.hintColor,
- size: 20,
- ),
- onTap: () {
- setState(() {
- _obscureText = !_obscureText;
- });
- },
- ),
- 16.horizontalSpace,
- ],
- );
- } else if (widget.showClearButton && widget.controller.text.isNotEmpty) {
- return Row(
- mainAxisSize: MainAxisSize.min,
- mainAxisAlignment: MainAxisAlignment.end,
- children: [
- ClickOpacity(
- child: Icon(
- Icons.clear,
- color: Get.reactiveTheme.hintColor,
- size: 20,
- ),
- onTap: () {
- widget.controller.clear();
- if (widget.onChanged != null) {
- widget.onChanged!('');
- }
- setState(() {
- _hasError = false;
- });
- },
- ),
- 16.horizontalSpace,
- ],
- );
- }
- return null;
- }
- }
|