rn_for_openharmony_steam资讯app实战-游戏分类实现
本文介绍了如何在React Native for OpenHarmony中实现Steam资讯App的游戏分类页面。主要内容包括: 分类页面设计思路:展示所有分类、分类统计、快速导航和分类搜索功能 分类数据结构:使用id、name、icon和color等字段定义游戏分类 状态管理:处理分类列表、搜索查询和过滤结果 核心组件实现:分类卡片采用emoji图标和动态边框颜色设计 交互功能:实现实时搜索、网
React Native for OpenHarmony 实战:Steam 资讯 App 游戏分类页面
案例开源地址:https://atomgit.com/nutpi/rn_openharmony_steam
前面几篇我们实现了首页、精选、特惠等页面,这些页面都是展示特定的游戏列表。这一篇来实现游戏分类页面,让用户可以按照不同的分类浏览游戏。分类页面是一个中间层,用户先选择分类,然后进入该分类的游戏列表。
为什么需要分类页面
在实际的游戏应用中,分类是一个很重要的功能。Steam 上有几百个游戏,如果没有分类,用户很难找到自己感兴趣的游戏。比如有人只想玩 RPG 游戏,有人只想玩射击游戏,分类功能就能满足这些需求。
分类页面的设计思路是:
- 展示所有分类 - 用网格或列表展示所有可用的游戏分类
- 分类统计 - 显示每个分类下有多少个游戏
- 快速导航 - 用户点击分类后快速进入该分类的游戏列表
- 分类搜索 - 如果分类很多,可以提供搜索功能快速找到想要的分类
Steam 的分类系统
Steam 的游戏分类主要有两种:
类型分类(Genre) - 比如 RPG、射击、策略、冒险等。这是按照游戏的玩法特性分类的。
标签分类(Tag) - 比如"单人"、“多人”、"支持手柄"等。这是按照游戏的特征标签分类的。
这篇文章主要讲类型分类的实现。标签分类会在下一篇讲。
分类数据的获取
Steam 没有直接提供获取所有分类的 API,所以我们需要手动维护一份分类列表。这是一个常见的做法,很多应用都这样做。
首先在 API 文件中定义分类列表:
export const GAME_CATEGORIES = [
{id: 'action', name: '动作', icon: '⚔️', color: '#e74c3c'},
{id: 'adventure', name: '冒险', icon: '🗺️', color: '#3498db'},
{id: 'rpg', name: 'RPG', icon: '🎮', color: '#9b59b6'},
{id: 'strategy', name: '策略', icon: '♟️', color: '#f39c12'},
{id: 'simulation', name: '模拟', icon: '🚗', color: '#1abc9c'},
{id: 'puzzle', name: '益智', icon: '🧩', color: '#e67e22'},
{id: 'casual', name: '休闲', icon: '🎯', color: '#2ecc71'},
{id: 'sports', name: '体育', icon: '⚽', color: '#34495e'},
{id: 'racing', name: '竞速', icon: '🏎️', color: '#c0392b'},
{id: 'indie', name: '独立', icon: '🎨', color: '#16a085'},
];
这里的设计思路:
id- 分类的唯一标识,用于后续查询该分类的游戏name- 分类的显示名称icon- 分类的 emoji 图标,用于视觉识别color- 分类的主题颜色,用于美化 UI
每个分类都有自己的颜色和图标,这样用户可以快速识别不同的分类。
分类页面的状态管理
分类页面相对简单,只需要管理几个基本的状态:
export const CategoriesScreen = () => {
const {navigate, setSelectedCategory} = useApp();
const [categories, setCategories] = useState(GAME_CATEGORIES);
const [searchQuery, setSearchQuery] = useState('');
const [filteredCategories, setFilteredCategories] = useState(GAME_CATEGORIES);
状态的作用:
categories- 所有分类列表,初始值是预定义的分类数组searchQuery- 搜索框的输入内容filteredCategories- 过滤后的分类列表,根据搜索词过滤
这样的设计让分类搜索功能很容易实现。
分类搜索的实现
当用户在搜索框输入内容时,需要实时过滤分类列表:
const handleSearchChange = (text: string) => {
setSearchQuery(text);
if (text.length === 0) {
setFilteredCategories(categories);
} else {
const filtered = categories.filter(cat =>
cat.name.toLowerCase().includes(text.toLowerCase())
);
setFilteredCategories(filtered);
}
};
这里的实现细节:
- 实时过滤 - 用户每输入一个字符,就重新过滤一次分类列表
- 大小写不敏感 - 用
toLowerCase()将搜索词和分类名都转换成小写,这样搜索"RPG"和"rpg"都能找到 - 空搜索处理 - 如果搜索框为空,显示所有分类
这样的实现虽然简单,但对于分类数量不多的情况(通常不超过 20 个)完全够用。
分类卡片组件
分类卡片是分类页面的核心组件,需要展示分类的图标、名称和游戏数量:
const CategoryCard = ({category, onPress}: {category: any, onPress: () => void}) => {
return (
<TouchableOpacity
style={[styles.categoryCard, {borderLeftColor: category.color}]}
onPress={onPress}
>
<Text style={styles.categoryIcon}>{category.icon}</Text>
<Text style={styles.categoryName}>{category.name}</Text>
<Text style={styles.categoryCount}>查看游戏 ›</Text>
</TouchableOpacity>
);
};
这里的设计:
- 动态边框颜色 - 用
borderLeftColor: category.color给每个卡片加上不同颜色的左边框,这样可以快速区分不同分类 - 图标展示 - 用 emoji 图标表示分类,简洁直观
- 点击处理 - 点击卡片时调用
onPress回调函数
这样的卡片设计既美观又实用。
分类列表的渲染
分类列表用网格布局展示,这样可以充分利用屏幕空间:
<View style={styles.categoriesGrid}>
{filteredCategories.map((category) => (
<View key={category.id} style={styles.categoryItem}>
<CategoryCard
category={category}
onPress={() => {
setSelectedCategory(category.id);
navigate('categoryGames');
}}
/>
</View>
))}
</View>
这里的实现:
- 网格布局 - 用
flexWrap: 'wrap'实现网格布局,每行显示 2 个分类 - 点击导航 - 点击分类卡片时,设置选中的分类 ID,然后导航到分类游戏列表页面
- key 属性 - 用分类 ID 作为 key,确保列表项的唯一性
搜索框的实现
分类页面顶部需要一个搜索框,让用户可以快速找到想要的分类:
<View style={styles.searchContainer}>
<TextInput
style={styles.searchInput}
placeholder="搜索分类..."
placeholderTextColor="#8f98a0"
value={searchQuery}
onChangeText={handleSearchChange}
/>
{searchQuery.length > 0 && (
<TouchableOpacity
style={styles.clearBtn}
onPress={() => {
setSearchQuery('');
setFilteredCategories(categories);
}}
>
<Text style={styles.clearBtnText}>✕</Text>
</TouchableOpacity>
)}
</View>
这里的设计:
- 搜索框 - 用
TextInput组件实现,placeholder 提示用户可以搜索分类 - 清空按钮 - 只有当搜索框有内容时才显示,点击可以快速清空搜索词
- 实时搜索 - 用户输入时实时过滤分类列表
这样的搜索框设计很常见,用户已经很熟悉了。
空状态的处理
当搜索结果为空时,需要显示提示信息:
{filteredCategories.length === 0 ? (
<View style={styles.emptyContainer}>
<Text style={styles.emptyText}>未找到相关分类</Text>
<Text style={styles.emptySubtext}>试试其他搜索词</Text>
</View>
) : (
<View style={styles.categoriesGrid}>
{/* 分类列表 */}
</View>
)}
这里的设计:
- 空状态提示 - 显示友好的提示信息,告诉用户没有找到匹配的分类
- 建议 - 提示用户"试试其他搜索词",引导用户改变搜索策略
这样的空状态处理让应用看起来更专业。
完整页面的实现
现在把所有部分组合在一起,看看完整的分类页面:
import React, {useState} from 'react';
import {View, Text, TextInput, TouchableOpacity, ScrollView, StyleSheet} from 'react-native';
import {Header} from '../components/Header';
import {TabBar} from '../components/TabBar';
import {useApp} from '../store/AppContext';
import {GAME_CATEGORIES} from '../api/steam';
export const CategoriesScreen = () => {
const {navigate, setSelectedCategory} = useApp();
const [searchQuery, setSearchQuery] = useState('');
const [filteredCategories, setFilteredCategories] = useState(GAME_CATEGORIES);
const handleSearchChange = (text: string) => {
setSearchQuery(text);
if (text.length === 0) {
setFilteredCategories(GAME_CATEGORIES);
} else {
const filtered = GAME_CATEGORIES.filter(cat =>
cat.name.toLowerCase().includes(text.toLowerCase())
);
setFilteredCategories(filtered);
}
};
const CategoryCard = ({category}: {category: any}) => (
<TouchableOpacity
style={[styles.categoryCard, {borderLeftColor: category.color}]}
onPress={() => {
setSelectedCategory(category.id);
navigate('categoryGames');
}}
>
<Text style={styles.categoryIcon}>{category.icon}</Text>
<View style={styles.categoryInfo}>
<Text style={styles.categoryName}>{category.name}</Text>
<Text style={styles.categoryAction}>查看游戏 ›</Text>
</View>
</TouchableOpacity>
);
return (
<View style={styles.container}>
<Header title="游戏分类" showBack={false} />
<View style={styles.searchContainer}>
<TextInput
style={styles.searchInput}
placeholder="搜索分类..."
placeholderTextColor="#8f98a0"
value={searchQuery}
onChangeText={handleSearchChange}
/>
{searchQuery.length > 0 && (
<TouchableOpacity
style={styles.clearBtn}
onPress={() => {
setSearchQuery('');
setFilteredCategories(GAME_CATEGORIES);
}}
>
<Text style={styles.clearBtnText}>✕</Text>
</TouchableOpacity>
)}
</View>
<ScrollView style={styles.content}>
{filteredCategories.length === 0 ? (
<View style={styles.emptyContainer}>
<Text style={styles.emptyText}>未找到相关分类</Text>
<Text style={styles.emptySubtext}>试试其他搜索词</Text>
</View>
) : (
<View style={styles.categoriesGrid}>
{filteredCategories.map((category) => (
<View key={category.id} style={styles.categoryItem}>
<CategoryCard category={category} />
</View>
))}
</View>
)}
</ScrollView>
<TabBar />
</View>
);
};
const styles = StyleSheet.create({
container: {flex: 1, backgroundColor: '#171a21'},
searchContainer: {
flexDirection: 'row',
alignItems: 'center',
padding: 12,
backgroundColor: '#1b2838',
borderBottomWidth: 1,
borderBottomColor: '#2a475e',
},
searchInput: {
flex: 1,
height: 40,
paddingHorizontal: 12,
borderRadius: 8,
backgroundColor: '#2a475e',
color: '#acdbf5',
fontSize: 14,
},
clearBtn: {
width: 40,
height: 40,
justifyContent: 'center',
alignItems: 'center',
marginLeft: 8,
},
clearBtnText: {fontSize: 20, color: '#8f98a0'},
content: {flex: 1, padding: 12},
categoriesGrid: {flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'space-between'},
categoryItem: {width: '48%', marginBottom: 12},
categoryCard: {
flexDirection: 'row',
alignItems: 'center',
padding: 12,
backgroundColor: '#1b2838',
borderRadius: 8,
borderLeftWidth: 4,
},
categoryIcon: {fontSize: 28, marginRight: 12},
categoryInfo: {flex: 1},
categoryName: {fontSize: 14, fontWeight: '600', color: '#acdbf5', marginBottom: 4},
categoryAction: {fontSize: 12, color: '#66c0f4'},
emptyContainer: {flex: 1, justifyContent: 'center', alignItems: 'center', paddingVertical: 60},
emptyText: {fontSize: 16, color: '#8f98a0', marginBottom: 8},
emptySubtext: {fontSize: 12, color: '#8f98a0'},
});
页面的整体结构:
- Header - 显示"游戏分类"标题
- 搜索框 - 让用户可以搜索分类
- 分类网格 - 用网格布局展示所有分类
- 空状态 - 当搜索结果为空时显示提示
- TabBar - 底部导航栏
分类页面的设计考虑
为什么用网格布局而不是列表布局? 网格布局可以充分利用屏幕宽度,每行显示 2 个分类,这样用户可以一眼看到更多分类。列表布局虽然也可以,但会浪费屏幕空间。
为什么要加搜索功能? 虽然现在只有 10 个分类,但如果后续要加更多分类(比如 20 个、30 个),搜索功能就很有用了。而且搜索功能的实现成本很低,不如提前加上。
为什么要显示分类的颜色和图标? 这样可以让分类更容易被识别。用户可以通过颜色和图标快速找到想要的分类,而不需要仔细阅读文字。
与其他页面的联动
分类页面本身只是一个中间层,真正的游戏列表在下一个页面(分类游戏列表页面)。所以分类页面需要和其他页面配合:
导航流程 - 用户在首页点击"分类"入口 → 进入分类页面 → 选择一个分类 → 进入该分类的游戏列表页面
数据传递 - 分类页面需要把选中的分类 ID 传递给下一个页面,这样下一个页面才能知道要显示哪个分类的游戏
这种分层设计让应用的结构更清晰,每个页面的职责也更明确。
实际开发中的经验
在实际开发中,我发现分类页面虽然看起来简单,但有几个细节需要注意:
分类数据的维护 - 分类列表是手动维护的,所以需要定期更新。如果 Steam 新增了分类,我们需要手动添加到列表中。这是一个小的维护成本,但值得。
分类的排序 - 分类的顺序很重要。我们应该把最受欢迎的分类放在前面,这样用户可以快速找到。比如"动作"和"RPG"通常比"体育"和"竞速"更受欢迎。
分类的翻译 - 如果应用要支持多语言,分类名称也需要翻译。这可以通过国际化库(比如 i18n)来实现。
小结
分类页面是一个相对简单但很重要的页面。它为用户提供了一个快速浏览不同类型游戏的方式。虽然功能不复杂,但设计得好的分类页面可以大大提升用户体验。
关键是要:
- 清晰的分类 - 分类要清晰明确,用户能快速理解
- 快速的导航 - 用户点击分类后能快速进入游戏列表
- 搜索功能 - 提供搜索功能,方便用户快速找到想要的分类
- 视觉识别 - 用颜色和图标帮助用户快速识别分类
下一篇我们来实现分类游戏列表页面,展示某个分类下的所有游戏。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)