1.双列瀑布流布局
安装依赖:npm install @react-native-seoul/masonry-list
1.1MasonryList 简介
MasonryList 是一个 React Native 组件库(如 @react-native-seoul/masonry-list),用于实现类似瀑布流(Masonry Layout)的列表展示。它的特点是:
- 支持多列(
numColumns),每列高度自适应,内容像“砖块”一样错落排列。 - 适合展示图片、卡片等高度不一的内容,常见于小红书、Pinterest 等应用。
- 性能优良,支持大数据量虚拟滚动。
1.2在本项目中的运用
在(HomeScreen.tsx)中,MasonryList 主要用于首页内容区域的瀑布流展示:
<MasonryList
data={posts}
keyExtractor={(item: Post) => item.id.toString()}
numColumns={2}
contentContainerStyle={styles.content}
showsVerticalScrollIndicator={true}
renderItem={({ item }: any) => {
// 渲染每个帖子卡片,支持图片和文字
}}
/>
具体作用:
data={posts}:传入帖子数据数组。numColumns={2}:设置为2列瀑布流。renderItem:自定义每个卡片的渲染方式,支持图片、标题、摘要等。contentContainerStyle:自定义内容区样式,保证左右留白和顶部间距。- 在 Web 端和移动端都能自适应高度,实现内容错落有致的排列和流畅滚动。
2.发布页面
2.1向首页传递帖子数据
- 首页跳转发布页时传递 onPublish 回调。
// 跳转发布页时传递回调
const handleGoPostEditor = () => {
navigation.navigate('PostEditor', {
onPublish: async (post: { title: string; text: string; images: string[] }) => {
const newPost = {
id: Date.now(),
title: post.title || '新发布',
summary: post.text || '',
images: post.images || []
};
await addPost(newPost);
setPosts(prev => [newPost, ...prev]);
}
});
};
- 发布页发布时调用 onPublish 并 goBack,数据回传并插入首页列表。
// 发布帖子
const handlePublish = () => {
if (!title.trim() && !text.trim() && images.length === 0) {
alert('请输入标题、内容或添加图片');
return;
}
// 回调传递数据
if (route.params && typeof route.params.onPublish === 'function') {
route.params.onPublish({ title, text, images });
}
navigation.goBack();
};
2.2发帖支持本地相册和拍照图片上传
安装依赖:npm install expo-image-picker
ImagePicker 是 Expo 提供的一个图片/视频选择与拍照库(expo-image-picker),用于在 React Native 应用中实现从相册选择图片或调用摄像头拍照的功能。
主要功能
- 打开系统相册选择图片或视频
- 调用摄像头拍照或录像
- 支持多选、压缩、裁剪等参数
- 兼容 iOS、Android、Web
在本项目中的使用
在PostEditorScreen.tsx 文件中,ImagePicker 主要用于发帖时选择图片或拍照:
import * as ImagePicker from 'expo-image-picker';
// 选择图片
const pickImage = async () => {
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsMultipleSelection: true,
quality: 0.8,
});
if (!result.canceled && result.assets) {
setImages([...images, ...result.assets.map(a => a.uri)]);
}
};
// 拍照
const takePhoto = async () => {
const result = await ImagePicker.launchCameraAsync({
quality: 0.8,
});
if (!result.canceled && result.assets) {
setImages([...images, ...result.assets.map(a => a.uri)]);
}
};
具体说明
launchImageLibraryAsync:打开系统相册,允许多选图片,设置图片压缩质量。launchCameraAsync:调用摄像头拍照,设置图片压缩质量。- 选择或拍照后,将图片的
uri存入images状态,供发帖时展示和上传。
3.踩坑记录
3.1web端底部导航栏消失
在测试的过程中发现,底部导航栏在android手机上能正常显示,web端直接消失。查看dom发现由于定位的问题,在web端该导航栏跑到内容区的下面去了,而内容区又是一个很长的滚动列表,导致导航栏不可见。
解决办法:
tabBar: {
flexDirection: 'row',
backgroundColor: COLORS.primary,
height: 56,
alignItems: 'center',
justifyContent: 'space-around',
// 兼容 web 端底部固定
position: Platform.OS === 'web' ? 'fixed' : 'absolute',
left: 0,
right: 0,
bottom: 0,
borderTopLeftRadius: 12,
borderTopRightRadius: 12,
shadowColor: COLORS.cardShadow,
shadowOffset: { width: 0, height: -2 },
shadowOpacity: 0.08,
shadowRadius: 8,
elevation: 8,
zIndex: 100,
},
- Platform.OS === 'web' ? 'fixed' : 'absolute' 这行代码让底部导航栏在 Web 端用 CSS 的 position: fixed,在原生端用 position: absolute。
fixed能让导航栏在 Web 页面滚动时始终固定在底部,不会被内容遮挡或随内容滚动。- 其他属性(如 left: 0, right: 0, bottom: 0)确保导航栏横跨底部。
3.2内容区域在web端无法滚动
MasonryList 在 web 端无法滚动,主要原因是底部导航栏用 position: fixed,而内容区的父容器高度被“撑满”,导致内容溢出时不会出现滚动条。
解决办法:
- 让内容区高度减去底部导航栏高度在 web 端,内容区需要用 height: calc(100vh - 56px),这样内容区才会出现滚动条。React Native 的 StyleSheet 不支持
calc,但可以用内联样式+平台判断实现。 - MasonryList 不要设置 flex: 1,而是让外层 View 控制高度
这样 MasonryList 会自动填满内容区并可滚动。
import { Platform } from 'react-native';
// ...existing code...
{
/* 内容区域:真正瀑布流 */
}
<View
style={
Platform.OS === 'web'
? {
height: 'calc(100vh - 56px)', // 56px为底部tabBar高度
overflowY: 'auto',
}
: { flex: 1, minHeight: 0 }
}
>
<MasonryList
data={posts}
keyExtractor={(item: Post) => item.id.toString()}
numColumns={2}
contentContainerStyle={styles.content}
showsVerticalScrollIndicator={true}
renderItem={({ item }: any) => {
if (item.images && item.images.length > 0) {
return (
<View style={[styles.card, { width: CARD_WIDTH, alignSelf: 'flex-start' }]}>
<Text style={styles.cardTitle}>{item.title}</Text>
<Text style={styles.cardSummary}>{item.summary}</Text>
<View style={{ flexDirection: 'row', flexWrap: 'wrap', marginTop: 8 }}>
{item.images.map((uri: string, idx: number) => (
<Image
key={idx}
source={{ uri }}
style={{ width: 80, height: 80, borderRadius: 8, marginRight: 8, marginBottom: 8 }}
/>
))}
</View>
</View>
);
}
return (
<View style={[styles.card, { width: CARD_WIDTH, alignSelf: 'flex-start' }]}>
<Text style={styles.cardTitle}>{item.title}</Text>
<Text style={styles.cardSummary}>{item.summary}</Text>
</View>
);
}}
/>
</View>
是不是感觉跨端开发要考虑的问题还挺多的呢
项目地址如下,欢迎关注:github.com/ggbond11/pi…