Flutter如何为商品卡片实现骨架加载器替代圆形进度指示器
替换FutureBuilder加载状态为商品卡片骨架加载器
方法一:自定义骨架加载组件
你可以手动实现模拟商品卡片结构的骨架组件,搭配渐变动画模拟加载效果:
- 先定义基础骨架结构(匹配你的商品卡片布局):
class ProductSkeleton extends StatelessWidget { const ProductSkeleton({super.key}); @override Widget build(BuildContext context) { return Container( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.grey[200], borderRadius: BorderRadius.circular(8), ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 商品图片占位 Container( width: 80, height: 80, decoration: BoxDecoration( color: Colors.grey[300], borderRadius: BorderRadius.circular(8), ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 商品名称占位 Container( height: 16, width: double.infinity, color: Colors.grey[300], margin: const EdgeInsets.only(bottom: 8), ), // 商品价格占位 Container( height: 14, width: 60, color: Colors.grey[300], margin: const EdgeInsets.only(bottom: 8), ), // 操作按钮占位 Container( height: 32, width: 80, color: Colors.grey[300], ), ], ), ), ], ), ); } }
- 给骨架添加渐变动画(让加载效果更自然):
class AnimatedProductSkeleton extends StatefulWidget { const AnimatedProductSkeleton({super.key}); @override State<AnimatedProductSkeleton> createState() => _AnimatedProductSkeletonState(); } class _AnimatedProductSkeletonState extends State<AnimatedProductSkeleton> with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation<double> _animation; @override void initState() { super.initState(); _controller = AnimationController( duration: const Duration(seconds: 1), vsync: this, )..repeat(reverse: true); _animation = Tween<double>(begin: -1.0, end: 2.0).animate(CurvedAnimation(parent: _controller, curve: Curves.easeInOut)); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return ShaderMask( shaderCallback: (bounds) { return LinearGradient( begin: Alignment.centerLeft, end: Alignment.centerRight, colors: [Colors.grey[200]!, Colors.grey[300]!, Colors.grey[200]!], stops: const [0.0, 0.5, 1.0], transform: SlideTransform(_animation.value), ).createShader(bounds); }, blendMode: BlendMode.srcATop, child: const ProductSkeleton(), ); } } class SlideTransform extends GradientTransform { final double offset; SlideTransform(this.offset); @override Matrix4? transform(Rect bounds, {TextDirection? textDirection}) { return Matrix4.translationValues(offset * bounds.width, 0.0, 0.0); } }
- 修改你的FutureBuilder代码,替换等待状态的组件:
FutureBuilder<List<Product>>( future: productFuture, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { // 返回多个骨架,模拟商品列表加载状态 return ListView.builder( itemCount: 5, itemBuilder: (context, index) { return const AnimatedProductSkeleton(); }, ); } else if (snapshot.hasData) { final catalog = snapshot.data; return buildCatalog(catalog!); } else { return const Text("No widget to build"); } }, ),
方法二:使用第三方库快速实现
用shimmer库可以快速实现骨架加载效果:
- 在
pubspec.yaml添加依赖:
dependencies: shimmer: ^3.0.0
- 修改FutureBuilder代码:
import 'package:shimmer/shimmer.dart'; FutureBuilder<List<Product>>( future: productFuture, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return Shimmer.fromColors( baseColor: Colors.grey[300]!, highlightColor: Colors.grey[100]!, child: ListView.builder( itemCount: 5, itemBuilder: (context, index) { return const ProductSkeleton(); // 复用自定义的骨架结构 }, ), ); } else if (snapshot.hasData) { final catalog = snapshot.data; return buildCatalog(catalog!); } else { return const Text("No widget to build"); } }, ),
内容的提问来源于stack exchange,提问作者st4tic
相关产品推荐
相关产品推荐

