Flutter GridView 如何实现蜂窝状六边形布局?
结论
HexagonClipper 只裁剪每个项目的绘制区域,不会改变项目在 GridView 中的位置。SliverGridDelegateWithFixedCrossAxisCount 会将项目排列在规则的网格槽位中,所以六边形默认仍会整齐对齐。
要让相邻行左右错开,可以根据项目所在的行,为奇数行增加水平偏移。下面的写法保留了现有的 GridView.builder 和六边形裁剪逻辑:
LayoutBuilder(
builder: (context, constraints) {
const columns = 3;
const spacing = 2.0;
const padding = 8.0;
final tileWidth =
(constraints.maxWidth - padding * 2 - spacing * (columns - 1)) /
columns;
return GridView.builder(
primary: true,
scrollDirection: Axis.vertical,
clipBehavior: Clip.none,
padding: const EdgeInsets.all(padding),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: columns,
mainAxisSpacing: spacing,
crossAxisSpacing: spacing,
childAspectRatio: 0.866,
),
itemCount: _exploreScreenController.posts.length,
itemBuilder: (context, index) {
final post = _exploreScreenController.posts[index];
if (post.gallery.isEmpty) {
return const SizedBox.shrink();
}
final galleryItem = post.gallery.first;
final isVideo = galleryItem.mediaType == 2;
final row = index ~/ columns;
return Transform.translate(
offset: Offset(row.isOdd ? tileWidth / 2 : 0, 0),
child: GestureDetector(
onTap: () {
Get.to(() => ReelScrollView(
posts: _exploreScreenController.posts,
initialIndex: index,
));
},
child: ClipPath(
clipper: HexagonClipper(),
child: Stack(
fit: StackFit.expand,
children: [
CachedNetworkImage(
imageUrl: galleryItem.thumbnail,
fit: BoxFit.cover,
placeholder: (context, url) => const Center(
child: CircularProgressIndicator(),
),
errorWidget: (context, url, error) =>
const Icon(Icons.error),
),
Container(color: Colors.black.withOpacity(0.1)),
if (isVideo)
const Positioned(
top: 6,
right: 6,
child: ThemeIconWidget(
ThemeIcon.goLive,
color: Colors.white,
size: 16,
),
),
],
),
),
),
);
},
);
},
)
row = index ~/ columns 用来计算项目所在的行。奇数行通过 Transform.translate 向右移动半个项目宽度。childAspectRatio 控制网格槽位的宽高比,可以按实际设计调整。clipBehavior: Clip.none 允许偏移后的项目绘制到网格槽位边界之外。如果边缘项目仍被裁掉,可以检查外层容器的裁剪设置,或为布局预留水平空间。
如果需要更紧密的蜂窝效果
这种做法只会错开各行,行与行之间仍保留 mainAxisSpacing,六边形不会自动嵌合。如果设计要求相邻行垂直重叠、边缘紧密贴合,可以用 Stack 配合自定义 CustomScrollView/Sliver 布局,根据每个六边形的宽高计算 left 和 top,也可以实现自定义 SliverGridDelegate。具体间距要根据六边形尺寸和目标效果确定。
现有代码的注意事项
示例中第一个 return GestureDetector(...) 后还有第二个 GestureDetector(...)。第一个 return 已结束当前 itemBuilder 调用,后面的代码不会执行。删除第二段,避免误以为两种布局都会参与渲染。
此外,当前 HexagonClipper.shouldReclip 固定返回 false,适用于裁剪形状不会随状态变化的情况。如果裁剪路径会动态改变,需要根据实际变化判断是否重绘。
备注:内容仅供参考。
