React Native鸿蒙跨平台实现方式通过修改 A miibo 的 `owned` 属性,实现了拥有状态的切换
本文分析了基于React Native实现的游戏收藏应用架构设计。应用采用组件化结构,包含主应用组件、列表渲染、系列筛选、搜索排序等功能模块,实现清晰的关注点分离。状态管理使用useState钩子处理收藏数据、当前系列、搜索查询等核心状态,结合TypeScript确保类型安全。应用实现了多重筛选(系列、搜索关键字、拥有状态)和排序(名称、日期、价格)功能,通过颜色区分不同稀有度。布局采用顶部标题、
在移动应用开发中,收藏管理类应用是一种常见的应用类型,需要考虑数据展示、筛选、排序等多个方面。本文将深入分析一个功能完备的 React Native 游戏收藏应用实现,探讨其架构设计、状态管理、数据处理以及跨端兼容性策略。
组件化
该实现采用了清晰的组件化架构,主要包含以下部分:
- 主应用组件 (
GameLibraryApp) - 负责整体布局和状态管理 - Amiibo 列表渲染 - 负责渲染 A miibo 卡片列表
- 系列筛选 - 提供按系列筛选 A miibo 的功能
- 搜索功能 - 提供搜索 A miibo 的功能
- 排序功能 - 提供按名称、日期、价格排序的功能
- 筛选功能 - 提供按拥有状态筛选的功能
这种架构设计使得代码结构清晰,易于维护和扩展。主应用组件负责管理全局状态和业务逻辑,而各个功能部分负责具体的 UI 渲染,实现了关注点分离。
状态管理
GameLibraryApp 组件使用 useState 钩子管理多个关键状态:
const [collection, setCollection] = useState<Amiibo[]>(amiibos);
const [activeSeries, setActiveSeries] = useState<string>('1');
const [searchQuery, setSearchQuery] = useState<string>('');
const [sortBy, setSortBy] = useState<'name' | 'date' | 'price'>('name');
const [filterOwned, setFilterOwned] = useState<boolean>(false);
这种状态管理方式简洁高效,通过状态更新触发组件重新渲染,实现了 A miibo 的筛选、搜索、排序等功能。使用 TypeScript 类型定义确保了数据结构的类型安全,提高了代码的可靠性。
数据筛选
应用实现了复杂的数据筛选和处理逻辑:
// 获取当前系列的Amiibo
const getCurrentSeriesAmiibos = () => {
const seriesName = amiiboSeries.find(series => series.id === activeSeries)?.name;
return collection.filter(amiibo =>
amiibo.series === seriesName &&
(searchQuery === '' || amiibo.name.toLowerCase().includes(searchQuery.toLowerCase())) &&
(!filterOwned || amiibo.owned)
);
};
// 按排序方式整理数据
const sortedAmiibos = getCurrentSeriesAmiibos().sort((a, b) => {
switch (sortBy) {
case 'name':
return a.name.localeCompare(b.name);
case 'date':
return new Date(a.releaseDate).getTime() - new Date(b.releaseDate).getTime();
case 'price':
return a.price - b.price;
default:
return 0;
}
});
这种实现方式支持多重筛选条件:
- 系列筛选 - 只显示当前选中系列的 A miibo
- 搜索筛选 - 根据搜索关键字筛选 A miibo
- 拥有状态筛选 - 只显示已拥有的 A miibo
- 排序 - 按名称、日期或价格排序
这种灵活的筛选和排序功能为用户提供了便捷的 A miibo 管理体验。
状态
应用实现了 A miibo 拥有状态的管理:
// 切换拥有状态
const toggleOwned = (id: string) => {
setCollection(collection.map(amiibo =>
amiibo.id === id ? { ...amiibo, owned: !amiibo.owned } : amiibo
));
};
这种实现方式通过修改 A miibo 的 owned 属性,实现了拥有状态的切换。当用户点击 A miibo 卡片时,可以轻松切换其拥有状态。
应用实现了 A miibo 稀有度的视觉化:
// 获取稀有度颜色
const getRarityColor = (rarity: string) => {
switch (rarity) {
case 'common': return '#9ca3af';
case 'rare': return '#3b82f6';
case 'epic': return '#8b5cf6';
case 'legendary': return '#f59e0b';
default: return '#9ca3af';
}
};
这种实现方式根据 A miibo 的稀有度返回不同的颜色,使得用户可以通过颜色直观地区分不同稀有度的 A miibo。
类型定义
该实现使用 TypeScript 定义了两个核心数据类型:
- Amiibo - A miibo 类型,包含 A miibo 的完整信息,如 ID、名称、角色、系列、发布日期、价格、拥有状态、稀有度和图片
- Series - 系列类型,包含系列的 ID、名称和图标
这些类型定义使得数据结构更加清晰,提高了代码的可读性和可维护性,同时也提供了类型安全保障。
应用数据按照功能模块进行组织:
- amiiboSeries - 系列列表
- amiibos - A miibo 列表
- collection - 当前管理的 A miibo 集合
- activeSeries - 当前选中的系列
- searchQuery - 搜索关键字
- sortBy - 排序方式
- filterOwned - 是否筛选已拥有的 A miibo
这种数据组织方式使得数据管理更加清晰,易于扩展和维护。
布局结构
应用界面采用了清晰的层次结构:
- 顶部 - 显示应用标题和搜索栏
- 系列筛选 - 显示系列列表,允许用户选择系列
- 功能栏 - 显示排序、筛选等功能按钮
- Amiibo 列表 - 显示当前筛选条件下的 A miibo 列表
这种布局结构符合用户的使用习惯,用户可以快速了解应用内容并进行操作。
交互设计
应用实现了直观的交互设计:
- 系列筛选 - 点击系列切换显示的 A miibo
- 搜索 - 输入关键字搜索 A miibo
- 排序 - 点击排序按钮切换排序方式
- 筛选 - 点击筛选按钮切换是否只显示已拥有的 A miibo
- 拥有状态 - 点击 A miibo 卡片切换拥有状态
这些交互设计元素共同构成了良好的用户体验,使得 A miibo 管理操作简单直观。
当前实现使用 FlatList 渲染 A miibo 列表,这是一个好的做法,但可以进一步优化:
// 优化前
<FlatList
data={sortedAmiibos}
renderItem={({ item }) => (
<AmiiboCard amiibo={item} />
)}
keyExtractor={item => item.id}
/>
// 优化后
<FlatList
data={sortedAmiibos}
renderItem={({ item }) => (
<AmiiboCard amiibo={item} />
)}
keyExtractor={item => item.id}
initialNumToRender={5} // 初始渲染的项目数
maxToRenderPerBatch={10} // 每批渲染的最大项目数
windowSize={10} // 可见区域外渲染的项目数
removeClippedSubviews={true} // 移除不可见的子视图
updateCellsBatchingPeriod={100} // 单元格更新的批处理周期
getItemLayout={(data, index) => ({
length: ITEM_HEIGHT, // 预计算的项目高度
offset: ITEM_HEIGHT * index,
index
})}
/>
2. 状态管理
当前实现使用多个 useState 钩子管理状态,可以考虑使用 useReducer 或状态管理库来管理复杂状态:
// 优化前
const [collection, setCollection] = useState<Amiibo[]>(amiibos);
const [activeSeries, setActiveSeries] = useState<string>('1');
const [searchQuery, setSearchQuery] = useState<string>('');
const [sortBy, setSortBy] = useState<'name' | 'date' | 'price'>('name');
const [filterOwned, setFilterOwned] = useState<boolean>(false);
// 优化后
type AppState = {
collection: Amiibo[];
activeSeries: string;
searchQuery: string;
sortBy: 'name' | 'date' | 'price';
filterOwned: boolean;
};
type AppAction =
| { type: 'SET_COLLECTION'; payload: Amiibo[] }
| { type: 'SET_ACTIVE_SERIES'; payload: string }
| { type: 'SET_SEARCH_QUERY'; payload: string }
| { type: 'SET_SORT_BY'; payload: 'name' | 'date' | 'price' }
| { type: 'SET_FILTER_OWNED'; payload: boolean }
| { type: 'TOGGLE_OWNED'; payload: string };
const initialState: AppState = {
collection: amiibos,
activeSeries: '1',
searchQuery: '',
sortBy: 'name',
filterOwned: false
};
const appReducer = (state: AppState, action: AppAction): AppState => {
switch (action.type) {
case 'SET_COLLECTION':
return { ...state, collection: action.payload };
case 'SET_ACTIVE_SERIES':
return { ...state, activeSeries: action.payload };
case 'SET_SEARCH_QUERY':
return { ...state, searchQuery: action.payload };
case 'SET_SORT_BY':
return { ...state, sortBy: action.payload };
case 'SET_FILTER_OWNED':
return { ...state, filterOwned: action.payload };
case 'TOGGLE_OWNED':
return {
...state,
collection: state.collection.map(amiibo =>
amiibo.id === action.payload ? { ...amiibo, owned: !amiibo.owned } : amiibo
)
};
default:
return state;
}
};
const [state, dispatch] = useReducer(appReducer, initialState);
3. 数据持久化
当前实现使用内存状态存储数据,可以考虑集成本地存储实现数据持久化:
import AsyncStorage from '@react-native-async-storage/async-storage';
const STORAGE_KEYS = {
COLLECTION: '@amiibo_collection',
SETTINGS: '@app_settings',
};
const GameLibraryApp = () => {
const [collection, setCollection] = useState<Amiibo[]>(amiibos);
const [settings, setSettings] = useState({
activeSeries: '1',
sortBy: 'name' as 'name' | 'date' | 'price',
filterOwned: false,
});
// 加载数据
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
try {
const storedCollection = await AsyncStorage.getItem(STORAGE_KEYS.COLLECTION);
const storedSettings = await AsyncStorage.getItem(STORAGE_KEYS.SETTINGS);
if (storedCollection) {
setCollection(JSON.parse(storedCollection));
}
if (storedSettings) {
setSettings(JSON.parse(storedSettings));
}
} catch (error) {
console.error('加载数据失败:', error);
}
};
// 保存数据
const saveData = async () => {
try {
await AsyncStorage.setItem(STORAGE_KEYS.COLLECTION, JSON.stringify(collection));
await AsyncStorage.setItem(STORAGE_KEYS.SETTINGS, JSON.stringify(settings));
} catch (error) {
console.error('保存数据失败:', error);
}
};
// 当数据变化时保存
useEffect(() => {
saveData();
}, [collection, settings]);
// 其他代码...
};
4. 导航系统
可以集成 React Navigation 实现详情页面的导航:
import { createStackNavigator } from '@react-navigation/stack';
const Stack = createStackNavigator();
const App = () => {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Home"
component={GameLibraryApp}
options={{ title: '游戏收藏库' }}
/>
<Stack.Screen
name="AmiiboDetail"
component={AmiiboDetailScreen}
options={({ route }) => ({ title: route.params?.amiiboName || 'Amiibo详情' })}
/>
</Stack.Navigator>
</NavigationContainer>
);
};
const AmiiboDetailScreen = ({ route }: { route: any }) => {
const { amiiboId } = route.params;
// 获取 Amiibo 详情并渲染
return (
<View style={styles.detailContainer}>
{/* Amiibo 详情内容 */}
</View>
);
};
本文深入分析了一个功能完备的 React Native 游戏收藏应用实现,从架构设计、状态管理、数据处理到跨端兼容性都进行了详细探讨。该实现不仅功能完整,而且代码结构清晰,具有良好的可扩展性和可维护性。
理解这个功能完整的 React Native Amiibo 收藏库应用的技术实现逻辑,同时掌握其向鸿蒙(HarmonyOS)平台跨端适配的核心思路与具体实现方案。该应用涵盖了数据筛选、排序、状态管理、UI组件封装等移动端开发的核心场景,是典型的分类列表型应用架构,非常适合作为跨端开发的学习案例。
该 Amiibo 收藏库应用采用状态驱动+数据过滤排序的现代化 React Native 架构,核心数据模型设计体现了收藏类应用的典型特征:
// Amiibo核心数据模型 - 覆盖收藏类应用核心属性
type Amiibo = {
id: string; // 唯一标识
name: string; // 名称
character: string; // 角色名
series: string; // 所属系列
releaseDate: string; // 发行日期
price: number; // 价格
owned: boolean; // 拥有状态
rarity: 'common' | 'rare' | 'epic' | 'legendary'; // 稀有度
image: string; // 图片地址
};
// 系列分类模型 - 用于分类筛选
type Series = {
id: string;
name: string;
icon: string; // 系列图标(emoji形式)
};
数据模型设计亮点:
- 类型完整性:涵盖收藏类应用所需的核心属性,包括基础信息、状态标识、分类标签;
- 枚举型稀有度:使用联合类型限定稀有度取值范围,保证数据规范性;
- 布尔状态标识:
owned字段清晰标识收藏状态,便于状态切换; - 分类体系:独立的
Series模型支持多维度筛选,符合内容分类的最佳实践。
(1)数据筛选
应用的核心价值在于多维度的数据筛选和排序能力,这是收藏类应用的核心功能:
// 核心状态定义 - 控制筛选和排序行为
const [collection, setCollection] = useState<Amiibo[]>(amiibos);
const [activeSeries, setActiveSeries] = useState<string>('1');
const [searchQuery, setSearchQuery] = useState<string>('');
const [sortBy, setSortBy] = useState<'name' | 'date' | 'price'>('name');
const [filterOwned, setFilterOwned] = useState<boolean>(false);
// 多条件数据筛选逻辑
const getCurrentSeriesAmiibos = () => {
const seriesName = amiiboSeries.find(series => series.id === activeSeries)?.name;
return collection.filter(amiibo =>
// 系列筛选
amiibo.series === seriesName &&
// 搜索筛选(不区分大小写)
(searchQuery === '' || amiibo.name.toLowerCase().includes(searchQuery.toLowerCase())) &&
// 拥有状态筛选
(!filterOwned || amiibo.owned)
);
};
// 多维度排序逻辑
const sortedAmiibos = getCurrentSeriesAmiibos().sort((a, b) => {
switch (sortBy) {
case 'name':
return a.name.localeCompare(b.name); // 按名称字母排序
case 'date':
return new Date(a.releaseDate).getTime() - new Date(b.releaseDate).getTime(); // 按日期排序
case 'price':
return a.price - b.price; // 按价格排序
default:
return 0;
}
});
筛选排序设计要点:
- 组合筛选:支持系列、关键词、拥有状态的多条件组合筛选;
- 类型安全:排序字段使用联合类型限定,避免非法值;
- 用户体验优化:搜索筛选支持大小写不敏感匹配;
- 性能考量:筛选和排序逻辑分离,便于维护和扩展;
- 纯函数设计:筛选和排序函数无副作用,便于测试和复用。
(2)状态管理
// 切换拥有状态(不可变更新)
const toggleOwned = (id: string) => {
setCollection(collection.map(amiibo =>
amiibo.id === id ? { ...amiibo, owned: !amiibo.owned } : amiibo
));
};
// 稀有度颜色映射(视觉化展示)
const getRarityColor = (rarity: string) => {
switch (rarity) {
case 'common': return '#9ca3af'; // 普通 - 灰色
case 'rare': return '#3b82f6'; // 稀有 - 蓝色
case 'epic': return '#8b5cf6'; // 史诗 - 紫色
case 'legendary': return '#f59e0b'; // 传说 - 金色
default: return '#9ca3af';
}
};
状态管理设计亮点:
- 不可变更新:使用数组
map和对象展开语法实现状态的不可变更新,符合 React 最佳实践; - 视觉映射:稀有度与颜色的映射函数,实现数据到视觉的统一转换;
- 单一职责:每个函数只负责单一功能,便于维护和扩展。
(1)分类标签栏实现
实现了移动端经典的横向滚动分类标签栏,支持选中状态切换:
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
style={styles.seriesContainer}
>
<View style={styles.seriesList}>
{amiiboSeries.map(series => (
<TouchableOpacity
key={series.id}
style={[
styles.seriesItem,
activeSeries === series.id && styles.activeSeries
]}
onPress={() => setActiveSeries(series.id)}
>
<Text style={[
styles.seriesIcon,
activeSeries === series.id && styles.activeSeriesIcon
]}>
{series.icon}
</Text>
<Text style={[
styles.seriesText,
activeSeries === series.id && styles.activeSeriesText
]}>
{series.name}
</Text>
</TouchableOpacity>
))}
</View>
</ScrollView>
(2)筛选与排序
<View style={styles.filtersContainer}>
{/* 拥有状态筛选按钮 */}
<TouchableOpacity
style={[styles.filterButton, filterOwned && styles.activeFilter]}
onPress={() => setFilterOwned(!filterOwned)}
>
<Text style={styles.filterText}>只看拥有</Text>
</TouchableOpacity>
{/* 排序选项组 */}
<View style={styles.sortContainer}>
<Text style={styles.sortLabel}>排序:</Text>
<TouchableOpacity
style={[styles.sortButton, sortBy === 'name' && styles.activeSort]}
onPress={() => setSortBy('name')}
>
<Text style={styles.sortText}>名称</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.sortButton, sortBy === 'date' && styles.activeSort]}
onPress={() => setSortBy('date')}
>
<Text style={styles.sortText}>日期</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.sortButton, sortBy === 'price' && styles.activeSort]}
onPress={() => setSortBy('price')}
>
<Text style={styles.sortText}>价格</Text>
</TouchableOpacity>
</View>
</View>
(3)数据统计卡片
<View style={styles.statsContainer}>
<View style={styles.statItem}>
<Text style={styles.statNumber}>{sortedAmiibos.length}</Text>
<Text style={styles.statLabel}>当前系列</Text>
</View>
<View style={styles.statItem}>
<Text style={styles.statNumber}>{collection.filter(a => a.owned).length}</Text>
<Text style={styles.statLabel}>已拥有</Text>
</View>
<View style={styles.statItem}>
<Text style={styles.statNumber}>{collection.filter(a => !a.owned).length}</Text>
<Text style={styles.statLabel}>未拥有</Text>
</View>
<View style={styles.statItem}>
<Text style={styles.statNumber}>10</Text>
<Text style={styles.statLabel}>总计</Text>
</View>
</View>
(4)Amiibo列表项
const renderAmiiboItem = ({ item }: { item: Amiibo }) => (
<View style={styles.amiiboItem}>
{/* 图片占位区 */}
<View style={styles.imageContainer}>
<View style={styles.imagePlaceholder}>
<Text style={styles.imageText}> toy </Text>
</View>
</View>
{/* 信息区域 */}
<View style={styles.infoContainer}>
{/* 名称和拥有状态 */}
<View style={styles.headerContainer}>
<Text style={styles.name}>{item.name}</Text>
<TouchableOpacity
style={styles.ownedButton}
onPress={() => toggleOwned(item.id)}
>
<Text style={styles.ownedText}>{item.owned ? '✅' : '⬜'}</Text>
</TouchableOpacity>
</View>
{/* 基础信息 */}
<Text style={styles.character}>{item.character}</Text>
<Text style={styles.series}>{item.series}</Text>
{/* 详情信息 */}
<View style={styles.detailsContainer}>
<Text style={styles.releaseDate}>发行: {item.releaseDate}</Text>
<Text style={styles.price}>¥{item.price.toFixed(2)}</Text>
</View>
{/* 稀有度标签(动态颜色) */}
<View style={styles.rarityContainer}>
<Text style={[styles.rarity, { color: getRarityColor(item.rarity) }]}>
{item.rarity === 'common' ? '普通' : item.rarity === 'rare' ? '稀有' : item.rarity === 'epic' ? '史诗' : '传说'}
</Text>
</View>
</View>
</View>
);
将该 React Native Amiibo 收藏库应用适配到鸿蒙平台,核心是将 React 的状态管理、数据筛选排序、列表渲染等核心能力映射到鸿蒙 ArkTS + ArkUI 生态,以下是完整的适配方案。
1. 核心技术栈映射
| React Native 核心能力 | 鸿蒙 ArkTS 对应实现 | 适配关键说明 |
|---|---|---|
useState 状态管理 |
@State/@Link |
状态声明语法替换 |
FlatList 列表渲染 |
List + LazyForEach |
高性能列表渲染 |
ScrollView (横向) |
Scroll + scrollDirection |
横向滚动容器替换 |
TouchableOpacity |
Button + stateEffect(false) |
可点击组件替换 |
StyleSheet.create |
@Styles/@Extend + 内联样式 |
样式体系重构 |
Alert.alert |
AlertDialog 组件 |
弹窗交互替换 |
| 条件样式(数组语法) | 三元运算符 + 内联样式 | 样式条件判断适配 |
Dimensions 尺寸获取 |
@ohos.window API |
屏幕尺寸获取适配 |
2. 鸿蒙端
// index.ets - 鸿蒙端Amiibo收藏库完整实现
import router from '@ohos.router';
import window from '@ohos.window';
// Amiibo类型定义(与RN端保持一致)
type Amiibo = {
id: string;
name: string;
character: string;
series: string;
releaseDate: string;
price: number;
owned: boolean;
rarity: 'common' | 'rare' | 'epic' | 'legendary';
image: string;
};
// 系列类型定义(与RN端保持一致)
type Series = {
id: string;
name: string;
icon: string;
};
// 模拟数据(与RN端保持一致)
const amiiboSeries: Series[] = [
{ id: '1', name: '超级马里奥', icon: '🍄' },
{ id: '2', name: '塞尔达传说', icon: '⚔️' },
{ id: '3', name: '宝可梦', icon: '⚡' },
{ id: '4', name: '动物森友会', icon: '🌳' },
{ id: '5', name: '火焰纹章', icon: '🗡️' },
{ id: '6', name: '大金刚', icon: '🍌' },
{ id: '7', name: '星之卡比', icon: '💫' },
{ id: '8', name: '任天堂明星大乱斗', icon: '🎮' },
];
const amiibos: Amiibo[] = [
{
id: '1',
name: '马里奥',
character: '马里奥',
series: '超级马里奥',
releaseDate: '2014-11-21',
price: 19.99,
owned: true,
rarity: 'common',
image: ''
},
{
id: '2',
name: '路易吉',
character: '路易吉',
series: '超级马里奥',
releaseDate: '2014-11-21',
price: 19.99,
owned: false,
rarity: 'common',
image: ''
},
{
id: '3',
name: '桃花公主',
character: '桃花公主',
series: '超级马里奥',
releaseDate: '2014-11-21',
price: 24.99,
owned: true,
rarity: 'rare',
image: ''
},
{
id: '4',
name: '林克',
character: '林克',
series: '塞尔达传说',
releaseDate: '2014-11-21',
price: 29.99,
owned: false,
rarity: 'epic',
image: ''
},
{
id: '5',
name: '塞尔达',
character: '塞尔达',
series: '塞尔达传说',
releaseDate: '2014-11-21',
price: 29.99,
owned: true,
rarity: 'epic',
image: ''
},
{
id: '6',
name: '皮卡丘',
character: '皮卡丘',
series: '宝可梦',
releaseDate: '2014-11-21',
price: 19.99,
owned: false,
rarity: 'rare',
image: ''
},
{
id: '7',
name: '杰尼龟',
character: '杰尼龟',
series: '宝可梦',
releaseDate: '2014-11-21',
price: 19.99,
owned: true,
rarity: 'common',
image: ''
},
{
id: '8',
name: '提米',
character: '提米',
series: '动物森友会',
releaseDate: '2014-11-21',
price: 14.99,
owned: true,
rarity: 'common',
image: ''
},
{
id: '9',
name: '罗宾',
character: '罗宾',
series: '火焰纹章',
releaseDate: '2014-11-21',
price: 24.99,
owned: false,
rarity: 'rare',
image: ''
},
{
id: '10',
name: '大金刚',
character: '大金刚',
series: '大金刚',
releaseDate: '2014-11-21',
price: 24.99,
owned: true,
rarity: 'epic',
image: ''
},
];
@Entry
@Component
struct GameLibraryApp {
// 核心状态管理(对应RN的useState)
@State collection: Amiibo[] = amiibos;
@State activeSeries: string = '1';
@State searchQuery: string = '';
@State sortBy: 'name' | 'date' | 'price' = 'name';
@State filterOwned: boolean = false;
// 屏幕尺寸(对应RN的Dimensions)
@State windowWidth: number = 0;
@State windowHeight: number = 0;
// 通用样式封装
@Styles
cardShadow() {
.shadow({ radius: 2, color: '#000', opacity: 0.1, offsetX: 0, offsetY: 1 });
}
// 组件生命周期(获取屏幕尺寸)
async aboutToAppear() {
const windowClass = await window.getLastWindow(getContext());
const windowSize = await windowClass.getWindowProperties();
this.windowWidth = windowSize.windowRect.width;
this.windowHeight = windowSize.windowRect.height;
}
// 获取当前系列的Amiibo(逻辑与RN完全一致)
private getCurrentSeriesAmiibos(): Amiibo[] {
const seriesName = amiiboSeries.find(series => series.id === this.activeSeries)?.name;
return this.collection.filter(amiibo =>
amiibo.series === seriesName &&
(this.searchQuery === '' || amiibo.name.toLowerCase().includes(this.searchQuery.toLowerCase())) &&
(!this.filterOwned || amiibo.owned)
);
}
// 按排序方式整理数据(逻辑与RN完全一致)
private get sortedAmiibos(): Amiibo[] {
return this.getCurrentSeriesAmiibos().sort((a, b) => {
switch (this.sortBy) {
case 'name':
return a.name.localeCompare(b.name);
case 'date':
return new Date(a.releaseDate).getTime() - new Date(b.releaseDate).getTime();
case 'price':
return a.price - b.price;
default:
return 0;
}
});
}
// 切换拥有状态(逻辑与RN完全一致)
private toggleOwned(id: string) {
this.collection = this.collection.map(amiibo =>
amiibo.id === id ? { ...amiibo, owned: !amiibo.owned } : amiibo
);
}
// 获取稀有度颜色(逻辑与RN完全一致)
private getRarityColor(rarity: string): string {
switch (rarity) {
case 'common': return '#9ca3af';
case 'rare': return '#3b82f6';
case 'epic': return '#8b5cf6';
case 'legendary': return '#f59e0b';
default: return '#9ca3af';
}
}
// 渲染Amiibo项(Builder函数替换RN的renderAmiiboItem)
@Builder
renderAmiiboItem(item: Amiibo) {
Row({ space: 12 }) {
// 图片占位区
Column() {
Stack() {
Text(' toy ')
.fontSize(24)
.textAlign(TextAlign.Center);
}
.width(80)
.height(80)
.backgroundColor('#e2e8f0')
.borderRadius(8)
.justifyContent(FlexAlign.Center);
}
// 信息区域
Column({ space: 4 }) {
// 名称和拥有状态
Row({ space: 0 }) {
Text(item.name)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor('#1e293b')
.flex(1);
Button(item.owned ? '✅' : '⬜')
.backgroundColor(Color.Transparent)
.fontSize(18)
.stateEffect(true)
.marginLeft(8)
.onClick(() => this.toggleOwned(item.id));
}
// 角色名
Text(item.character)
.fontSize(14)
.fontColor('#3b82f6');
// 系列名
Text(item.series)
.fontSize(12)
.fontColor('#64748b');
// 详情信息
Row({ space: 0 }) {
Text(`发行: ${item.releaseDate}`)
.fontSize(12)
.fontColor('#94a3b8');
Text(`¥${item.price.toFixed(2)}`)
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor('#10b981')
.marginLeft('auto');
}
// 稀有度标签
Text(item.rarity === 'common' ? '普通' : item.rarity === 'rare' ? '稀有' : item.rarity === 'epic' ? '史诗' : '传说')
.fontSize(12)
.fontWeight(FontWeight.Medium)
.fontColor(this.getRarityColor(item.rarity))
.alignSelf(ItemAlign.Start);
}
.flex(1);
}
.backgroundColor('#ffffff')
.borderRadius(12)
.padding(12)
.marginBottom(12)
.cardShadow();
}
// 空状态渲染
@Builder
renderEmptyState() {
Column({ space: 8 }) {
Text('暂无数据')
.fontSize(16)
.fontColor('#64748b');
Text('请调整筛选条件查看')
.fontSize(14)
.fontColor('#94a3b8')
.textAlign(TextAlign.Center);
}
.width('100%')
.paddingVertical(60)
.alignItems(ItemAlign.Center);
}
build() {
Column({ space: 0 }) {
// 头部组件
this.Header();
// 内容区域
Scroll() {
Column({ space: 16 }) {
// 搜索栏
this.SearchBar();
// 系列分类
this.SeriesCategory();
// 筛选和排序选项
this.FiltersAndSort();
// 收藏统计
this.StatsCard();
// Amiibo列表标题
Text(`${amiiboSeries.find(s => s.id === this.activeSeries)?.name || 'Amiibo'} (${this.sortedAmiibos.length})`)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#1e293b')
.marginVertical(12)
.width('100%');
// Amiibo列表
if (this.sortedAmiibos.length > 0) {
List() {
LazyForEach(new AmiiboDataSource(this.sortedAmiibos), (item: Amiibo) => {
ListItem() {
this.renderAmiiboItem(item);
}
});
}
.width('100%')
.scrollBar(BarState.Off);
} else {
this.renderEmptyState();
}
// 收藏说明
this.InfoCard();
}
.padding(16)
.width('100%');
}
.flex(1)
.width('100%');
// 底部导航
this.BottomNav();
}
.width('100%')
.height('100%')
.backgroundColor('#f8fafc')
.safeArea(true);
}
// 头部组件
@Builder
Header() {
Row({ space: 0 }) {
Text('Amiibo收藏库')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#1e293b');
Row({ space: 12 }) {
// 搜索按钮
Button()
.width(36)
.height(36)
.borderRadius(18)
.backgroundColor('#f1f5f9')
.stateEffect(true)
.onClick(() => {
AlertDialog.show({
title: '搜索',
confirm: { value: '确定' }
});
}) {
Text('🔍')
.fontSize(18)
.fontColor('#64748b');
}
// 设置按钮
Button()
.width(36)
.height(36)
.borderRadius(18)
.backgroundColor('#f1f5f9')
.stateEffect(true)
.onClick(() => {
AlertDialog.show({
title: '设置',
confirm: { value: '确定' }
});
}) {
Text('⚙️')
.fontSize(18)
.fontColor('#64748b');
}
}
.marginLeft('auto');
}
.padding(20)
.backgroundColor('#ffffff')
.borderBottom({ width: 1, color: '#e2e8f0' })
.width('100%');
}
// 搜索栏
@Builder
SearchBar() {
Row({ space: 12 }) {
Text('🔍')
.fontSize(18)
.fontColor('#64748b');
Text('搜索Amiibo名称')
.fontSize(14)
.fontColor('#94a3b8')
.flex(1);
}
.backgroundColor('#ffffff')
.borderRadius(20)
.padding({ top: 12, bottom: 12, left: 16, right: 16 })
.cardShadow()
.width('100%');
}
// 系列分类标签栏
@Builder
SeriesCategory() {
Column({ space: 0 }) {
Text('Amiibo系列')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#1e293b')
.marginVertical(12)
.width('100%');
Scroll({ scrollDirection: ScrollDirection.Horizontal }) {
Row({ space: 12 }) {
ForEach(amiiboSeries, (series: Series) => {
Button()
.backgroundColor(this.activeSeries === series.id ? '#3b82f6' : '#ffffff')
.borderRadius(20)
.padding({ top: 8, bottom: 8, left: 16, right: 16 })
.stateEffect(true)
.cardShadow()
.onClick(() => this.activeSeries = series.id) {
Column({ space: 4 }) {
Text(series.icon)
.fontSize(16)
.fontColor(this.activeSeries === series.id ? '#ffffff' : '#64748b');
Text(series.name)
.fontSize(12)
.fontColor(this.activeSeries === series.id ? '#ffffff' : '#64748b')
.fontWeight(this.activeSeries === series.id ? FontWeight.Medium : FontWeight.Normal);
}
};
});
}
.width('auto');
}
.scrollBar(BarState.Off);
}
.width('100%');
}
// 筛选和排序选项
@Builder
FiltersAndSort() {
Column({ space: 12 }) {
// 拥有状态筛选
Button('只看拥有')
.backgroundColor(this.filterOwned ? '#3b82f6' : '#f1f5f9')
.borderRadius(20)
.padding({ top: 6, bottom: 6, left: 12, right: 12 })
.fontSize(12)
.fontColor('#3b82f6')
.fontWeight(FontWeight.Medium)
.stateEffect(true)
.alignSelf(ItemAlign.Start)
.onClick(() => this.filterOwned = !this.filterOwned);
// 排序选项
Row({ space: 8 }) {
Text('排序:')
.fontSize(14)
.fontColor('#64748b');
// 名称排序
Button('名称')
.backgroundColor(this.sortBy === 'name' ? '#3b82f6' : '#f1f5f9')
.borderRadius(20)
.padding({ top: 6, bottom: 6, left: 12, right: 12 })
.fontSize(12)
.fontColor('#3b82f6')
.stateEffect(true)
.onClick(() => this.sortBy = 'name');
// 日期排序
Button('日期')
.backgroundColor(this.sortBy === 'date' ? '#3b82f6' : '#f1f5f9')
.borderRadius(20)
.padding({ top: 6, bottom: 6, left: 12, right: 12 })
.fontSize(12)
.fontColor('#3b82f6')
.stateEffect(true)
.onClick(() => this.sortBy = 'date');
// 价格排序
Button('价格')
.backgroundColor(this.sortBy === 'price' ? '#3b82f6' : '#f1f5f9')
.borderRadius(20)
.padding({ top: 6, bottom: 6, left: 12, right: 12 })
.fontSize(12)
.fontColor('#3b82f6')
.stateEffect(true)
.onClick(() => this.sortBy = 'price');
}
.width('100%')
.justifyContent(FlexAlign.Start);
}
.backgroundColor('#ffffff')
.borderRadius(12)
.padding(12)
.cardShadow()
.width('100%');
}
// 收藏统计卡片
@Builder
StatsCard() {
Row({ space: 8 }) {
// 当前系列
Column({ space: 4 }) {
Text(`${this.sortedAmiibos.length}`)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#3b82f6');
Text('当前系列')
.fontSize(12)
.fontColor('#64748b');
}
.backgroundColor('#ffffff')
.borderRadius(12)
.padding(12)
.alignItems(ItemAlign.Center)
.flex(1)
.cardShadow();
// 已拥有
Column({ space: 4 }) {
Text(`${this.collection.filter(a => a.owned).length}`)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#3b82f6');
Text('已拥有')
.fontSize(12)
.fontColor('#64748b');
}
.backgroundColor('#ffffff')
.borderRadius(12)
.padding(12)
.alignItems(ItemAlign.Center)
.flex(1)
.cardShadow();
// 未拥有
Column({ space: 4 }) {
Text(`${this.collection.filter(a => !a.owned).length}`)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#3b82f6');
Text('未拥有')
.fontSize(12)
.fontColor('#64748b');
}
.backgroundColor('#ffffff')
.borderRadius(12)
.padding(12)
.alignItems(ItemAlign.Center)
.flex(1)
.cardShadow();
// 总计
Column({ space: 4 }) {
Text('10')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#3b82f6');
Text('总计')
.fontSize(12)
.fontColor('#64748b');
}
.backgroundColor('#ffffff')
.borderRadius(12)
.padding(12)
.alignItems(ItemAlign.Center)
.flex(1)
.cardShadow();
}
.width('100%');
}
// 使用说明卡片
@Builder
InfoCard() {
Column({ space: 8 }) {
Text('收藏说明')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor('#1e293b');
Text('• 点击✅/⬜按钮标记是否拥有')
.fontSize(14)
.fontColor('#64748b')
.lineHeight(22);
Text('• 可按系列、稀有度筛选')
.fontSize(14)
.fontColor('#64748b')
.lineHeight(22);
Text('• 支持按名称、日期、价格排序')
.fontSize(14)
.fontColor('#64748b')
.lineHeight(22);
Text('• 所有数据本地存储')
.fontSize(14)
.fontColor('#64748b')
.lineHeight(22);
}
.backgroundColor('#ffffff')
.borderRadius(12)
.padding(16)
.cardShadow()
.width('100%');
}
// 底部导航
@Builder
BottomNav() {
Row({ space: 0 }) {
// 首页
Button()
.flex(1)
.backgroundColor(Color.Transparent)
.stateEffect(true)
.onClick(() => {
AlertDialog.show({
title: '首页',
confirm: { value: '确定' }
});
}) {
Column({ space: 4 }) {
Text('🏠')
.fontSize(20)
.fontColor('#94a3b8');
Text('首页')
.fontSize(12)
.fontColor('#94a3b8');
}
};
// 收藏
Button()
.flex(1)
.backgroundColor(Color.Transparent)
.stateEffect(true)
.onClick(() => {
AlertDialog.show({
title: '收藏',
confirm: { value: '确定' }
});
}) {
Column({ space: 4 }) {
Text('收藏')
.fontSize(20)
.fontColor('#94a3b8');
Text('收藏')
.fontSize(12)
.fontColor('#94a3b8');
}
};
// 发现
Button()
.flex(1)
.backgroundColor(Color.Transparent)
.stateEffect(true)
.onClick(() => {
AlertDialog.show({
title: '发现',
confirm: { value: '确定' }
});
}) {
Column({ space: 4 }) {
Text('🔍')
.fontSize(20)
.fontColor('#94a3b8');
Text('发现')
.fontSize(12)
.fontColor('#94a3b8');
}
};
// 我的
Button()
.flex(1)
.backgroundColor(Color.Transparent)
.stateEffect(true)
.onClick(() => {
AlertDialog.show({
title: '我的',
confirm: { value: '确定' }
});
}) {
Column({ space: 4 }) {
Text('👤')
.fontSize(20)
.fontColor('#94a3b8');
Text('我的')
.fontSize(12)
.fontColor('#94a3b8');
}
};
}
.backgroundColor('#ffffff')
.borderTop({ width: 1, color: '#e2e8f0' })
.paddingVertical(12)
.width('100%');
}
}
// 鸿蒙List数据源(LazyForEach必需)
class AmiiboDataSource implements IDataSource {
private data: Amiibo[];
private listener: DataChangeListener | null = null;
constructor(data: Amiibo[]) {
this.data = data;
}
totalCount(): number {
return this.data.length;
}
getData(index: number): Amiibo {
return this.data[index];
}
registerDataChangeListener(listener: DataChangeListener): void {
this.listener = listener;
}
unregisterDataChangeListener(): void {
this.listener = null;
}
}
(1)状态管理
React 的 useState 钩子替换为鸿蒙的 @State 装饰器,核心差异在于状态更新方式:
// React Native
const [filterOwned, setFilterOwned] = useState<boolean>(false);
// 更新状态
setFilterOwned(!filterOwned);
// 鸿蒙
@State filterOwned: boolean = false;
// 更新状态
this.filterOwned = !this.filterOwned;
适配要点:
- React 状态更新需要调用
setter函数,鸿蒙直接赋值即可; - React 状态更新是不可变的,鸿蒙直接修改状态对象;
- 两者的响应式原理不同,但开发体验和效果一致;
- 鸿蒙支持计算属性(
get sortedAmiibos()),简化数据处理逻辑。
(2)横向滚动标签栏
React Native 的横向 ScrollView 替换为鸿蒙的 Scroll 组件 + scrollDirection 属性:
// React Native
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
>
{/* 子元素 */}
</ScrollView>
// 鸿蒙
Scroll({ scrollDirection: ScrollDirection.Horizontal }) {
{/* 子元素 */}
}
.scrollBar(BarState.Off);
适配优势:
- 鸿蒙的滚动方向通过构造函数参数明确指定,语义更清晰;
- 滚动条控制通过链式调用实现,代码更简洁;
- 支持更多滚动特性(弹性效果、滚动速度等)。
该 Amiibo 收藏库应用的跨端适配实践验证了分类列表型应用从 React Native 向鸿蒙迁移的高效性,核心业务逻辑可实现完全复用,仅需适配UI组件层和状态管理语法,这种适配模式特别适合数据驱动的分类列表型应用开发。
真实演示案例代码:
// app.tsx
import React, { useState } from 'react';
import { SafeAreaView, View, Text, StyleSheet, TouchableOpacity, ScrollView, Dimensions, Alert, FlatList } from 'react-native';
// Base64 图标库
const ICONS_BASE64 = {
home: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
collect: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
search: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
filter: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
sort: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
settings: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
info: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
more: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
};
const { width, height } = Dimensions.get('window');
// Amiibo类型
type Amiibo = {
id: string;
name: string;
character: string;
series: string;
releaseDate: string;
price: number;
owned: boolean;
rarity: 'common' | 'rare' | 'epic' | 'legendary';
image: string;
};
// 系列类型
type Series = {
id: string;
name: string;
icon: string;
};
// 模拟数据
const amiiboSeries: Series[] = [
{ id: '1', name: '超级马里奥', icon: '🍄' },
{ id: '2', name: '塞尔达传说', icon: '⚔️' },
{ id: '3', name: '宝可梦', icon: '⚡' },
{ id: '4', name: '动物森友会', icon: '🌳' },
{ id: '5', name: '火焰纹章', icon: '🗡️' },
{ id: '6', name: '大金刚', icon: '🍌' },
{ id: '7', name: '星之卡比', icon: '💫' },
{ id: '8', name: '任天堂明星大乱斗', icon: '🎮' },
];
const amiibos: Amiibo[] = [
{
id: '1',
name: '马里奥',
character: '马里奥',
series: '超级马里奥',
releaseDate: '2014-11-21',
price: 19.99,
owned: true,
rarity: 'common',
image: ''
},
{
id: '2',
name: '路易吉',
character: '路易吉',
series: '超级马里奥',
releaseDate: '2014-11-21',
price: 19.99,
owned: false,
rarity: 'common',
image: ''
},
{
id: '3',
name: '桃花公主',
character: '桃花公主',
series: '超级马里奥',
releaseDate: '2014-11-21',
price: 24.99,
owned: true,
rarity: 'rare',
image: ''
},
{
id: '4',
name: '林克',
character: '林克',
series: '塞尔达传说',
releaseDate: '2014-11-21',
price: 29.99,
owned: false,
rarity: 'epic',
image: ''
},
{
id: '5',
name: '塞尔达',
character: '塞尔达',
series: '塞尔达传说',
releaseDate: '2014-11-21',
price: 29.99,
owned: true,
rarity: 'epic',
image: ''
},
{
id: '6',
name: '皮卡丘',
character: '皮卡丘',
series: '宝可梦',
releaseDate: '2014-11-21',
price: 19.99,
owned: false,
rarity: 'rare',
image: ''
},
{
id: '7',
name: '杰尼龟',
character: '杰尼龟',
series: '宝可梦',
releaseDate: '2014-11-21',
price: 19.99,
owned: true,
rarity: 'common',
image: ''
},
{
id: '8',
name: '提米',
character: '提米',
series: '动物森友会',
releaseDate: '2014-11-21',
price: 14.99,
owned: true,
rarity: 'common',
image: ''
},
{
id: '9',
name: '罗宾',
character: '罗宾',
series: '火焰纹章',
releaseDate: '2014-11-21',
price: 24.99,
owned: false,
rarity: 'rare',
image: ''
},
{
id: '10',
name: '大金刚',
character: '大金刚',
series: '大金刚',
releaseDate: '2014-11-21',
price: 24.99,
owned: true,
rarity: 'epic',
image: ''
},
];
const GameLibraryApp: React.FC = () => {
const [collection, setCollection] = useState<Amiibo[]>(amiibos);
const [activeSeries, setActiveSeries] = useState<string>('1');
const [searchQuery, setSearchQuery] = useState<string>('');
const [sortBy, setSortBy] = useState<'name' | 'date' | 'price'>('name');
const [filterOwned, setFilterOwned] = useState<boolean>(false);
// 获取当前系列的Amiibo
const getCurrentSeriesAmiibos = () => {
const seriesName = amiiboSeries.find(series => series.id === activeSeries)?.name;
return collection.filter(amiibo =>
amiibo.series === seriesName &&
(searchQuery === '' || amiibo.name.toLowerCase().includes(searchQuery.toLowerCase())) &&
(!filterOwned || amiibo.owned)
);
};
// 按排序方式整理数据
const sortedAmiibos = getCurrentSeriesAmiibos().sort((a, b) => {
switch (sortBy) {
case 'name':
return a.name.localeCompare(b.name);
case 'date':
return new Date(a.releaseDate).getTime() - new Date(b.releaseDate).getTime();
case 'price':
return a.price - b.price;
default:
return 0;
}
});
// 切换拥有状态
const toggleOwned = (id: string) => {
setCollection(collection.map(amiibo =>
amiibo.id === id ? { ...amiibo, owned: !amiibo.owned } : amiibo
));
};
// 获取稀有度颜色
const getRarityColor = (rarity: string) => {
switch (rarity) {
case 'common': return '#9ca3af';
case 'rare': return '#3b82f6';
case 'epic': return '#8b5cf6';
case 'legendary': return '#f59e0b';
default: return '#9ca3af';
}
};
// 渲染Amiibo项
const renderAmiiboItem = ({ item }: { item: Amiibo }) => (
<View style={styles.amiiboItem}>
<View style={styles.imageContainer}>
<View style={styles.imagePlaceholder}>
<Text style={styles.imageText}> toy </Text>
</View>
</View>
<View style={styles.infoContainer}>
<View style={styles.headerContainer}>
<Text style={styles.name}>{item.name}</Text>
<TouchableOpacity
style={styles.ownedButton}
onPress={() => toggleOwned(item.id)}
>
<Text style={styles.ownedText}>{item.owned ? '✅' : '⬜'}</Text>
</TouchableOpacity>
</View>
<Text style={styles.character}>{item.character}</Text>
<Text style={styles.series}>{item.series}</Text>
<View style={styles.detailsContainer}>
<Text style={styles.releaseDate}>发行: {item.releaseDate}</Text>
<Text style={styles.price}>¥{item.price.toFixed(2)}</Text>
</View>
<View style={styles.rarityContainer}>
<Text style={[styles.rarity, { color: getRarityColor(item.rarity) }]}>
{item.rarity === 'common' ? '普通' : item.rarity === 'rare' ? '稀有' : item.rarity === 'epic' ? '史诗' : '传说'}
</Text>
</View>
</View>
</View>
);
return (
<SafeAreaView style={styles.container}>
{/* 头部 */}
<View style={styles.header}>
<Text style={styles.title}>Amiibo收藏库</Text>
<View style={styles.headerActions}>
<TouchableOpacity style={styles.searchButton} onPress={() => Alert.alert('搜索')}>
<Text style={styles.searchIcon}>🔍</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.settingsButton} onPress={() => Alert.alert('设置')}>
<Text style={styles.settingsIcon}>⚙️</Text>
</TouchableOpacity>
</View>
</View>
<ScrollView style={styles.content}>
{/* 搜索栏 */}
<View style={styles.searchContainer}>
<Text style={styles.searchIcon}>🔍</Text>
<Text style={styles.searchPlaceholder}>搜索Amiibo名称</Text>
</View>
{/* 系列分类 */}
<Text style={styles.sectionTitle}>Amiibo系列</Text>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
style={styles.seriesContainer}
>
<View style={styles.seriesList}>
{amiiboSeries.map(series => (
<TouchableOpacity
key={series.id}
style={[
styles.seriesItem,
activeSeries === series.id && styles.activeSeries
]}
onPress={() => setActiveSeries(series.id)}
>
<Text style={[
styles.seriesIcon,
activeSeries === series.id && styles.activeSeriesIcon
]}>
{series.icon}
</Text>
<Text style={[
styles.seriesText,
activeSeries === series.id && styles.activeSeriesText
]}>
{series.name}
</Text>
</TouchableOpacity>
))}
</View>
</ScrollView>
{/* 筛选和排序选项 */}
<View style={styles.filtersContainer}>
<TouchableOpacity
style={[styles.filterButton, filterOwned && styles.activeFilter]}
onPress={() => setFilterOwned(!filterOwned)}
>
<Text style={styles.filterText}>只看拥有</Text>
</TouchableOpacity>
<View style={styles.sortContainer}>
<Text style={styles.sortLabel}>排序:</Text>
<TouchableOpacity
style={[styles.sortButton, sortBy === 'name' && styles.activeSort]}
onPress={() => setSortBy('name')}
>
<Text style={styles.sortText}>名称</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.sortButton, sortBy === 'date' && styles.activeSort]}
onPress={() => setSortBy('date')}
>
<Text style={styles.sortText}>日期</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.sortButton, sortBy === 'price' && styles.activeSort]}
onPress={() => setSortBy('price')}
>
<Text style={styles.sortText}>价格</Text>
</TouchableOpacity>
</View>
</View>
{/* 收藏统计 */}
<View style={styles.statsContainer}>
<View style={styles.statItem}>
<Text style={styles.statNumber}>{sortedAmiibos.length}</Text>
<Text style={styles.statLabel}>当前系列</Text>
</View>
<View style={styles.statItem}>
<Text style={styles.statNumber}>{collection.filter(a => a.owned).length}</Text>
<Text style={styles.statLabel}>已拥有</Text>
</View>
<View style={styles.statItem}>
<Text style={styles.statNumber}>{collection.filter(a => !a.owned).length}</Text>
<Text style={styles.statLabel}>未拥有</Text>
</View>
<View style={styles.statItem}>
<Text style={styles.statNumber}>10</Text>
<Text style={styles.statLabel}>总计</Text>
</View>
</View>
{/* Amiibo列表 */}
<Text style={styles.sectionTitle}>
{amiiboSeries.find(s => s.id === activeSeries)?.name || 'Amiibo'} ({sortedAmiibos.length})
</Text>
<FlatList
data={sortedAmiibos}
renderItem={renderAmiiboItem}
keyExtractor={item => item.id}
showsVerticalScrollIndicator={false}
/>
{/* 收藏说明 */}
<View style={styles.infoCard}>
<Text style={styles.infoTitle}>收藏说明</Text>
<Text style={styles.infoText}>• 点击✅/⬜按钮标记是否拥有</Text>
<Text style={styles.infoText}>• 可按系列、稀有度筛选</Text>
<Text style={styles.infoText}>• 支持按名称、日期、价格排序</Text>
<Text style={styles.infoText}>• 所有数据本地存储</Text>
</View>
</ScrollView>
{/* 底部导航 */}
<View style={styles.bottomNav}>
<TouchableOpacity
style={styles.navItem}
onPress={() => Alert.alert('首页')}
>
<Text style={styles.navIcon}>🏠</Text>
<Text style={styles.navText}>首页</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.navItem}
onPress={() => Alert.alert('收藏')}
>
<Text style={styles.navIcon}>收藏</Text>
<Text style={styles.navText}>收藏</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.navItem}
onPress={() => Alert.alert('发现')}
>
<Text style={styles.navIcon}>🔍</Text>
<Text style={styles.navText}>发现</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.navItem}
onPress={() => Alert.alert('我的')}
>
<Text style={styles.navIcon}>👤</Text>
<Text style={styles.navText}>我的</Text>
</TouchableOpacity>
</View>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f8fafc',
},
header: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
padding: 20,
backgroundColor: '#ffffff',
borderBottomWidth: 1,
borderBottomColor: '#e2e8f0',
},
title: {
fontSize: 20,
fontWeight: 'bold',
color: '#1e293b',
},
headerActions: {
flexDirection: 'row',
},
searchButton: {
width: 36,
height: 36,
borderRadius: 18,
backgroundColor: '#f1f5f9',
alignItems: 'center',
justifyContent: 'center',
marginRight: 12,
},
searchIcon: {
fontSize: 18,
color: '#64748b',
},
settingsButton: {
width: 36,
height: 36,
borderRadius: 18,
backgroundColor: '#f1f5f9',
alignItems: 'center',
justifyContent: 'center',
},
settingsIcon: {
fontSize: 18,
color: '#64748b',
},
content: {
flex: 1,
padding: 16,
},
searchContainer: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#ffffff',
borderRadius: 20,
paddingVertical: 12,
paddingHorizontal: 16,
marginBottom: 16,
elevation: 1,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 2,
},
searchPlaceholder: {
fontSize: 14,
color: '#94a3b8',
marginLeft: 12,
flex: 1,
},
sectionTitle: {
fontSize: 18,
fontWeight: 'bold',
color: '#1e293b',
marginVertical: 12,
},
seriesContainer: {
marginBottom: 16,
},
seriesList: {
flexDirection: 'row',
},
seriesItem: {
backgroundColor: '#ffffff',
borderRadius: 20,
paddingVertical: 8,
paddingHorizontal: 16,
marginRight: 12,
alignItems: 'center',
elevation: 1,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 2,
},
activeSeries: {
backgroundColor: '#3b82f6',
},
seriesIcon: {
fontSize: 16,
marginBottom: 4,
color: '#64748b',
},
activeSeriesIcon: {
color: '#ffffff',
},
seriesText: {
fontSize: 12,
color: '#64748b',
},
activeSeriesText: {
color: '#ffffff',
fontWeight: '500',
},
filtersContainer: {
backgroundColor: '#ffffff',
borderRadius: 12,
padding: 12,
marginBottom: 16,
elevation: 1,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 2,
},
filterButton: {
backgroundColor: '#f1f5f9',
paddingHorizontal: 12,
paddingVertical: 6,
borderRadius: 20,
alignSelf: 'flex-start',
marginBottom: 12,
},
activeFilter: {
backgroundColor: '#3b82f6',
},
filterText: {
fontSize: 12,
color: '#3b82f6',
fontWeight: '500',
},
activeFilterText: {
color: '#ffffff',
},
sortContainer: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
},
sortLabel: {
fontSize: 14,
color: '#64748b',
marginRight: 8,
},
sortButton: {
backgroundColor: '#f1f5f9',
paddingHorizontal: 12,
paddingVertical: 6,
borderRadius: 20,
marginLeft: 8,
},
activeSort: {
backgroundColor: '#3b82f6',
},
sortText: {
fontSize: 12,
color: '#3b82f6',
},
activeSortText: {
color: '#ffffff',
},
statsContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: 16,
},
statItem: {
backgroundColor: '#ffffff',
borderRadius: 12,
padding: 12,
alignItems: 'center',
flex: 1,
marginHorizontal: 4,
elevation: 1,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 2,
},
statNumber: {
fontSize: 18,
fontWeight: 'bold',
color: '#3b82f6',
},
statLabel: {
fontSize: 12,
color: '#64748b',
marginTop: 4,
},
amiiboItem: {
backgroundColor: '#ffffff',
borderRadius: 12,
flexDirection: 'row',
padding: 12,
marginBottom: 12,
elevation: 1,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 2,
},
imageContainer: {
marginRight: 12,
},
imagePlaceholder: {
width: 80,
height: 80,
backgroundColor: '#e2e8f0',
borderRadius: 8,
alignItems: 'center',
justifyContent: 'center',
},
imageText: {
fontSize: 24,
},
infoContainer: {
flex: 1,
justifyContent: 'space-between',
},
headerContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 4,
},
name: {
fontSize: 16,
fontWeight: 'bold',
color: '#1e293b',
flex: 1,
},
ownedButton: {
marginLeft: 8,
},
ownedText: {
fontSize: 18,
},
character: {
fontSize: 14,
color: '#3b82f6',
marginBottom: 4,
},
series: {
fontSize: 12,
color: '#64748b',
marginBottom: 8,
},
detailsContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 4,
},
releaseDate: {
fontSize: 12,
color: '#94a3b8',
},
price: {
fontSize: 14,
fontWeight: 'bold',
color: '#10b981',
},
rarityContainer: {
alignSelf: 'flex-start',
},
rarity: {
fontSize: 12,
fontWeight: '500',
},
infoCard: {
backgroundColor: '#ffffff',
borderRadius: 12,
padding: 16,
marginTop: 16,
elevation: 1,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 2,
},
infoTitle: {
fontSize: 16,
fontWeight: 'bold',
color: '#1e293b',
marginBottom: 12,
},
infoText: {
fontSize: 14,
color: '#64748b',
lineHeight: 22,
marginBottom: 8,
},
bottomNav: {
flexDirection: 'row',
justifyContent: 'space-around',
backgroundColor: '#ffffff',
borderTopWidth: 1,
borderTopColor: '#e2e8f0',
paddingVertical: 12,
},
navItem: {
alignItems: 'center',
flex: 1,
},
navIcon: {
fontSize: 20,
color: '#94a3b8',
marginBottom: 4,
},
activeNavIcon: {
color: '#3b82f6',
},
navText: {
fontSize: 12,
color: '#94a3b8',
},
activeNavText: {
color: '#3b82f6',
fontWeight: '500',
},
});
export default GameLibraryApp;

打包
接下来通过打包命令npn run harmony将reactNative的代码打包成为bundle,这样可以进行在开源鸿蒙OpenHarmony中进行使用。

打包之后再将打包后的鸿蒙OpenHarmony文件拷贝到鸿蒙的DevEco-Studio工程目录去:

最后运行效果图如下显示:

本文分析了基于React Native实现的游戏收藏应用架构设计。应用采用组件化结构,包含主应用组件、列表渲染、系列筛选、搜索排序等功能模块,实现清晰的关注点分离。状态管理使用useState钩子处理收藏数据、当前系列、搜索查询等核心状态,结合TypeScript确保类型安全。应用实现了多重筛选(系列、搜索关键字、拥有状态)和排序(名称、日期、价格)功能,通过颜色区分不同稀有度。布局采用顶部标题、系列筛选、功能栏和列表的层次结构,提供直观交互体验。优化建议包括使用FlatList性能参数和useReducer管理复杂状态。该实现展示了React Native在收藏类应用开发中的完整解决方案。
欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。
更多推荐




所有评论(0)