Flutter实现类Pinterest固定宽度自适应网格布局的技术问题
实现固定宽度、自适应列数的瀑布流布局
针对你需要固定容器宽度、高度各异,且列数随屏幕尺寸自适应的需求,flutter_staggered_grid_view的Masonry方案确实不适用(它会均分列宽),可以通过自定义布局来实现,核心思路是动态计算列数并将容器分配到高度最小的列中:
核心实现步骤
- 动态计算列数:根据屏幕宽度、容器固定宽度和间距,计算当前能容纳的列数
- 分配容器到列:维护每列的当前总高度,每次将新容器添加到高度最小的列
- 适配屏幕变化:通过
LayoutBuilder监听父容器尺寸变化,自动重新计算列数并重建布局
代码示例
import 'package:flutter/material.dart'; class FixedWidthMasonry extends StatefulWidget { final double itemWidth; final double spacing; final List<Widget> items; const FixedWidthMasonry({ super.key, required this.itemWidth, required this.spacing, required this.items, }); @override State<FixedWidthMasonry> createState() => _FixedWidthMasonryState(); } class _FixedWidthMasonryState extends State<FixedWidthMasonry> { late List<double> columnHeights; late int columnCount; @override void didChangeDependencies() { super.didChangeDependencies(); _calculateColumns(); } void _calculateColumns() { final screenWidth = MediaQuery.of(context).size.width; // 计算列数,确保间距正确分配 columnCount = ((screenWidth + widget.spacing) / (widget.itemWidth + widget.spacing)).floor(); columnCount = columnCount < 1 ? 1 : columnCount; columnHeights = List.filled(columnCount, 0.0); } @override Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { _calculateColumns(); // 初始化各列的容器列表 final List<List<Widget>> columnItems = List.generate(columnCount, (_) => []); for (final item in widget.items) { // 找到当前高度最小的列 final minIndex = columnHeights.indexOf(columnHeights.reduce((a, b) => a < b ? a : b)); columnItems[minIndex].add( Padding( padding: EdgeInsets.only( bottom: widget.spacing, right: minIndex != columnCount - 1 ? widget.spacing : 0, ), child: _MeasuredItem( onHeightChange: (height) { // 更新对应列的高度 setState(() { columnHeights[minIndex] = height + widget.spacing; }); }, child: item, ), ), ); } return SingleChildScrollView( child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: columnItems.map((items) { return SizedBox( width: widget.itemWidth, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: items, ), ); }).toList(), ), ); }, ); } } // 用于测量容器实际高度的组件 class _MeasuredItem extends StatefulWidget { final Widget child; final Function(double height) onHeightChange; const _MeasuredItem({ required this.child, required this.onHeightChange, }); @override State<_MeasuredItem> createState() => _MeasuredItemState(); } class _MeasuredItemState extends State<_MeasuredItem> { final _key = GlobalKey(); @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { _measureHeight(); }); } void _measureHeight() { final renderBox = _key.currentContext?.findRenderObject() as RenderBox?; if (renderBox != null) { widget.onHeightChange(renderBox.size.height); } } @override Widget build(BuildContext context) { return SizedBox( key: _key, child: widget.child, ); } }
使用方式
FixedWidthMasonry( itemWidth: 150, // 固定容器宽度 spacing: 10, // 容器间距 items: [ Container(height: 120, color: Colors.red), Container(height: 180, color: Colors.blue), Container(height: 90, color: Colors.green), // 更多高度各异的容器... ], )
关键说明
- 用
_MeasuredItem组件自动获取每个容器的实际渲染高度,确保列高度计算准确,适配动态内容(比如文本、图片) LayoutBuilder会在屏幕尺寸变化时触发重建,自动调整列数,完美适配不同屏幕- 完全保持容器宽度固定,不会像Masonry网格那样拉伸或压缩容器宽度
内容的提问来源于stack exchange,提问作者Milad
相关产品推荐
相关产品推荐

