Files
monisuo/flutter_monisuo/lib/ui/components/material_input.dart

231 lines
6.8 KiB
Dart
Raw Normal View History

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,
),
);
}
}