Flutter实现仿微信群头像功能
需求
在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与我交流。