布局介绍
在很多内容/电商平台中,比如说小红书、Pinterest等,都会用到砌体布局。
砌体布局(Masonry Layout),又称「瀑布流布局」。这种布局的特征就是在垂直方向上按列排列,而每列的高度可以不同,给用户一种“错落有致”的视觉效果。
解决方案
那么如何在React Native中实现砌体布局呢?
实现的方法有很多,包括可以直接使用第三方库,如 react-native-masonry-list 。具体表现如何因为没有尝试过所以暂时不知道,想要使用第三方库进行快速集成的读者可以自行尝试。
在这篇文章中,我主要分享如何通过react native原生的方法进行Masonry布局的实现。
Flexbox方法实现
基础布局
根据观察砌体布局,我们可以发现布局的每一列宽度是固定的。因此,我们就可以给 ScrollView 的contentContainerStyle
以下属性:
flexDirection: "row"
:使 ScrollView 中的两个 View 并排排列gap: 8
:给两个View之间留出一些空隙
具体的组件排列方式如下图所示:
- 别忘了给 View
flexGrow: 1
的样式
图片填充View
接着,我们要做的事情就是用图片来填充这两个View。
假设我们有一个Images数组,包含了图片的uri数据。我们可以写一个splitImages2Columns
函数,来把这一个图片数组分成两个数组leftColumnImgs
和rightColumnImgs
,分别用于填充左边的View和右边的View:
const splitImages2Columns = (imgs) => {
const leftColumnImgs = []; // 用于填充左边的View
const rightColumnImgs = []; // 用于填充右边的View
// 平均地将原数组的数据分配到两个数组中
imgs.forEach((img, index) => {
if (index % 2 === 0) {
leftColumnImgs.push(img);
} else {
rightColumnImgs.push(img);
}
});
return { leftColumnImgs, rightColumnImgs };
};
调用函数,获取左右两个数组:
const { leftColumnImgs, rightColumnImgs } = splitImages2Columns(Images);
得到数组之后,就能在左右两个View中分别使用Map映射到展示图片的组件中。
<View className="flex-1 gap-2">
{leftColumnImgs.map((item, index) => (
<PhotoCard key={`photo-${index}-l`} {...item} />
))}
</View>
<View className="flex-1 gap-2">
{rightColumnImgs.map((item, index) => (
<PhotoCard key={`photo-${index}-r`} {...item} />
))}
</View>
图片组件
如何在图片组件中,恰当地设置图片的尺寸呢?我们要的效果是能够使图片根据其原始的比例来展现,即:宽度固定,但是高度能够动态调整。
在我的项目中,由于要在图片上显示一些其它的数据,因此采用的是ImageBackground。并且由于图片资源是静态的,所以需要通过onLoad
函数以及nativeEvent
参数动态获取并设置图片的高度。
这部分如果有和我的需求不一样的,可以跳过;但是我还是记录一下自己的解决方法:
const PhotoCard = ({ imgsrc, stars, views, likes }: PhotoCardProps) => {
const [imgHeight, setImgHeight] = useState(0);
return (
<ImageBackground
onLoad={({ nativeEvent }) => {
setImgHeight(nativeEvent.source.height);
}}
source={imgsrc}
imageStyle={{ width: '100%', height: imgHeight, borderRadius: 16 }}
className="justify-between"
style={{ height: imgHeight, width: '100%' }}>
<Views views={views} />
<View className="flex-row justify-between">
<Stars stars={stars} />
<Likes likes={likes} />
</View>
</ImageBackground>
);
};
在这里的代码中,我通过使用ImageBackground的onLoad函数,来设置图片组件的高度。这里的高度是使用一个state来实现的。
别忘了,要把图片的宽度设置成100%,这样就能够在水平方向上填满两个50%的View容器。
实现效果
最终实现的效果是这样的:
为什么不用FlatList?
一开始,我是想用高性能的FlatList,通过设置 numColumns={2}
来实现2列的瀑布流布局。
但是使用的过程中发现,FlatList暂时不支持瀑布流布局,于是采用了ScrollView来代替。
React Native的官方文档中对于FlatList的numColumns
属性的解释:
如果想要通过FlatList来实现多列的高性能列表,还是能够行得通的;只不过每个元素的尺寸都是相同的,因此不能用于实现砌体布局。
本文使用的ScrollView中嵌入两个View的方法,在数据量极大的情况下应该不是很适用。不像高性能的FlatList,ScrollView会一次性把所有的子组件都渲染出来。如果对于性能有要求,需要在此基础上进行优化。
参考
实现方法的灵感来源于这个视频,但是自己在其基础上实现了保持图片原有比例,动态设置图片高度的功能。