import 'package:flutter/material.dart'; import '../../../core/theme/app_theme.dart'; /// 自定义按钮组件 - 提供多种样式和状态的可复用按钮 /// 支持主要按钮、次要按钮、文本按钮、图标按钮等多种类型 /// 提供加载状态、禁用状态等交互反馈 class CustomButton extends StatelessWidget { /// 按钮文本内容 final String text; /// 按钮点击回调函数 final VoidCallback? onPressed; /// 按钮类型,决定外观样式 final ButtonType buttonType; /// 按钮尺寸,影响整体大小 final ButtonSize buttonSize; /// 是否显示加载状态 final bool isLoading; /// 是否禁用按钮 final bool isDisabled; /// 前置图标,显示在文本左侧 final IconData? prefixIcon; /// 后置图标,显示在文本右侧 final IconData? suffixIcon; /// 自定义背景颜色,优先级高于buttonType final Color? backgroundColor; /// 自定义文本颜色,优先级高于buttonType final Color? textColor; /// 按钮圆角半径,null时使用默认值 final double? borderRadius; /// 按钮最小宽度,null时根据内容自适应 final double? minWidth; /// 按钮最大宽度,null时无限制 final double? maxWidth; /// 内边距,null时使用默认内边距 final EdgeInsetsGeometry? padding; /// 阴影高度,null时根据按钮类型使用默认阴影 final double? elevation; const CustomButton({ super.key, required this.text, required this.onPressed, this.buttonType = ButtonType.primary, this.buttonSize = ButtonSize.medium, this.isLoading = false, this.isDisabled = false, this.prefixIcon, this.suffixIcon, this.backgroundColor, this.textColor, this.borderRadius, this.minWidth, this.maxWidth, this.padding, this.elevation, }); /// 创建主要按钮 - 用于主要操作,视觉突出 factory CustomButton.primary({ required String text, required VoidCallback? onPressed, bool isLoading = false, bool isDisabled = false, IconData? prefixIcon, IconData? suffixIcon, double? minWidth, double? maxWidth, }) { return CustomButton( text: text, onPressed: onPressed, buttonType: ButtonType.primary, isLoading: isLoading, isDisabled: isDisabled, prefixIcon: prefixIcon, suffixIcon: suffixIcon, minWidth: minWidth, maxWidth: maxWidth, ); } /// 创建次要按钮 - 用于次要操作,视觉弱化 factory CustomButton.secondary({ required String text, required VoidCallback? onPressed, bool isLoading = false, bool isDisabled = false, IconData? prefixIcon, IconData? suffixIcon, double? minWidth, double? maxWidth, }) { return CustomButton( text: text, onPressed: onPressed, buttonType: ButtonType.secondary, isLoading: isLoading, isDisabled: isDisabled, prefixIcon: prefixIcon, suffixIcon: suffixIcon, minWidth: minWidth, maxWidth: maxWidth, ); } /// 创建文本按钮 - 无边框无背景,适合嵌入其他组件 factory CustomButton.text({ required String text, required VoidCallback? onPressed, bool isLoading = false, bool isDisabled = false, IconData? prefixIcon, IconData? suffixIcon, Color? textColor, double? minWidth, double? maxWidth, }) { return CustomButton( text: text, onPressed: onPressed, buttonType: ButtonType.text, isLoading: isLoading, isDisabled: isDisabled, prefixIcon: prefixIcon, suffixIcon: suffixIcon, textColor: textColor, minWidth: minWidth, maxWidth: maxWidth, ); } /// 创建轮廓按钮 - 有边框无填充,适合次要操作 factory CustomButton.outlined({ required String text, required VoidCallback? onPressed, bool isLoading = false, bool isDisabled = false, IconData? prefixIcon, IconData? suffixIcon, double? minWidth, double? maxWidth, }) { return CustomButton( text: text, onPressed: onPressed, buttonType: ButtonType.outlined, isLoading: isLoading, isDisabled: isDisabled, prefixIcon: prefixIcon, suffixIcon: suffixIcon, minWidth: minWidth, maxWidth: maxWidth, ); } /// 创建危险按钮 - 用于删除等危险操作,通常使用红色主题 factory CustomButton.danger({ required String text, required VoidCallback? onPressed, bool isLoading = false, bool isDisabled = false, IconData? prefixIcon, IconData? suffixIcon, double? minWidth, double? maxWidth, }) { return CustomButton( text: text, onPressed: onPressed, buttonType: ButtonType.danger, isLoading: isLoading, isDisabled: isDisabled, prefixIcon: prefixIcon, suffixIcon: suffixIcon, minWidth: minWidth, maxWidth: maxWidth, ); } @override Widget build(BuildContext context) { final theme = Theme.of(context); final appColors = AppColors.of(context); // 确定按钮是否可点击 final bool buttonEnabled = !isLoading && !isDisabled && onPressed != null; // 获取按钮样式配置 final buttonStyle = _getButtonStyle(theme, appColors); final textStyle = _getTextStyle(theme, appColors); final paddingValue = _getPadding(); // 构建按钮内容 final buttonContent = isLoading ? SizedBox( width: textStyle.fontSize, height: textStyle.fontSize, child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation( buttonStyle.foregroundColor?.resolve({}) ?? theme.colorScheme.onPrimary, ), ), ) : Row( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, children: [ if (prefixIcon != null) ...[ Icon( prefixIcon, size: textStyle.fontSize! * 1.2, color: textStyle.color, ), const SizedBox(width: 8), ], Text( text, style: textStyle, overflow: TextOverflow.ellipsis, maxLines: 1, ), if (suffixIcon != null) ...[ const SizedBox(width: 8), Icon( suffixIcon, size: textStyle.fontSize! * 1.2, color: textStyle.color, ), ], ], ); // 根据按钮类型构建不同的按钮组件 switch (buttonType) { case ButtonType.primary: case ButtonType.secondary: case ButtonType.danger: return ConstrainedBox( constraints: BoxConstraints( minWidth: minWidth ?? 0, maxWidth: maxWidth ?? double.infinity, minHeight: _getMinHeight(), ), child: ElevatedButton( onPressed: buttonEnabled ? onPressed : null, style: ElevatedButton.styleFrom( backgroundColor: buttonStyle.backgroundColor?.resolve({}), foregroundColor: buttonStyle.foregroundColor?.resolve({}), elevation: elevation ?? (buttonEnabled ? 2 : 0), padding: paddingValue, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular( borderRadius ?? _getDefaultBorderRadius()), ), disabledBackgroundColor: buttonStyle.backgroundColor?.resolve({MaterialState.disabled}), disabledForegroundColor: buttonStyle.foregroundColor?.resolve({MaterialState.disabled}), ), child: buttonContent, ), ); case ButtonType.outlined: return ConstrainedBox( constraints: BoxConstraints( minWidth: minWidth ?? 0, maxWidth: maxWidth ?? double.infinity, minHeight: _getMinHeight(), ), child: OutlinedButton( onPressed: buttonEnabled ? onPressed : null, style: OutlinedButton.styleFrom( foregroundColor: buttonStyle.foregroundColor?.resolve({}), padding: paddingValue, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular( borderRadius ?? _getDefaultBorderRadius()), ), side: BorderSide( color: buttonEnabled ? (buttonStyle.foregroundColor?.resolve({}) ?? theme.colorScheme.primary) : (buttonStyle.foregroundColor?.resolve({MaterialState.disabled}) ?? theme.colorScheme.onSurface.withOpacity(0.12)), ), disabledForegroundColor: buttonStyle.foregroundColor?.resolve({MaterialState.disabled}), ), child: buttonContent, ), ); case ButtonType.text: return ConstrainedBox( constraints: BoxConstraints( minWidth: minWidth ?? 0, maxWidth: maxWidth ?? double.infinity, minHeight: _getMinHeight(), ), child: TextButton( onPressed: buttonEnabled ? onPressed : null, style: TextButton.styleFrom( foregroundColor: buttonStyle.foregroundColor?.resolve({}), padding: paddingValue, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular( borderRadius ?? _getDefaultBorderRadius()), ), disabledForegroundColor: buttonStyle.foregroundColor?.resolve({MaterialState.disabled}), ), child: buttonContent, ), ); } } /// 获取按钮样式配置 - 根据按钮类型和主题返回对应的样式 ButtonStyle _getButtonStyle(ThemeData theme, AppColors appColors) { switch (buttonType) { case ButtonType.primary: return ButtonStyle( backgroundColor: MaterialStateProperty.resolveWith((states) { if (states.contains(MaterialState.disabled)) { return backgroundColor?.withOpacity(0.12) ?? theme.colorScheme.primary.withOpacity(0.12); } return backgroundColor ?? theme.colorScheme.primary; }), foregroundColor: MaterialStateProperty.resolveWith((states) { if (states.contains(MaterialState.disabled)) { return textColor?.withOpacity(0.38) ?? theme.colorScheme.onPrimary.withOpacity(0.38); } return textColor ?? theme.colorScheme.onPrimary; }), ); case ButtonType.secondary: return ButtonStyle( backgroundColor: MaterialStateProperty.resolveWith((states) { if (states.contains(MaterialState.disabled)) { return (backgroundColor ?? theme.colorScheme.secondaryContainer) .withOpacity(0.12); } return backgroundColor ?? theme.colorScheme.secondaryContainer; }), foregroundColor: MaterialStateProperty.resolveWith((states) { if (states.contains(MaterialState.disabled)) { return (textColor ?? theme.colorScheme.onSecondaryContainer) .withOpacity(0.38); } return textColor ?? theme.colorScheme.onSecondaryContainer; }), ); case ButtonType.danger: return ButtonStyle( backgroundColor: MaterialStateProperty.resolveWith((states) { if (states.contains(MaterialState.disabled)) { return (backgroundColor ?? AppColors.error).withOpacity(0.12); } return backgroundColor ?? AppColors.error; }), foregroundColor: MaterialStateProperty.resolveWith((states) { if (states.contains(MaterialState.disabled)) { return (textColor ?? AppColors.onError).withOpacity(0.38); } return textColor ?? AppColors.onError; }), ); case ButtonType.outlined: case ButtonType.text: return ButtonStyle( foregroundColor: MaterialStateProperty.resolveWith((states) { if (states.contains(MaterialState.disabled)) { return (textColor ?? theme.colorScheme.primary).withOpacity(0.38); } return textColor ?? theme.colorScheme.primary; }), ); } } /// 获取文本样式 - 根据按钮尺寸返回对应的文本样式 TextStyle _getTextStyle(ThemeData theme, AppColors appColors) { final baseStyle = theme.textTheme.labelLarge ?? const TextStyle(); switch (buttonSize) { case ButtonSize.small: return baseStyle.copyWith( fontSize: 12, fontWeight: FontWeight.w500, color: _getButtonStyle(theme, appColors) .foregroundColor ?.resolve(isDisabled || isLoading ? {MaterialState.disabled} : {}), ); case ButtonSize.medium: return baseStyle.copyWith( fontSize: 14, fontWeight: FontWeight.w600, color: _getButtonStyle(theme, appColors) .foregroundColor ?.resolve(isDisabled || isLoading ? {MaterialState.disabled} : {}), ); case ButtonSize.large: return baseStyle.copyWith( fontSize: 16, fontWeight: FontWeight.w700, color: _getButtonStyle(theme, appColors) .foregroundColor ?.resolve(isDisabled || isLoading ? {MaterialState.disabled} : {}), ); } } /// 获取内边距 - 根据按钮尺寸返回对应的内边距 EdgeInsetsGeometry _getPadding() { if (padding != null) return padding!; switch (buttonSize) { case ButtonSize.small: return const EdgeInsets.symmetric(horizontal: 12, vertical: 8); case ButtonSize.medium: return const EdgeInsets.symmetric(horizontal: 16, vertical: 12); case ButtonSize.large: return const EdgeInsets.symmetric(horizontal: 24, vertical: 16); } } /// 获取最小高度 - 根据按钮尺寸返回对应的最小高度 double _getMinHeight() { switch (buttonSize) { case ButtonSize.small: return 32; case ButtonSize.medium: return 40; case ButtonSize.large: return 48; } } /// 获取默认圆角半径 - 根据按钮尺寸返回对应的圆角半径 double _getDefaultBorderRadius() { switch (buttonSize) { case ButtonSize.small: return 4; case ButtonSize.medium: return 8; case ButtonSize.large: return 12; } } } /// 按钮类型枚举 - 定义按钮的视觉样式 enum ButtonType { /// 主要按钮 - 用于主要操作,视觉突出,使用主题主色调 primary, /// 次要按钮 - 用于次要操作,视觉弱化,使用主题次要色调 secondary, /// 轮廓按钮 - 有边框无填充,适合次要操作 outlined, /// 文本按钮 - 无边框无背景,适合嵌入其他组件 text, /// 危险按钮 - 用于删除等危险操作,通常使用红色主题 danger, } /// 按钮尺寸枚举 - 定义按钮的大小 enum ButtonSize { /// 小尺寸 - 适合紧凑布局,内边距较小 small, /// 中等尺寸 - 默认尺寸,适合大多数场景 medium, /// 大尺寸 - 适合重要操作或触摸友好的场景 large, }