各大行业网站免费域名网站申请

张小明 2025/12/27 23:30:35
各大行业网站,免费域名网站申请,lnmp装wordpress,asp简单的网站怎么做在 Flutter 开发中#xff0c;表单输入#xff08;登录、注册、设置页#xff09;是高频场景。原生 TextField 存在样式配置繁琐、校验逻辑分散、交互反馈单一等问题。本文封装的 CustomInputWidget 整合 “统一样式 实时校验 输入格式化 交互反馈” 四大核心能力#x…在 Flutter 开发中表单输入登录、注册、设置页是高频场景。原生TextField存在样式配置繁琐、校验逻辑分散、交互反馈单一等问题。本文封装的CustomInputWidget整合 “统一样式 实时校验 输入格式化 交互反馈” 四大核心能力支持手机号、密码、验证码等 10 场景一行代码调用覆盖 90% 表单需求。一、核心优势样式统一支持边框 / 下划线两种风格颜色、圆角可统一配置无需重复写样式实时校验内置手机号、邮箱、密码等校验规则支持自定义校验实时反馈错误输入格式化手机号3-4-4 分隔、金额保留两位小数等自动处理提升体验交互优化密码可见切换、清除输入、图标点击聚焦 / 错误状态高亮高扩展左侧图标、右侧自定义组件如验证码倒计时可灵活嵌入二、核心配置速览配置分类核心参数核心作用必选配置controller、hintText输入控制器、占位提示文本样式配置borderType、focusColor、inputStyle边框风格、聚焦颜色、文本样式校验配置inputType、validator、errorText输入类型、自定义校验、错误提示交互配置isPassword、showClearBtn、onIconTap密码类型、清除按钮、图标点击扩展配置prefixIcon、suffixWidget、formatter左侧图标、右侧组件、输入格式化三、完整代码可直接复制dartimport package:flutter/material.dart; import package:flutter/services.dart; /// 输入框边框类型枚举 enum InputBorderType { outline, underline, none } /// 预设输入类型枚举 enum InputType { normal, phone, email, password, code, idCard, number } /// 通用表单输入组件 class CustomInputWidget extends StatefulWidget { // 必选参数 final TextEditingController controller; final String hintText; // 样式配置 final InputBorderType borderType; final TextStyle inputStyle; final TextStyle hintStyle; final Color focusColor; final Color normalColor; final Color errorColor; final double borderWidth; final double borderRadius; final EdgeInsetsGeometry padding; // 校验配置 final InputType inputType; final String? errorText; final String? Function(String?)? validator; final bool autoValidate; // 交互配置 final bool isPassword; final bool showClearBtn; final bool enabled; final bool readOnly; final int maxLength; final int maxLines; // 扩展配置 final Widget? prefixIcon; final VoidCallback? onIconTap; final Widget? suffixWidget; final ListTextInputFormatter? formatter; final ValueChangedString? onChanged; final VoidCallback? onEditingComplete; // 适配配置 final bool adaptDarkMode; const CustomInputWidget({ super.key, required this.controller, required this.hintText, this.borderType InputBorderType.outline, this.inputStyle const TextStyle(fontSize: 16, color: Colors.black87), this.hintStyle const TextStyle(fontSize: 16, color: Colors.grey), this.focusColor Colors.blue, this.normalColor Colors.grey, this.errorColor Colors.redAccent, this.borderWidth 1.0, this.borderRadius 8.0, this.padding const EdgeInsets.symmetric(horizontal: 16, vertical: 14), this.inputType InputType.normal, this.errorText, this.validator, this.autoValidate true, this.isPassword false, this.showClearBtn true, this.enabled true, this.readOnly false, this.maxLength 100, this.maxLines 1, this.prefixIcon, this.onIconTap, this.suffixWidget, this.formatter, this.onChanged, this.onEditingComplete, this.adaptDarkMode true, }); override StateCustomInputWidget createState() _CustomInputWidgetState(); } class _CustomInputWidgetState extends StateCustomInputWidget { late FocusNode _focusNode; bool _isFocused false; bool _showPassword false; bool _showClearBtn false; String? _currentErrorText; // 预设输入格式化器 ListTextInputFormatter get _defaultFormatters { switch (widget.inputType) { case InputType.phone: return [FilteringTextInputFormatter.digitsOnly, LengthLimitingTextInputFormatter(13), _PhoneInputFormatter()]; case InputType.code: return [FilteringTextInputFormatter.digitsOnly, LengthLimitingTextInputFormatter(6)]; case InputType.idCard: return [FilteringTextInputFormatter.allow(RegExp(r[0-9Xx])), LengthLimitingTextInputFormatter(18)]; case InputType.number: return [_NumberInputFormatter()]; default: return [LengthLimitingTextInputFormatter(widget.maxLength)]; } } // 预设校验规则 String? _defaultValidator(String? value) { if (value null || value.trim().isEmpty) return 请输入${_getInputTypeName()}; switch (widget.inputType) { case InputType.phone: final purePhone value.replaceAll(RegExp(r\D), ); return purePhone.length ! 11 ? 请输入11位有效手机号 : null; case InputType.email: final emailReg RegExp(r^[\w-](\.[\w-])*[\w-](\.[\w-])$); return emailReg.hasMatch(value) ? null : 请输入有效邮箱地址; case InputType.password: if (value.length 6 || value.length 20) return 密码长度为6-20位; final hasLetter RegExp(r[a-zA-Z]).hasMatch(value); final hasNumber RegExp(r[0-9]).hasMatch(value); return (hasLetter hasNumber) ? null : 密码需包含字母和数字; case InputType.code: return (value.length 4 value.length 6) ? null : 请输入4-6位验证码; case InputType.idCard: final idReg RegExp(r^\d{17}[\dXx]$); return idReg.hasMatch(value) ? null : 请输入18位有效身份证号; default: return null; } } String _getInputTypeName() { switch (widget.inputType) { case InputType.phone: return 手机号; case InputType.email: return 邮箱; case InputType.password: return 密码; case InputType.code: return 验证码; case InputType.idCard: return 身份证号; case InputType.number: return 数字; default: return 内容; } } void _validateInput(String value) { if (!widget.autoValidate) return; String? error widget.validator?.call(value) ?? _defaultValidator(value); setState(() _currentErrorText error); } override void initState() { super.initState(); _focusNode FocusNode()..addListener(() setState(() _isFocused _focusNode.hasFocus)); widget.controller.addListener(() { final value widget.controller.text; setState(() _showClearBtn widget.showClearBtn value.isNotEmpty !widget.isPassword); _validateInput(value); widget.onChanged?.call(value); }); _validateInput(widget.controller.text); } override void didUpdateWidget(covariant CustomInputWidget oldWidget) { super.didUpdateWidget(oldWidget); if (widget.errorText ! oldWidget.errorText) setState(() _currentErrorText widget.errorText); } override void dispose() { _focusNode.dispose(); super.dispose(); } Color _adaptDarkMode(Color lightColor, Color darkColor) { if (!widget.adaptDarkMode) return lightColor; return MediaQuery.platformBrightnessOf(context) Brightness.dark ? darkColor : lightColor; } InputBorder _buildBorder() { final currentColor _currentErrorText ! null ? _adaptDarkMode(widget.errorColor, Colors.red[400]!) : (_isFocused ? _adaptDarkMode(widget.focusColor, Colors.blueAccent) : _adaptDarkMode(widget.normalColor, Colors.grey[600]!)); switch (widget.borderType) { case InputBorderType.outline: return OutlineInputBorder(borderSide: BorderSide(color: currentColor, width: widget.borderWidth), borderRadius: BorderRadius.circular(widget.borderRadius)); case InputBorderType.underline: return UnderlineInputBorder(borderSide: BorderSide(color: currentColor, width: widget.borderWidth)); case InputBorderType.none: return InputBorder.none; } } Widget? _buildSuffixWidget() { if (widget.suffixWidget ! null) return widget.suffixWidget; if (widget.isPassword) { return IconButton( icon: Icon(_showPassword ? Icons.visibility : Icons.visibility_off, size: 20, color: _adaptDarkMode(widget.normalColor, Colors.grey[400]!)), onPressed: () setState(() _showPassword !_showPassword), padding: EdgeInsets.zero, constraints: const BoxConstraints(minWidth: 40), ); } if (_showClearBtn) { return IconButton( icon: Icon(Icons.clear, size: 20, color: _adaptDarkMode(widget.normalColor, Colors.grey[400]!)), onPressed: () widget.controller.clear(), padding: EdgeInsets.zero, constraints: const BoxConstraints(minWidth: 40), ); } return null; } override Widget build(BuildContext context) { final formatters [..._defaultFormatters, if (widget.formatter ! null) ...widget.formatter!]; final adaptedInputStyle widget.inputStyle.copyWith(color: _adaptDarkMode(widget.inputStyle.color!, Colors.white70)); final adaptedHintStyle widget.hintStyle.copyWith(color: _adaptDarkMode(widget.hintStyle.color!, Colors.grey[400]!)); return Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ TextField( controller: widget.controller, focusNode: _focusNode, style: adaptedInputStyle, hintText: widget.hintText, hintStyle: adaptedHintStyle, obscureText: widget.isPassword !_showPassword, enabled: widget.enabled, readOnly: widget.readOnly, maxLines: widget.maxLines, inputFormatters: formatters, keyboardType: _getKeyboardType(), decoration: InputDecoration( prefixIcon: widget.prefixIcon ! null ? GestureDetector(onTap: widget.onIconTap, child: Padding(padding: const EdgeInsets.symmetric(horizontal: 12), child: widget.prefixIcon)) : null, suffixIcon: _buildSuffixWidget(), border: _buildBorder(), focusedBorder: _buildBorder(), enabledBorder: _buildBorder(), disabledBorder: _buildBorder(), errorBorder: _buildBorder(), focusedErrorBorder: _buildBorder(), contentPadding: widget.padding, isDense: true, errorText: _currentErrorText, errorStyle: TextStyle(fontSize: 12, color: _adaptDarkMode(widget.errorColor, Colors.red[400]!)), errorMaxLines: 2, ), onEditingComplete: widget.onEditingComplete, ), ], ); } TextInputType _getKeyboardType() { switch (widget.inputType) { case InputType.phone: case InputType.code: return TextInputType.phone; case InputType.email: return TextInputType.emailAddress; case InputType.number: return TextInputType.numberWithOptions(decimal: true); default: return TextInputType.text; } } } /// 手机号格式化器3-4-4分隔 class _PhoneInputFormatter extends TextInputFormatter { override TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) { final text newValue.text.replaceAll(RegExp(r\D), ); final buffer StringBuffer(); for (int i 0; i text.length; i) { buffer.write(text[i]); if (i 2 || i 6) if (i ! text.length - 1) buffer.write(-); } final value buffer.toString(); return newValue.copyWith(text: value, selection: TextSelection.collapsed(offset: value.length)); } } /// 数字格式化器保留两位小数 class _NumberInputFormatter extends TextInputFormatter { override TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) { String value newValue.text; if (!RegExp(r^[\d.]*$).hasMatch(value)) return oldValue; if (value.contains(.) value.indexOf(.) ! value.lastIndexOf(.)) return oldValue; if (value.contains(.)) { final parts value.split(.); if (parts.length 1 parts[1].length 2) value ${parts[0]}.${parts[1].substring(0, 2)}; } if (value.startsWith(.)) value 0$value; return newValue.copyWith(text: value, selection: TextSelection.collapsed(offset: value.length)); } }四、三大高频场景示例场景 1登录页手机号 密码dartclass LoginPage extends StatefulWidget { override StateLoginPage createState() _LoginPageState(); } class _LoginPageState extends StateLoginPage { final TextEditingController _phoneController TextEditingController(); final TextEditingController _pwdController TextEditingController(); bool _isLoginEnabled false; override void initState() { super.initState(); _phoneController.addListener(_checkLoginEnable); _pwdController.addListener(_checkLoginEnable); } void _checkLoginEnable() { final phoneValid _phoneController.text.replaceAll(RegExp(r\D), ).length 11; final pwdValid _pwdController.text.length 6 _pwdController.text.length 20; setState(() _isLoginEnabled phoneValid pwdValid); } override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text(登录)), body: Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), child: Column( children: [ CustomInputWidget( controller: _phoneController, hintText: 请输入手机号, inputType: InputType.phone, prefixIcon: const Icon(Icons.phone, color: Colors.blue), borderType: InputBorderType.underline, ), const SizedBox(height: 20), CustomInputWidget( controller: _pwdController, hintText: 请输入密码, inputType: InputType.password, isPassword: true, prefixIcon: const Icon(Icons.lock, color: Colors.blue), borderType: InputBorderType.underline, ), const SizedBox(height: 40), SizedBox( width: double.infinity, child: ElevatedButton( onPressed: _isLoginEnabled ? () debugPrint(执行登录) : null, child: const Text(登录), ), ), ], ), ), ); } override void dispose() { _phoneController.dispose(); _pwdController.dispose(); super.dispose(); } }场景 2验证码输入带倒计时dartclass CodeInputPage extends StatefulWidget { override StateCodeInputPage createState() _CodeInputPageState(); } class _CodeInputPageState extends StateCodeInputPage { final TextEditingController _codeController TextEditingController(); bool _isCounting false; int _countDown 60; void _getCode() { setState(() _isCounting true); ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text(验证码已发送))); Timer.periodic(const Duration(seconds: 1), (timer) { setState(() { _countDown--; if (_countDown 0) { _isCounting false; _countDown 60; timer.cancel(); } }); }); } override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text(验证手机号)), body: Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), child: Column( children: [ CustomInputWidget( controller: _codeController, hintText: 请输入4位验证码, inputType: InputType.code, prefixIcon: const Icon(Icons.sms, color: Colors.orangeAccent), suffixWidget: Padding( padding: const EdgeInsets.only(right: 8), child: TextButton( onPressed: _isCounting ? null : _getCode, child: Text(_isCounting ? $_countDown秒后重发 : 获取验证码), ), ), ), const SizedBox(height: 30), ElevatedButton( onPressed: _codeController.text.length 4 ? () Navigator.pop(context) : null, child: const Text(验证), ), ], ), ), ); } }场景 3金额输入带格式化dartclass AmountInputPage extends StatefulWidget { override StateAmountInputPage createState() _AmountInputPageState(); } class _AmountInputPageState extends StateAmountInputPage { final TextEditingController _amountController TextEditingController(); final double _maxAmount 10000.0; override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text(充值)), body: Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text(充值金额, style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)), const SizedBox(height: 12), CustomInputWidget( controller: _amountController, hintText: 最多10000元, inputType: InputType.number, prefixIcon: const Icon(Icons.money, color: Colors.green), validator: (value) { if (value null || value.isEmpty) return 请输入金额; final amount double.tryParse(value) ?? 0; if (amount 0.01) return 最小0.01元; if (amount _maxAmount) return 最大10000元; return null; }, ), const SizedBox(height: 20), Wrap( spacing: 12, children: [100, 500, 1000, 2000, 5000, 10000] .map((amount) GestureDetector( onTap: () _amountController.text amount.toString(), child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration(border: Border.all(color: Colors.grey[300]!), borderRadius: BorderRadius.circular(8)), child: Text(¥$amount), ), )) .toList(), ), const Spacer(), SizedBox( width: double.infinity, child: ElevatedButton( onPressed: _amountController.text.isNotEmpty double.parse(_amountController.text) 0.01 ? () ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(充值¥${_amountController.text}))) : null, child: const Text(确认充值), ), ), ], ), ), ); } }五、核心封装技巧分层校验预设规则 自定义校验自定义优先级更高兼顾通用与灵活输入格式化通过TextInputFormatter自动处理手机号、金额格式状态联动内部监听输入与聚焦状态自动切换组件显示插槽设计右侧支持自定义组件适配倒计时等复杂场景深色适配所有颜色通过_adaptDarkMode统一适配无需额外配置六、避坑指南控制器管理外部传入控制器并手动dispose避免内存泄漏校验触发autoValidate: false时需手动校验后提交密码状态isPassword: true后内部自动管理可见性无需外部维护格式化冲突外部formatter与预设合并避免重复限制错误提示errorText外部传入优先级高于内部校验适合接口错误提示https://openharmonycrossplatform.csdn.net/content
版权声明:本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

