Implementing Mixed-Span Waterfall Layout in Flutter
To enable mixed‑span waterfall layouts in Flutter, we built a custom FlowView that mimics RecyclerView’s StaggeredGridLayoutManager, handling normal and full‑width cards by dynamically selecting the shortest or tallest column, recycling off‑screen items, and achieving performance comparable to native GridView, now deployed in Xianyu’s home and search pages.
Flow layouts are widely used for feed‑style pages in both web and native applications. In e‑commerce scenarios such as Taobao, JD, Meituan, and Xianyu, a multi‑column waterfall layout is needed where the container column count is fixed but each card may have a different height.
Native platforms provide CollectionView (iOS) and RecyclerView (Android) for such use cases, but Flutter originally only offered ListView and GridView without native waterfall support. Existing open‑source solutions (WaterFallFlow, FlutterStaggeredGridView) cannot satisfy Xianyu’s mixed‑span requirements, such as full‑width banner cards mixed with regular cards.
We therefore implemented a custom layout inspired by RecyclerView’s StaggeredGridLayoutManager , supporting both normal cards and full‑span (full‑width) cards.
Grid Layout Analysis – The size of a grid card is determined by crossAxisCount (column count) and childAspectRatio . The width is the screen width divided by the column count, and the height is derived from the aspect ratio. Example initialization:
GridView.count({
@required int crossAxisCount,
double childAspectRatio = 1.0,
})Position calculation uses the card index: crossAxisStart = (index % crossAxisCount) * crossAxisStride for the x‑coordinate and scrollOffset = (index ~/ crossAxisCount) * mainAxisStride for the y‑coordinate.
Waterfall Layout Size Calculation – Two card types are considered:
Normal cards: width follows crossAxisCount division; height is variable and can be supplied via an IndexedMainAxisExtentBuilder callback.
Full‑span cards: width always equals the screen width; height is also supplied via the same callback.
typedef double IndexedMainAxisExtentBuilder(int index);
typedef bool IndexedFullSpanBuilder(int index);The layout is created with:
FlowView.count({
@required int crossAxisCount,
IndexedFullSpanBuilder fullSpanBuilder,
IndexedMainAxisExtentBuilder mainAxisExtentBuilder,
})Waterfall Position Calculation – For normal cards, the next card is placed below the column with the smallest current offset (the “lowest” column). For full‑span cards, the card is placed after the column with the largest offset (the “highest” column). The algorithm updates a list of column offsets and selects the appropriate column based on the card type.
SliverFlowGeometry getGeometryForChildIndex(int index, List
startOffsets) {
bool isFullSpan = _getIsFullSpan(index);
double maxOffset = startOffsets.reduce(math.max);
double minOffset = startOffsets.reduce(math.min);
var scrollOffset = minOffset;
var crossAxisIndex = startOffsets.indexOf(minOffset);
int needCrossAxisCount = isFullSpan ? crossAxisCount : 1;
if (isFullSpan) {
scrollOffset = maxOffset;
crossAxisIndex = 0;
}
var crossAxisOffset = crossAxisIndex * crossAxisStride;
var mainAxisExtent = _getChildMainAxisExtent(index);
return SliverFlowGeometry(
scrollOffset: scrollOffset,
crossAxisOffset: crossAxisOffset,
mainAxisExtent: mainAxisExtent,
crossAxisExtent: crossAxisStride * needCrossAxisCount - crossAxisSpacing,
isFullSpan: isFullSpan,
crossAxisIndex: crossAxisIndex,
);
}Memory Recycling & Performance Optimization – Because a scrolling container may contain a huge number of cards, only the visible cards and a small cache are laid out. The layout is paginated by a configurable PageSize (typically a screen height). Each page records its column offsets, allowing the system to recycle cards that are completely off‑screen when scrolling up or down.
Performance tests comparing the custom FlowView with the native GridView show comparable frame rates after dynamically adjusting the page size based on average card height.
Effect & Deployment – The implementation is used in Xianyu’s home page and search results, supporting features such as scrollController, reverse scrolling, and both vertical and horizontal directions. The demo demonstrates full functionality and is currently being rolled out gradually.
Conclusion & Outlook – The mixed‑span waterfall layout is now integrated with PowerScrollView. Future work includes supporting variable column counts within a single sliver (similar to RecyclerView’s GridLayoutManager) and further reducing computational overhead for smoother scrolling.
Xianyu Technology
Official account of the Xianyu technology team
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.