需求
在Flutter项目中,实现一个类似于微信群头像的控件。该控件能够显示多个头像图片,并且根据图片的数量,自动调整布局,确保每个图片都是正方形,且有统一的边框和间距。
效果
该控件能够实现以下效果:
- 根据图片数量,动态调整图片布局。
- 每个图片都有统一的边框和间距。
- 图片显示为正方形,并支持圆角效果。
- 使用网络图片,并支持缓存和占位符。
实现思路
- 控件设计:创建一个
AvatarGroup
控件,接受图片URL列表和一些布局参数。 - 布局计算:根据图片数量,动态计算每个图片的尺寸和位置。
- 图片显示:使用
CachedNetworkImage
加载网络图片,支持缓存、占位符和错误显示。 - 边框和圆角:使用
Container
和BoxDecoration
实现统一的边框和圆角效果。
实现代码
import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; class AvatarGroup extends StatelessWidget { final List<String> imageUrls; final double size; final double space; final Color color; final Color borderColor; final double borderWidth; final double borderRadius; const AvatarGroup({ Key? key, required this.imageUrls, this.size = 150.0, this.space = 4.0, this.color = Colors.grey, this.borderWidth = 3.0, this.borderColor = Colors.grey, this.borderRadius = 4.0, }) : super(key: key); double get width { return size - borderWidth * 2; } int get itemCount { return imageUrls.length; } double get itemWidth { if (itemCount == 1) { return width; } else if (itemCount >= 2 && itemCount <= 4) { return (width - space) / 2; } else { return (width - 2 * space) / 3; } } @override Widget build(BuildContext context) { return Container( decoration: BoxDecoration( color: color, border: Border.all(color: borderColor, width: borderWidth), borderRadius: BorderRadius.circular(borderRadius), ), width: size, height: size, child: Stack( children: _buildAvatarStack(), ), ); } List<Widget> _buildAvatarStack() { List<Widget> avatars = []; for (int i = 0; i < imageUrls.length; i++) { double left = 0; double top = 0; if (itemCount == 1) { left = 0; top = 0; } else if (itemCount == 2) { left = i * itemWidth + i * space; top = (width - itemWidth) / 2; } else if (itemCount == 3) { if (i == 0) { left = (width - itemWidth) / 2; top = 0; } else { left = (i - 1) * itemWidth + (i - 1) * space; top = itemWidth + space; } } else if (itemCount == 4) { if (i == 0 || i == 1) { left = i * itemWidth + i * space; top = 0; } else { left = (i - 2) * itemWidth + (i - 2) * space; top = itemWidth + space; } } else if (itemCount == 5) { if (i == 0 || i == 1) { left = (width - itemWidth * 2 - space) / 2 + i * itemWidth + i * space; top = (width - itemWidth * 2 - space) / 2; } else { left = (i - 2) * itemWidth + (i - 2) * space; top = (width - itemWidth * 2 - space) / 2 + itemWidth + space; } } else if (itemCount == 6) { var topOffset = (width - 2 * itemWidth - space) / 2; left = (i % 3) * itemWidth + (i % 3) * space; top = topOffset + (i / 3).floor() * itemWidth + (i / 3).floor() * space; } else if (itemCount == 7) { if (i == 0) { left = (width - itemWidth) / 2; top = 0; } else { left = ((i - 1) % 3) * itemWidth + ((i - 1) % 3) * space; top = itemWidth + space + ((i - 1) / 3).floor() * itemWidth + ((i - 1) / 3).floor() * space; } } else if (itemCount == 8) { if (i == 0 || i == 1) { left = (width - itemWidth * 2 - space) / 2 + i * itemWidth + i * space; top = 0; } else { left = ((i - 2) % 3) * itemWidth + ((i - 2) % 3) * space; top = itemWidth + space + ((i - 2) / 3).floor() * itemWidth + ((i - 2) / 3).floor() * space; } } else if (itemCount == 9) { left = (i % 3) * itemWidth + (i % 3) * space; top = (i / 3).floor() * itemWidth + (i / 3).floor() * space; } avatars.add(Positioned( left: left, top: top, child: ClipRect( child: CachedNetworkImage( imageUrl: imageUrls[i], placeholder: (context, url) => const CircularProgressIndicator(), errorWidget: (context, url, error) => const Icon(Icons.error), width: itemWidth, height: itemWidth, fit: BoxFit.cover, ), ), )); } return avatars; } }
使用
import 'package:flutter/material.dart'; import 'package:flutter_xy/xydemo/image/avatar/avatar_grid.dart'; import '../../../widgets/xy_app_bar.dart'; class AvatarGridPage extends StatelessWidget { AvatarGridPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: XYAppBar( title: "微信群头像", onBack: () { Navigator.pop(context); }, ), body: Padding( padding: const EdgeInsets.all(8.0), child: GridView.builder( gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, crossAxisSpacing: 8.0, mainAxisSpacing: 8.0, ), itemCount: 9, itemBuilder: (context, index) { return LayoutBuilder(builder: (context, constraints) { return AvatarGroup( size: constraints.maxWidth, color: Colors.grey.withAlpha(50), borderColor: Colors.redAccent.withAlpha(80), borderWidth: 2, imageUrls: List.generate( index + 1, (i) => _imageUrls[i % _imageUrls.length]), ); }); }, ), ), ); } final List<String> _imageUrls = [ 'https://files.mdnice.com/user/34651/0d938792-603e-4945-a1d8-e53e605693d8.jpeg', 'https://files.mdnice.com/user/34651/a3b1fd72-ef80-4e31-8a33-2a57c4d115ce.jpeg', 'https://files.mdnice.com/user/34651/06de6046-bf3a-454c-a75b-6beeba78408b.jpeg', 'https://files.mdnice.com/user/34651/010ac7cb-9aa9-4a4d-93bb-14dc2cc0d994.jpeg', 'https://files.mdnice.com/user/34651/d88604e3-0dae-46f0-ab3f-d9e016516401.jpeg', 'https://files.mdnice.com/user/34651/91a53974-7bf7-47ba-a303-40d5fb61e31f.jpeg', 'https://files.mdnice.com/user/34651/6b7ca51c-65d0-4f35-b5c1-2c17ef494fd8.jpeg', 'https://files.mdnice.com/user/34651/0fbdd801-66d8-487c-bcdd-9afcaa611541.jpeg', 'https://files.mdnice.com/user/34651/0fbdd801-66d8-487c-bcdd-9afcaa611541.jpeg', ]; }
结束语
通过上述实现,我们成功地在Flutter中创建了一个仿微信群头像的控件。这个控件不仅可以动态调整布局,还支持网络图片的缓存和占位符显示。希望这篇文章对你在Flutter项目中实现类似功能有所帮助。如果你有任何问题或建议,欢迎访问我的GitHub项目:github.com/yixiaolunhui/flutter_xy与我交流。