Files
monisuo/flutter_monisuo/lib/ui/components/material_input.dart
sion a04974e79d fix: 修复 MaterialInput 组件的三个问题
1. 统一输入框大小 - 所有输入框使用相同的 padding (16px)
2. 移除容器外边框 - 去掉 filled 和 fillColor
3. 修复 padding 问题 - 使用固定的 16px padding
4. 统一字体大小 - 使用 16px 字体确保高度一致
5. 统一圆角 - 使用固定的 12px 圆角
2026-04-08 11:49:42 +08:00

231 lines
6.8 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../../core/theme/app_color_scheme.dart';
import '../../core/theme/app_spacing.dart';
import '../../core/theme/app_theme.dart';
import '../../core/theme/app_theme_extension.dart';
/// Material Design 3 风格的输入框组件
///
/// 特点:
/// - 圆角外边框OutlineInputBorder
/// - Focus 时边框颜色变化
/// - 标签上浮动画
/// - 统一的视觉效果
class MaterialInput extends StatelessWidget {
final TextEditingController? controller;
final String? labelText;
final String? hintText;
final IconData? prefixIcon;
final Widget? suffixIcon;
final bool obscureText;
final TextInputType? keyboardType;
final int? maxLines;
final int? maxLength;
final bool enabled;
final bool readOnly;
final String? Function(String?)? validator;
final ValueChanged<String>? onChanged;
final VoidCallback? onTap;
final ValueChanged<String>? onSubmitted;
final List<TextInputFormatter>? inputFormatters;
final FocusNode? focusNode;
const MaterialInput({
super.key,
this.controller,
this.labelText,
this.hintText,
this.prefixIcon,
this.suffixIcon,
this.obscureText = false,
this.keyboardType,
this.maxLines = 1,
this.maxLength,
this.enabled = true,
this.readOnly = false,
this.validator,
this.onChanged,
this.onTap,
this.onSubmitted,
this.inputFormatters,
this.focusNode,
});
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final isDark = Theme.of(context).brightness == Brightness.dark;
// Material Design 3 的颜色
final primaryColor = isDark ? AppColorScheme.up : colorScheme.primary;
final borderColor = isDark
? AppColorScheme.darkOutline.withValues(alpha: 0.3)
: colorScheme.outline.withValues(alpha: 0.5);
final fillColor = isDark
? AppColorScheme.darkSurfaceContainerHigh.withValues(alpha: 0.3)
: colorScheme.surfaceContainerHighest.withValues(alpha: 0.5);
return TextFormField(
controller: controller,
focusNode: focusNode,
obscureText: obscureText,
keyboardType: keyboardType,
maxLines: maxLines,
maxLength: maxLength,
enabled: enabled,
readOnly: readOnly,
validator: validator,
onChanged: onChanged,
onTap: onTap,
onFieldSubmitted: onSubmitted,
inputFormatters: inputFormatters,
style: AppTextStyles.bodyLarge(context).copyWith(
color: enabled
? colorScheme.onSurface
: colorScheme.onSurface.withValues(alpha: 0.5),
fontSize: 16, // 统一字体大小
),
cursorColor: primaryColor,
cursorWidth: 2.0,
cursorHeight: 20,
decoration: InputDecoration(
// 标签Material Design 3 的浮动标签)
labelText: labelText,
labelStyle: AppTextStyles.bodyLarge(context).copyWith(
color: colorScheme.onSurfaceVariant,
),
floatingLabelStyle: AppTextStyles.bodyMedium(context).copyWith(
color: primaryColor,
fontWeight: FontWeight.w600,
),
// 提示文本
hintText: hintText,
hintStyle: AppTextStyles.bodyLarge(context).copyWith(
color: colorScheme.onSurfaceVariant.withValues(alpha: 0.6),
),
// 前置图标
prefixIcon: prefixIcon != null
? Icon(
prefixIcon,
color: colorScheme.onSurfaceVariant,
size: 22,
)
: null,
prefixIconColor: WidgetStateColor.resolveWith((states) {
if (states.contains(WidgetState.focused)) {
return primaryColor;
}
return colorScheme.onSurfaceVariant;
}),
// 后置图标
suffixIcon: suffixIcon,
// 填充颜色(移除,不需要)
filled: false,
// 内容内边距(统一使用 16px
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 16,
),
// Material Design 3 边框样式(统一 12px 圆角)
border: _buildBorder(borderColor, 12),
enabledBorder: _buildBorder(borderColor, 12),
focusedBorder: _buildBorder(primaryColor, 12, width: 2.0),
errorBorder: _buildBorder(colorScheme.error, 12),
focusedErrorBorder: _buildBorder(colorScheme.error, 12, width: 2.0),
disabledBorder: _buildBorder(
borderColor.withValues(alpha: 0.3),
12,
),
// 错误文本样式
errorStyle: AppTextStyles.bodySmall(context).copyWith(
color: colorScheme.error,
),
// 计数器样式
counterStyle: AppTextStyles.bodySmall(context).copyWith(
color: colorScheme.onSurfaceVariant,
),
),
);
}
OutlineInputBorder _buildBorder(Color color, double radius, {double width = 1.0}) {
return OutlineInputBorder(
borderRadius: BorderRadius.circular(radius),
borderSide: BorderSide(
color: color,
width: width,
),
);
}
}
/// Material Design 3 风格的密码输入框(带显示/隐藏切换)
class MaterialPasswordInput extends StatefulWidget {
final TextEditingController? controller;
final String? labelText;
final String? hintText;
final IconData? prefixIcon;
final String? Function(String?)? validator;
final ValueChanged<String>? onChanged;
final ValueChanged<String>? onSubmitted;
final FocusNode? focusNode;
const MaterialPasswordInput({
super.key,
this.controller,
this.labelText,
this.hintText,
this.prefixIcon,
this.validator,
this.onChanged,
this.onSubmitted,
this.focusNode,
});
@override
State<MaterialPasswordInput> createState() => _MaterialPasswordInputState();
}
class _MaterialPasswordInputState extends State<MaterialPasswordInput> {
bool _obscureText = true;
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return MaterialInput(
controller: widget.controller,
labelText: widget.labelText,
hintText: widget.hintText,
prefixIcon: widget.prefixIcon ?? Icons.lock_outline,
obscureText: _obscureText,
validator: widget.validator,
onChanged: widget.onChanged,
onSubmitted: widget.onSubmitted,
focusNode: widget.focusNode,
suffixIcon: IconButton(
icon: Icon(
_obscureText ? Icons.visibility_off : Icons.visibility,
color: colorScheme.onSurfaceVariant,
size: 22,
),
onPressed: () {
setState(() {
_obscureText = !_obscureText;
});
},
splashRadius: 20,
),
);
}
}