RN for OpenHarmony AnimeHub项目实战:即将上映页面开发
本文介绍了一个动漫信息应用的"即将上映"页面设计与实现。该页面采用网格布局展示未开播的动漫作品,便于用户提前规划追番计划。通过分析动漫行业特点,文章阐述了网格布局相比列表布局的优势:能展示更多作品、突出视觉冲击力。技术实现上,使用FlatList的numColumns属性创建两列网格,配合自定义AnimeCard组件展示封面和标题。页面还处理了即将上映数据的特殊性(如无评分信息

案例开源地址:https://atomgit.com/nutpi/Rn_openharmony_AnimeHub
每个季度开始前,动漫迷们都会关注一个问题:下季度有什么新番?即将上映页面就是用来展示那些已经公布但还没开播的动漫作品。
这个页面解决什么问题
动漫行业有个特点:新番信息会提前几个月甚至半年公布。制作公司会先放出预告片、主视觉图、声优阵容等信息来预热。对于动漫迷来说,提前了解这些信息可以:
- 规划追番计划,避免同一天太多番要追
- 提前了解感兴趣的作品,开播时不会错过
- 参与社区讨论,和其他粉丝交流期待
即将上映页面把这些"预告中"的动漫集中展示,方便用户浏览和收藏。
页面设计选择:网格 vs 列表
这个页面选择了网格布局,和正在热播页面的列表布局不同。为什么?
即将上映的动漫有个特点:还没有评分。因为还没开播,没人看过,自然没有评分数据。既然没有评分,列表布局的优势(显示排名和评分)就不存在了。
相反,网格布局可以:
- 一屏显示更多作品
- 封面图更大,视觉冲击力更强
- 用户可以通过封面快速判断画风是否喜欢
所以这个页面用两列网格,每个卡片显示封面和标题。
代码实现
先看 API 调用。获取即将上映的动漫用的是专门的接口:
import { getUpcomingAnime } from '../../api/jikan';
const res = await getUpcomingAnime(pageNum);
这个接口返回的是按预计开播时间排序的动漫列表,最快开播的排在前面。
状态管理和其他分页列表一样,五件套:
const [animeList, setAnimeList] = useState<Anime[]>([]);
const [loading, setLoading] = useState(true);
const [loadingMore, setLoadingMore] = useState(false);
const [page, setPage] = useState(1);
const [hasMore, setHasMore] = useState(true);
数据加载函数,标准模板:
const loadData = async (pageNum: number, append = false) => {
try {
if (pageNum === 1) setLoading(true);
else setLoadingMore(true);
const res = await getUpcomingAnime(pageNum);
const newData = res.data || [];
if (append) {
setAnimeList(prev => [...prev, ...newData]);
} else {
setAnimeList(newData);
}
setHasMore(res.pagination?.has_next_page || false);
} catch (error) {
console.error('Load error:', error);
} finally {
setLoading(false);
setLoadingMore(false);
}
};
渲染函数是这个页面的特色。注意外面包了一层 View 用于控制宽度:
const renderItem = ({ item }: { item: Anime }) => (
<View style={styles.cardWrapper}>
<AnimeCard
anime={item}
onPress={() => navigation.navigate('AnimeDetail', { animeId: item.mal_id })}
/>
</View>
);
为什么需要 cardWrapper?因为 FlatList 的 numColumns 只是把列表分成多列,但不会自动处理每个卡片的宽度和间距。cardWrapper 的作用是:
cardWrapper: {
flex: 1,
maxWidth: '50%',
padding: Spacing.xs,
},
flex: 1让卡片填充可用空间maxWidth: '50%'确保每行最多两个padding创造卡片之间的间距
FlatList 配置,关键是 numColumns:
<FlatList
data={animeList}
renderItem={renderItem}
keyExtractor={item => item.mal_id.toString()}
numColumns={2}
contentContainerStyle={styles.list}
showsVerticalScrollIndicator={false}
onEndReached={handleLoadMore}
onEndReachedThreshold={0.5}
ListFooterComponent={loadingMore ? <Loading text="加载更多..." /> : null}
ListEmptyComponent={<EmptyState icon="rocket" title="暂无数据" />}
/>
numColumns={2} 告诉 FlatList 把数据分成两列显示。FlatList 会自动处理布局,我们只需要确保每个 item 的宽度正确。
空状态用了火箭图标(rocket),暗示"即将发射"的意思,和"即将上映"的主题呼应。
AnimeCard vs AnimeListItem
项目中有两个展示动漫的组件:
AnimeCard 用于网格布局:
- 显示大封面图
- 标题在图片下方
- 适合浏览、发现场景
AnimeListItem 用于列表布局:
- 显示小缩略图
- 标题和详情在右侧
- 可以显示排名
- 适合排行榜、搜索结果场景
选择哪个组件取决于页面的目的。即将上映页面的目的是"发现新作品",用户主要通过封面来判断是否感兴趣,所以用 AnimeCard。
样式代码
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: Colors.background,
},
list: {
padding: Spacing.sm,
},
cardWrapper: {
flex: 1,
maxWidth: '50%',
padding: Spacing.xs,
},
});
样式很简洁。container 是标准的全屏容器,list 设置列表的内边距,cardWrapper 控制每个卡片的布局。
注意 list 用的是 Spacing.sm(小间距),cardWrapper 用的是 Spacing.xs(超小间距)。这样整体边距是 sm,卡片之间的间距是 xs * 2 = sm,视觉上比较协调。
即将上映数据的特点
即将上映的动漫数据有一些特殊之处:
没有评分:score 字段通常是 null 或 0。AnimeCard 组件需要处理这种情况,不显示评分或显示"暂无评分"。
没有集数:episodes 字段可能是 null,因为还没确定总集数。有些作品会显示预计集数,有些则完全未知。
有预计开播时间:aired.from 字段包含预计开播日期。可以用这个信息显示"X月开播"或倒计时。
信息可能不完整:简介、角色、制作人员等信息可能还没公布,详情页可能比较空。
和季度动漫页的区别
即将上映页面和季度动漫页面有什么区别?
季度动漫页:
- 显示特定季度(如 2024 年春季)的动漫
- 包括已开播和未开播的
- 用户需要先选择年份和季度
即将上映页:
- 只显示还没开播的动漫
- 不限于某个季度,可能跨越多个季度
- 直接显示,不需要选择
简单说,季度动漫页是"按时间分类",即将上映页是"按状态筛选"。
可以添加的功能
当前实现比较基础,可以考虑添加:
开播倒计时:显示距离开播还有多少天。这个信息对用户很有价值,可以帮助他们规划。
按开播时间排序:当前是按热度排序,也可以提供按开播时间排序的选项。
筛选功能:按类型(TV、电影、OVA)、按季度(2024春、2024夏)筛选。
提醒功能:用户可以设置开播提醒,到时候推送通知。
这些功能会让页面更实用,但也会增加复杂度。在 MVP 阶段,先实现基础功能,后续根据用户反馈迭代。
完整代码
把上面的片段组合起来:
import React, { useEffect, useState, useCallback } from 'react';
import { View, FlatList, StyleSheet } from 'react-native';
import { Colors, Spacing } from '../../theme';
import { Anime } from '../../types';
import { getUpcomingAnime } from '../../api/jikan';
import { AnimeCard } from '../../components/anime';
import { Header, Loading, EmptyState } from '../../components/common';
export const UpcomingAnimeScreen = ({ navigation }: any) => {
const [animeList, setAnimeList] = useState<Anime[]>([]);
const [loading, setLoading] = useState(true);
const [loadingMore, setLoadingMore] = useState(false);
const [page, setPage] = useState(1);
const [hasMore, setHasMore] = useState(true);
// loadData, useEffect, handleLoadMore, renderItem...
// 省略,和前面讲的一样
if (loading) {
return (
<View style={styles.container}>
<Header title="即将上映" showBack onBack={() => navigation.goBack()} />
<Loading fullScreen text="加载中..." />
</View>
);
}
return (
<View style={styles.container}>
<Header title="即将上映" showBack onBack={() => navigation.goBack()} />
<FlatList
data={animeList}
renderItem={renderItem}
keyExtractor={item => item.mal_id.toString()}
numColumns={2}
contentContainerStyle={styles.list}
showsVerticalScrollIndicator={false}
onEndReached={handleLoadMore}
onEndReachedThreshold={0.5}
ListFooterComponent={loadingMore ? <Loading text="加载更多..." /> : null}
ListEmptyComponent={<EmptyState icon="rocket" title="暂无数据" />}
/>
</View>
);
};
小结
即将上映页面展示还没开播的动漫,帮助用户提前了解和规划追番。页面使用网格布局,因为即将上映的动漫没有评分数据,网格布局可以更好地展示封面。
实现上使用 FlatList 的 numColumns 属性创建两列网格,cardWrapper 控制每个卡片的宽度和间距。AnimeCard 组件负责渲染单个卡片,封装了封面图和标题的显示逻辑。
即将上映的数据有其特殊性:没有评分、集数可能未知、信息可能不完整。组件需要优雅地处理这些情况,不能因为数据缺失就崩溃或显示异常。
下一篇讲人气排行页面,展示按人气(而非评分)排序的动漫。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)