/// 时间: 2026-04-03 /// 功能: 骨架屏组件 /// 介绍: 用于在数据加载时显示占位动画,避免页面闪白 /// 最新变化: 新建文件 import 'package:flutter/material.dart'; /// 骨架屏基础组件 class SkeletonContainer extends StatefulWidget { final double width; final double height; final double borderRadius; final Color? baseColor; final Color? highlightColor; const SkeletonContainer({ super.key, required this.width, required this.height, this.borderRadius = 8, this.baseColor, this.highlightColor, }); @override State createState() => _SkeletonContainerState(); } class _SkeletonContainerState extends State with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation _animation; @override void initState() { super.initState(); _controller = AnimationController( duration: const Duration(milliseconds: 1500), vsync: this, )..repeat(); _animation = Tween(begin: -1, end: 2).animate( CurvedAnimation(parent: _controller, curve: Curves.easeInOutSine), ); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return AnimatedBuilder( animation: _animation, builder: (context, child) { return Container( width: widget.width, height: widget.height, decoration: BoxDecoration( borderRadius: BorderRadius.circular(widget.borderRadius), gradient: LinearGradient( begin: Alignment.centerLeft, end: Alignment.centerRight, colors: [ widget.baseColor ?? Colors.grey[300]!, widget.highlightColor ?? Colors.grey[100]!, widget.baseColor ?? Colors.grey[300]!, ], stops: [ _animation.value - 0.3, _animation.value, _animation.value + 0.3, ], ), ), ); }, ); } } /// 诗词卡片骨架屏 class PoetryCardSkeleton extends StatelessWidget { final bool isDark; const PoetryCardSkeleton({super.key, this.isDark = false}); @override Widget build(BuildContext context) { final baseColor = isDark ? const Color(0xFF3A3A3A) : Colors.grey[300]; final highlightColor = isDark ? const Color(0xFF4A4A4A) : Colors.grey[100]; return Container( margin: const EdgeInsets.symmetric(horizontal: 16), decoration: BoxDecoration( color: isDark ? const Color(0xFF2A2A2A) : Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withAlpha(isDark ? 40 : 10), blurRadius: 8, offset: const Offset(0, 2), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 标题区域骨架 Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), child: SkeletonContainer( width: double.infinity, height: 24, borderRadius: 4, baseColor: baseColor, highlightColor: highlightColor, ), ), // 诗句名称区域骨架 Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Column( children: [ SkeletonContainer( width: 120, height: 28, borderRadius: 4, baseColor: baseColor, highlightColor: highlightColor, ), const SizedBox(height: 8), SkeletonContainer( width: 80, height: 16, borderRadius: 4, baseColor: baseColor, highlightColor: highlightColor, ), ], ), ), // 原文区域骨架 Padding( padding: const EdgeInsets.all(16), child: Column( children: [ SkeletonContainer( width: double.infinity, height: 20, borderRadius: 4, baseColor: baseColor, highlightColor: highlightColor, ), const SizedBox(height: 8), SkeletonContainer( width: double.infinity, height: 20, borderRadius: 4, baseColor: baseColor, highlightColor: highlightColor, ), const SizedBox(height: 8), SkeletonContainer( width: double.infinity, height: 20, borderRadius: 4, baseColor: baseColor, highlightColor: highlightColor, ), const SizedBox(height: 8), SkeletonContainer( width: 150, height: 20, borderRadius: 4, baseColor: baseColor, highlightColor: highlightColor, ), ], ), ), // 关键词区域骨架 Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Wrap( spacing: 8, runSpacing: 8, children: [ SkeletonContainer( width: 60, height: 24, borderRadius: 12, baseColor: baseColor, highlightColor: highlightColor, ), SkeletonContainer( width: 80, height: 24, borderRadius: 12, baseColor: baseColor, highlightColor: highlightColor, ), SkeletonContainer( width: 70, height: 24, borderRadius: 12, baseColor: baseColor, highlightColor: highlightColor, ), ], ), ), // 译文区域骨架 Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SkeletonContainer( width: 80, height: 18, borderRadius: 4, baseColor: baseColor, highlightColor: highlightColor, ), const SizedBox(height: 12), SkeletonContainer( width: double.infinity, height: 16, borderRadius: 4, baseColor: baseColor, highlightColor: highlightColor, ), const SizedBox(height: 8), SkeletonContainer( width: double.infinity, height: 16, borderRadius: 4, baseColor: baseColor, highlightColor: highlightColor, ), const SizedBox(height: 8), SkeletonContainer( width: double.infinity, height: 16, borderRadius: 4, baseColor: baseColor, highlightColor: highlightColor, ), ], ), ), ], ), ); } }