import 'package:flutter/material.dart'; /// 加载组件 class LoadingWidget extends StatelessWidget { final String? message; final double? size; final Color? color; final EdgeInsetsGeometry? padding; final bool showMessage; const LoadingWidget({ super.key, this.message, this.size, this.color, this.padding, this.showMessage = true, }); @override Widget build(BuildContext context) { final theme = Theme.of(context); return Padding( padding: padding ?? const EdgeInsets.all(16), child: Column( mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ SizedBox( width: size ?? 32, height: size ?? 32, child: CircularProgressIndicator( strokeWidth: 3, valueColor: AlwaysStoppedAnimation( color ?? theme.colorScheme.primary, ), ), ), if (showMessage && message != null) ...[ const SizedBox(height: 16), Text( message!, style: theme.textTheme.bodyMedium?.copyWith( color: theme.colorScheme.onSurface.withOpacity(0.7), ), textAlign: TextAlign.center, ), ], ], ), ); } } /// 页面加载组件 class PageLoadingWidget extends StatelessWidget { final String? message; final bool showAppBar; final String? title; const PageLoadingWidget({ super.key, this.message, this.showAppBar = false, this.title, }); @override Widget build(BuildContext context) { return Scaffold( appBar: showAppBar ? AppBar( title: title != null ? Text(title!) : null, backgroundColor: Theme.of(context).colorScheme.surface, elevation: 0, ) : null, body: Center( child: LoadingWidget( message: message ?? '加载中...', size: 48, ), ), ); } } /// 列表加载组件 class ListLoadingWidget extends StatelessWidget { final int itemCount; final double itemHeight; final EdgeInsetsGeometry? padding; final EdgeInsetsGeometry? margin; const ListLoadingWidget({ super.key, this.itemCount = 5, this.itemHeight = 80, this.padding, this.margin, }); @override Widget build(BuildContext context) { return ListView.builder( padding: padding, itemCount: itemCount, itemBuilder: (context, index) => Container( height: itemHeight, margin: margin ?? const EdgeInsets.symmetric( horizontal: 16, vertical: 8, ), child: const ShimmerWidget(), ), ); } } /// 骨架屏组件 class ShimmerWidget extends StatefulWidget { final double? width; final double? height; final BorderRadius? borderRadius; final Color? baseColor; final Color? highlightColor; const ShimmerWidget({ super.key, this.width, this.height, this.borderRadius, this.baseColor, this.highlightColor, }); @override State createState() => _ShimmerWidgetState(); } class _ShimmerWidgetState extends State with SingleTickerProviderStateMixin { late AnimationController _animationController; late Animation _animation; @override void initState() { super.initState(); _animationController = AnimationController( duration: const Duration(milliseconds: 1500), vsync: this, ); _animation = Tween( begin: -1.0, end: 2.0, ).animate(CurvedAnimation( parent: _animationController, curve: Curves.easeInOut, )); _animationController.repeat(); } @override void dispose() { _animationController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final theme = Theme.of(context); final baseColor = widget.baseColor ?? theme.colorScheme.surfaceVariant; final highlightColor = widget.highlightColor ?? theme.colorScheme.surface.withOpacity(0.8); return AnimatedBuilder( animation: _animation, builder: (context, child) { return Container( width: widget.width, height: widget.height, decoration: BoxDecoration( borderRadius: widget.borderRadius ?? BorderRadius.circular(8), gradient: LinearGradient( begin: Alignment.centerLeft, end: Alignment.centerRight, colors: [ baseColor, highlightColor, baseColor, ], stops: [ _animation.value - 0.3, _animation.value, _animation.value + 0.3, ], ), ), ); }, ); } } /// 卡片骨架屏 class CardShimmerWidget extends StatelessWidget { final EdgeInsetsGeometry? margin; final double? height; const CardShimmerWidget({ super.key, this.margin, this.height, }); @override Widget build(BuildContext context) { return Container( margin: margin ?? const EdgeInsets.symmetric( horizontal: 16, vertical: 8, ), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Theme.of(context).colorScheme.surface, borderRadius: BorderRadius.circular(12), border: Border.all( color: Theme.of(context).colorScheme.outline.withOpacity(0.2), ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const ShimmerWidget( width: 40, height: 40, borderRadius: BorderRadius.all(Radius.circular(20)), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ShimmerWidget( width: double.infinity, height: 16, borderRadius: BorderRadius.circular(4), ), const SizedBox(height: 8), ShimmerWidget( width: 120, height: 12, borderRadius: BorderRadius.circular(4), ), ], ), ), ], ), if (height != null && height! > 100) ...[ const SizedBox(height: 16), ShimmerWidget( width: double.infinity, height: 12, borderRadius: BorderRadius.circular(4), ), const SizedBox(height: 8), ShimmerWidget( width: double.infinity, height: 12, borderRadius: BorderRadius.circular(4), ), const SizedBox(height: 8), ShimmerWidget( width: 200, height: 12, borderRadius: BorderRadius.circular(4), ), ], ], ), ); } } /// 按钮加载状态 class LoadingButton extends StatelessWidget { final String text; final VoidCallback? onPressed; final bool isLoading; final IconData? icon; final Color? backgroundColor; final Color? foregroundColor; final EdgeInsetsGeometry? padding; final double? width; final double? height; final BorderRadius? borderRadius; const LoadingButton({ super.key, required this.text, this.onPressed, this.isLoading = false, this.icon, this.backgroundColor, this.foregroundColor, this.padding, this.width, this.height, this.borderRadius, }); @override Widget build(BuildContext context) { final theme = Theme.of(context); return SizedBox( width: width, height: height ?? 48, child: ElevatedButton( onPressed: isLoading ? null : onPressed, style: ElevatedButton.styleFrom( backgroundColor: backgroundColor ?? theme.colorScheme.primary, foregroundColor: foregroundColor ?? theme.colorScheme.onPrimary, padding: padding ?? const EdgeInsets.symmetric( horizontal: 24, vertical: 12, ), shape: RoundedRectangleBorder( borderRadius: borderRadius ?? BorderRadius.circular(8), ), elevation: 0, ), child: isLoading ? SizedBox( width: 20, height: 20, child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation( foregroundColor ?? theme.colorScheme.onPrimary, ), ), ) : Row( mainAxisSize: MainAxisSize.min, children: [ if (icon != null) ...[ Icon(icon, size: 18), const SizedBox(width: 8), ], Text( text, style: theme.textTheme.titleSmall?.copyWith( fontWeight: FontWeight.w600, ), ), ], ), ), ); } } /// 刷新指示器 class RefreshIndicatorWidget extends StatelessWidget { final Widget child; final Future Function() onRefresh; final String? refreshText; const RefreshIndicatorWidget({ super.key, required this.child, required this.onRefresh, this.refreshText, }); @override Widget build(BuildContext context) { return RefreshIndicator( onRefresh: onRefresh, color: Theme.of(context).colorScheme.primary, backgroundColor: Theme.of(context).colorScheme.surface, child: child, ); } }