RN for OpenHarmony AnimeHub项目实战:全部季度页面开发

案例开源地址:https://atomgit.com/nutpi/Rn_openharmony_AnimeHub
全部季度页展示所有年份和季度,用户可以按时间维度浏览动漫。这篇来讲全部季度页的实现,重点是嵌套数据结构的渲染和 Emoji 图标的使用。
功能设计
全部季度页需要实现以下功能:
- 年份分组 - 按年份分组显示,每年有四个季度
- 季度卡片 - 用 Emoji 图标表示不同季节
- 点击跳转 - 点击季度跳转到该季度的动漫列表
- 时间排序 - 最新的年份在最上面
这个页面的布局比较特殊,不是简单的列表或网格,而是按年份分组的嵌套结构。每个年份下面有四个季度卡片横向排列,用户可以快速定位到想看的时间段。这种设计让时间维度的浏览变得直观高效。
季节名称映射
定义季节的中文名称和 Emoji 图标:
const SEASON_NAMES: Record<string, string> = {
winter: '冬季',
spring: '春季',
summer: '夏季',
fall: '秋季',
};
名称映射说明:
winter→ 冬季(1-3月)spring→ 春季(4-6月)summer→ 夏季(7-9月)fall→ 秋季(10-12月)
用
Record<string, string>类型定义映射对象。这是 TypeScript 的内置工具类型,表示一个对象,key 是 string 类型,value 也是 string 类型。比起普通的{ [key: string]: string },Record 更简洁易读。
为什么需要这个映射?因为 API 返回的是英文季节名(winter、spring 等),但我们想在界面上显示中文。用映射对象可以方便地进行转换,而且集中管理,修改时只需要改一处。
const SEASON_ICONS: Record<string, string> = {
winter: '❄️',
spring: '🌸',
summer: '☀️',
fall: '🍂',
};
图标映射说明:
winter→ ❄️(雪花,代表冬天的寒冷)spring→ 🌸(樱花,日本春天的象征)summer→ ☀️(太阳,代表夏天的炎热)fall→ 🍂(落叶,代表秋天的萧瑟)
Emoji 图标是一个非常聪明的设计选择,有以下优点:
- 零资源成本 - 不需要额外的图标文件,减少包体积
- 跨平台一致 - iOS、Android、鸿蒙都支持 Emoji
- 视觉直观 - 用户一眼就能理解每个季节
- 增加趣味性 - 比纯文字更生动有趣
樱花 🌸 的选择特别贴切。樱花是日本文化的象征,每年春天的樱花季是日本的重要时节。用樱花代表春季,和动漫的日本文化背景非常搭配。
状态定义
const [seasons, setSeasons] = useState<Season[]>([]);
const [loading, setLoading] = useState(true);
状态说明:
seasons- 季度列表数据,是一个数组,每项包含年份和该年的季度loading- 加载状态,控制 Loading 组件的显示
这个页面的状态非常简单,只有数据和加载状态。不需要分页状态,因为季度数据是按年份组织的,总数据量不大(从 1960 年代到现在,大约 60 年的数据),可以一次性加载完。
简单的状态设计意味着页面逻辑简单,bug 也会更少。能用简单方案解决的问题,就不要用复杂方案。
数据类型
季度数据的结构是嵌套的:
interface Season {
year: number;
seasons: string[];
}
字段详细说明:
year- 年份,数字类型,如 2024、2023、2022seasons- 该年的季度数组,字符串数组,如['winter', 'spring', 'summer', 'fall']
这个数据结构是"分组"模式的典型例子。外层按年份分组,内层是该年的季度列表。这种结构非常适合我们的 UI 设计:每个年份是一个区块,区块内显示四个季度卡片。
有些年份可能不是四个季度都有数据。比如很早的年份(1960 年代),可能只有部分季度有动漫播出。API 只返回有数据的季度,所以 seasons 数组的长度可能是 1-4。
数据加载
useEffect(() => {
loadSeasons();
}, []);
const loadSeasons = async () => {
try {
const data = await getSeasonsList();
setSeasons(data.data || []);
} catch (error) {
console.error('Load seasons error:', error);
} finally {
setLoading(false);
}
};
加载逻辑详解:
useEffect的依赖数组为空,表示只在组件挂载时执行一次getSeasonsList是封装好的 API 函数,返回所有年份和季度的列表- 用
data.data || []处理 API 返回空数据的情况 - 错误时打印日志,不会导致页面崩溃
finally确保无论成功失败都会把 loading 设为 false
季度数据是相对固定的,只有当新的季度开始时才会更新(每三个月一次)。这个 API 的响应非常适合做本地缓存,可以减少网络请求,提升用户体验。
为什么不需要分页?因为数据量可控。假设从 1960 年到 2024 年,共 64 年,每年一条数据,总共只有 64 条。这个数据量 FlatList 完全可以轻松处理。
年份和季度渲染
这是这个页面最核心的部分,实现嵌套渲染:
const renderItem = ({ item }: { item: Season }) => (
<View style={styles.yearSection}>
<Text style={styles.yearTitle}>{item.year}</Text>
<View style={styles.seasonsRow}>
{item.seasons.map((season) => (
<TouchableOpacity
key={season}
style={styles.seasonCard}
onPress={() => navigation.navigate('SeasonAnime', {
year: item.year,
season
})}
>
<Text style={styles.seasonIcon}>{SEASON_ICONS[season]}</Text>
<Text style={styles.seasonName}>{SEASON_NAMES[season]}</Text>
</TouchableOpacity>
))}
</View>
</View>
);
渲染逻辑详解:
- 外层
yearSection是年份区块,包含年份标题和季度卡片行 yearTitle显示年份数字,如"2024"seasonsRow是季度卡片的容器,横向排列- 内层用
map遍历该年的季度数组,生成季度卡片 - 每个卡片显示 Emoji 图标和中文季节名
- 点击卡片跳转到季度动漫页面
这是一个典型的嵌套渲染模式:FlatList 负责渲染年份列表(外层循环),每个年份内部用 map 渲染季度卡片(内层循环)。这种模式适合"分组 + 子项"的数据结构。
key={season}使用季节名作为 key。因为同一年内季节名是唯一的(不会有两个 winter),所以可以作为 key。
navigation.navigate('SeasonAnime', { year: item.year, season })传递两个参数:
year- 年份数字,如 2024season- 季节英文名,如 ‘spring’下一个页面会用这两个参数请求该季度的动漫列表。
年份区块样式
yearSection: {
marginBottom: Spacing.xl,
},
yearTitle: {
fontSize: FontSize.xxl,
fontWeight: '700',
color: Colors.text,
marginBottom: Spacing.md,
},
样式详解:
- 每个年份区块底部有较大间距(xl),让不同年份之间有明显分隔
- 年份标题用超大字号(xxl,可能是 24-28px),加粗
- 标题和下面的季度卡片之间有中等间距(md)
年份标题用超大字号是有意为之的设计。当用户快速滚动页面时,大字号的年份数字非常醒目,可以帮助用户快速定位到想要的年份。这是一种"扫描友好"的设计。
fontWeight: '700'是加粗。在 CSS/React Native 中,字重从 100(最细)到 900(最粗),700 相当于 bold。
季度卡片样式
seasonsRow: {
flexDirection: 'row',
gap: Spacing.md,
},
seasonCard: {
flex: 1,
backgroundColor: Colors.backgroundCard,
padding: Spacing.lg,
borderRadius: BorderRadius.lg,
alignItems: 'center',
},
样式详解:
flexDirection: 'row'让四个季度卡片横向排列gap: Spacing.md设置卡片之间的间距(gap 是 Flexbox 的属性,比 margin 更方便)flex: 1让每个卡片平分父容器的宽度- 卡片有背景色、内边距、圆角
alignItems: 'center'让卡片内容(图标和文字)水平居中
flex: 1是实现等宽布局的关键。当四个子元素都设置flex: 1时,它们会平分父容器的可用空间。这比手动计算百分比(如 width: ‘25%’)更灵活,因为它会自动处理间距。
假设父容器宽度是 343px(iPhone SE 屏幕宽度 375 - 左右边距 32),四个卡片加三个间距。如果间距是 12px,那么:
- 可用宽度 = 343 - 12 * 3 = 307px
- 每个卡片宽度 = 307 / 4 ≈ 77px
seasonIcon: {
fontSize: 28,
marginBottom: Spacing.sm,
},
seasonName: {
fontSize: FontSize.sm,
color: Colors.textSecondary,
},
卡片内容样式:
- Emoji 图标用 28 号字体,足够大,视觉上醒目
- 图标和文字之间有小间距
- 季节名称用小字号,次要颜色
Emoji 的 fontSize 和普通文字一样设置。28px 的 Emoji 在移动端显示效果很好,既能看清细节,又不会占用太多空间。
季节名称用次要颜色(textSecondary,通常是灰色),是因为 Emoji 图标已经足够表达季节含义,文字只是辅助说明,不需要太突出。
页面结构
return (
<View style={styles.container}>
<Header
title="季度列表"
showBack
onBack={() => navigation.goBack()}
/>
Header 配置:
- 标题显示"季度列表",简洁明了
- 显示返回按钮,点击返回上一页
条件渲染
{loading ? (
<Loading fullScreen />
) : (
<FlatList
data={seasons}
renderItem={renderItem}
keyExtractor={(item) => item.year.toString()}
contentContainerStyle={styles.list}
showsVerticalScrollIndicator={false}
/>
)}
</View>
);
两种状态的处理:
- 加载中显示全屏 Loading
- 加载完成显示年份列表
这里没有空状态处理,因为季度数据不可能为空。动漫从 1960 年代就开始有了,API 一定会返回数据。如果 API 出错,会在 catch 中处理,页面会显示空白列表。
FlatList 配置详解
<FlatList
data={seasons}
renderItem={renderItem}
keyExtractor={(item) => item.year.toString()}
contentContainerStyle={styles.list}
showsVerticalScrollIndicator={false}
/>
配置说明:
data={seasons}- 数据源是年份数组renderItem={renderItem}- 渲染函数keyExtractor- 用年份作为 key,年份是唯一的contentContainerStyle- 内容容器样式showsVerticalScrollIndicator={false}- 隐藏滚动条
注意这里没有
numColumns,因为我们不是要做网格布局。每个列表项(年份区块)内部自己处理了四列布局(用 flexDirection: ‘row’)。FlatList 只负责纵向滚动年份列表。
也没有分页相关的配置(onEndReached 等),因为数据量不大,一次性加载即可。
list: {
padding: Spacing.lg,
},
列表样式:
- 四周有内边距,内容不贴边
与新番页的关系
全部季度页和新番页(SeasonalScreen)功能有重叠,但定位不同:
- 新番页 - 显示当前季度的动漫,可以切换年份和季度,适合浏览最新内容
- 全部季度页 - 显示所有年份和季度的入口,适合查找特定时间段的动漫
举个例子:
- 用户想看"这个季度有什么新番" → 用新番页
- 用户想看"2019年春季有什么好看的动漫" → 用全部季度页
两个页面互补,满足不同的使用场景。新番页更适合"追新",全部季度页更适合"考古"。
季度动漫的时间规律
日本动漫的播出遵循固定的季度规律:
- 冬季(Winter) - 1月开播,1-3月播出
- 春季(Spring) - 4月开播,4-6月播出
- 夏季(Summer) - 7月开播,7-9月播出
- 秋季(Fall) - 10月开播,10-12月播出
每个季度大约有 30-50 部新番开播。一部标准的季番(12-13集)正好在一个季度内播完。有些作品是两季连播(24-26集),会跨越两个季度。
了解这个规律对用户很有帮助。比如现在是 2024 年 5 月,那么当前季度是"2024年春季",用户可以在这个季度找到正在播出的新番。
小结
全部季度页的核心是嵌套数据结构的渲染和 Emoji 图标的使用。FlatList 渲染年份列表,每个年份内部用 map 渲染四个季度卡片,形成清晰的层次结构。
Emoji 图标是一个巧妙的设计选择,零资源成本,跨平台兼容,视觉直观,还能增加页面的趣味性。季节名称用映射对象从英文转换为中文,代码集中管理,易于维护。
年份标题用超大字号显示,方便用户快速扫描定位。四个季度卡片用 flex: 1 平分宽度,布局简洁灵活,自动适应不同屏幕宽度。
下一篇会讲季度动漫页面,展示某个季度的所有动漫。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)