舟山建设企业网站网站开发代码交接文档书

目录具体实现截图项目介绍论文大纲核心代码部分展示可定制开发之亮点部门介绍结论源码获取详细视频演示 :文章底部获取博主联系方式!同行可合作具体实现截图 本系统(程序源码数据库调试部署讲解)同时还支持Python(flask,django)、…

张小明 2025/12/25 18:07:48 网站建设

南宁网站建设公司业绩网站服务器 维护

本文分享了一个智能问答系统的开发优化过程。针对三个不同子场景的智能问答需求,作者最初采用纯RAG技术建立三个知识库,但效果不佳,出现场景判断不清和召回率低的问题。后通过重新思考,改为按数据类型建立两个知识库(结…

张小明 2025/12/25 18:07:46 网站建设

招聘网站大全互联网营销师题库及答案

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容: 开发一个AI小说插件,支持以下功能:1. 根据关键词自动生成小说情节大纲;2. 提供角色设定模板,包括姓名、性格、背景等;3. …

张小明 2025/12/25 18:07:48 网站建设

开发企业网站的公司短期培训班学什么好

构建动态响应式动画架构:lottie-ios与现代数据流技术融合实践 【免费下载链接】lottie-ios airbnb/lottie-ios: Lottie-ios 是一个用于 iOS 平台的动画库,可以将 Adobe After Effects 动画导出成 iOS 应用程序,具有高性能,易用性和…

张小明 2025/12/25 18:07:50 网站建设

网站有死链接怎么办vi设计费用

导语:腾讯AI Lab联合香港科技大学推出DepthCrafter开源工具,无需额外信息即可为开放世界视频生成时间一致性强、细节丰富的长深度序列,显著降低视频深度估计技术门槛。 【免费下载链接】DepthCrafter DepthCrafter是一款开源工具,…

张小明 2025/12/27 8:14:01 网站建设