使用@react-navigation/native进行导航,使用react-native-vector-icons来添加图标,完成React Native鸿蒙跨平台开发
本文介绍了使用React Native开发旅行记录应用中"收藏旅行"页面的实现步骤: 环境准备与依赖安装:包括Node.js环境搭建和必要的库安装(导航、图标、本地存储等) 导航设置:使用React Navigation创建基础导航结构,包含主页和收藏页 收藏页面实现: 使用AsyncStorage存储收藏数据 通过FlatList展示收藏项 包含删除功能(点击爱心图标) 主页
在React Native中开发一个旅行记录应用中的“收藏旅行”页面,你可以按照以下步骤进行:
- 环境准备
确保你已经安装了Node.js和React Native的开发环境。你可以使用npx react-native init TravelApp来创建一个新的React Native项目。
- 安装依赖
你可能需要一些额外的库来帮助你管理状态和进行网络请求。例如,使用@react-navigation/native和@react-navigation/native-stack进行导航,使用react-native-vector-icons来添加图标,使用react-native-async-storage来保存用户数据等。
npm install @react-navigation/native @react-navigation/native-stack react-native-vector-icons react-native-async-storage
- 设置导航
在你的项目中设置导航。首先,安装依赖后,你可以在App.js中设置基本的导航结构。
import * as React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import HomeScreen from './screens/HomeScreen';
import FavoriteScreen from './screens/FavoriteScreen';
const Stack = createNativeStackNavigator();
function App() {
return (
<NavigationContainer>
<Stack.Navigator initialRouteName="Home">
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Favorites" component={FavoriteScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
export default App;
- 创建收藏页面
在screens文件夹中创建一个新的FavoriteScreen.js文件。这个页面将显示用户的收藏旅行记录。
import React, { useEffect, useState } from 'react';
import { View, Text, FlatList, TouchableOpacity, StyleSheet } from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
import Icon from 'react-native-vector-icons/FontAwesome'; // 假设你使用的是FontAwesome图标库
const FavoriteScreen = ({ navigation }) => {
const [favorites, setFavorites] = useState([]);
useEffect(() => {
loadFavorites();
}, []);
const loadFavorites = async () => {
try {
const data = await AsyncStorage.getItem('favorites');
if (data !== null) {
setFavorites(JSON.parse(data));
}
} catch (e) {
console.error(e);
}
};
return (
<View style={styles.container}>
<FlatList
data={favorites}
keyExtractor={item => item.id.toString()}
renderItem={({ item }) => (
<TouchableOpacity style={styles.item} onPress={() => navigation.navigate('Details', { item })}>
<Text>{item.title}</Text>
<Icon name="heart" size={20} color="red" /> {/* 显示收藏图标 */}
</TouchableOpacity>
)}
/>
</View>
);
};
const styles = StyleSheet.create({
container: { flex: 1, padding: 20 },
item: { padding: 10, marginVertical: 8, backgroundColor: 'f9c2ff', borderRadius: 5 },
});
export default FavoriteScreen;
- 添加收藏功能到其他页面(例如HomeScreen)
在你的主页面(例如HomeScreen)中,添加一个按钮允许用户将旅行添加到收藏中。这通常涉及到更新favorites数组并保存到AsyncStorage。例如:
const addToFavorites = async (item) => {
try {
let favorites = await AsyncStorage.getItem('favorites'); // 获取当前收藏数据
favorites = favorites ? JSON.parse(favorites) : []; // 如果不存在,初始化数组
favorites.push(item); // 添加新项目到收藏数组中
await AsyncStorage.setItem('favorites', JSON.stringify(favorites)); // 保存更新后的收藏数据到AsyncStorage中
} catch (e
真实实际案例代码演示:
// app.tsx
import React, { useState } from 'react';
import { SafeAreaView, View, Text, StyleSheet, TouchableOpacity, ScrollView, Modal, Alert } from 'react-native';
// Base64 图标库
const ICONS = {
mountain: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0id2hpdGUiPjxwYXRoIGQ9Ik0zIDEybDE4LTEydi0yLjI0bC0xOC43NiAxMi41N3oiLz48cGF0aCBkPSJNMyAxMmw5LTZsOSA2diEyLjI0bC05LTYuMzEtOSA2LjMxdi0yLjI0eiIvPjxwYXRoIGQ9Ik0zIDEybDktNiA5IDZ2Mi4yNGwtOS02LjMxLTktNi4zMS0uNzYtLjUxdjIuMjRsLjczLjQ5eiIvPjwvc3ZnPg==',
beach: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0id2hpdGUiPjxwYXRoIGQ9Ik0xMiAyYy00Ljk3IDAtOSA0LjAzLTkgOXM0LjAzIDkgOSA5IDktNC4wMyA5LTljMCA0Ljk3LTQuMDMtOS05LTl6Ii8+PHBhdGggZD0iTTEyIDRjMy44NyAwIDcgMy4xMyA3IDdzLTMuMTMgNy03IDctNy0zLjEzLTctNyAzLjEzLTcgNy03eiIvPjxwYXRoIGQ9Ik0xMiA2YzIuNzYgMCA1IDIuMjQgNSA1cy0yLjI0IDUtNSA1LTUtMi4yNC01LTUgMi4yNC01IDUtNXoiLz48L3N2Zz4=',
city: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0id2hpdGUiPjxwYXRoIGQ9Ik0xMCAyMGgtNnYtNmg0djZ6bS00LTR2NGg0di00aC00em04IDRoLTR2LTRoNHY0em0wLTR2LTRoLTR2NGg0em00IDRoLTR2LTRoNHY0em0wLTR2LTRoLTR2NGg0em0tOCA4aC00di00aDR2NHptLTQtNHY0aDR2LTRoLTR6bTggNGgtNHYtNGg0djR6bTAtNHYtNGgtNHY0aDR6bTQgNGgtNHYtNGg0djR6bTAtNHYtNGgtNHY0aDR6Ii8+PC9zdmc+',
forest: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0id2hpdGUiPjxwYXRoIGQ9Ik0xMiAydjJoMnYyaDJ2MmgydjJoMnYyaDJ2MmgydjJoLTJ2LTJoLTJ2LTJoLTJ2LTJoLTJ2LTJoLTJ2LTJoLTJ2LTJoLTJ2LTJoLTJ2LTJoLTJ2LTJoLTJ2LTJoLTJ2LTJoMnYyaDJ2MmgydjJoMnYyaDJ2MmgydjJoMnYyaDJ2MmgydjJoMnYyaDJ2MmgydjJoMnYyaDJ2MmgydjJoMnYyaDJ2MmgydjJoMnYyaDJ2MmgydjJoMnYyaDJ2MmgydjJoMnYyaDJ2MmgydjJoMnYyaDJ2MmgydjJoMnYyaDJ2MmgydjJoMnYyaDJ2MmgydjJoMnYyaDJ2MmgydjJoMnYyaDJ2MmgydjJoMnYyaDJ2MmgydjJoMnYyaDJ2MmgydjJoMnYyaDJ2MmgydjJoMnYyaDJ2MmgydjJoMnYyaDJ2MmgydjJoMnYyaDJ2MmgydjJoMnYyaDJ2MmgydjJoMnYyaDJ2MmgydjJoMnYyaDJ2MmgydjJoMnYyaDJ2MmgydjJoMnYyaDJ2MmgydjJoMnYyaDJ2MmgydjJoMnYyaDJ2MmgydjJoMnYyaDJ2MmgydjJoMnYyaDJ2MmgydjJoMnYyaDJ2MmgydjJoMnYyaDJ2MmgydjJoMnYyaDJ2MmgydjJoMnYyaDJ2MmgydjJoMnYyaDJ2MmgydjJoMnYyaDJ2MmgydjJoMnYyaDJ2MmgydjJoMnYyaDJ2MmgydjJoMnYyaDJ2MmgydjJoMnYyaDJ2MmgydjJoMnYyaDJ2MmgydjJoMnYyaDJ2MmgydjJoMnYyaDJ2MmgydjJoMnYyaDJ2MmgydjJoMnYyaDJ2MmgydjJoMnYyaDJ2MmgydjJoMnYyaDJ2MmgydjJoMnYyaDJ......',
heart: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0id2hpdGUiPjxwYXRoIGQ9Ik0xMiAyMS4zNWwtLjYxLS4yNy0yLjIxLS45OWMtLjM5LS4xOC0uNzEtLjQ5LS45MS0uODhsLS44OS0xLjg5Yy0uMTktLjM5LS40OS0uNzEtLjg4LS45MWwtMS44OS0uODljLS4zOS0uMTktLjcxLS40OS0uODgtLjg4bC0uOTktMi4yMWMtLjE4LS4zOS0uMjctLjYxLS4yNy0uNjFsLjI3LS42MS45OS0yLjIxYy4xOC0uMzkuNDktLjcxLjg4LS45MWwuODktMS44OWMuMTktLjM5LjQ5LS43MS44OC0uOTFsMi4yMS0uOTljLjM5LS4xOC42MS0uMjcuNjEtLjI3bC42MS4yNyAyLjIxLjk5Yy4zOS4xOC43MS40OS45MS44OGwuODkgMS44OWMuMTkuMzkuNDkuNzEuODguOTFsMS44OS44OWMuMzkuMTkuNzEuNDkuOTEuODhsLjk5IDIuMjFjLjE4LjM5LjI3LjYxLjI3LjYxbC0uMjcuNjEtLjk5IDIuMjFjLS4xOC4zOS0uNDkuNzEtLjg4LjkxbC0uODkgMS44OWMtLjE5LjM5LS40OS43MS0uODguOTFsLTIuMjEuOTljLS4zOS4xOC0uNjEuMjctLjYxLjI3eiIvPjxwYXRoIGQ9Ik0xMiAxN2MtLjU1IDAtMS0uNDUtMS0xczEuNDUtMSAxLTFjLjU1IDAgMSAuNDUgMSAxcy0uNDUgMS0xIDF6Ii8+PHBhdGggZD0iTTEyIDVjLS41NSAwLTEgLjQ1LTEgMXMxLjQ1IDEgMSAxcy0uNDUgMS0xIDFjLS41NSAwLTEtLjQ1LTEtMXMuNDUtMSAxLTF6Ii8+PC9zdmc+',
location: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0id2hpdGUiPjxwYXRoIGQ9Ik0xMiAyQzguMTMgMiA1IDUuMTMgNSA5YzAgNS4yNSA3IDEzIDcgMTMgMCAwIDcgLTcuNzUgNy0xMyAwLTMuODctMy4xMy03LTctN3ptMCA5LjVjLTEuMzggMC0yLjUtMS4xMi0yLjUtMi41cy4xMi0yLjUgMi41LTIuNSAyLjUgMS4xMiAyLjUgMi41UzEzLjM4IDExLjUgMTIgMTEuNXoiLz48L3N2Zz4=',
camera: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0id2hpdGUiPjxwYXRoIGQ9Ik0xOSA3aC00LjE4Yy0uNDEtMS4yMy0xLjUyLTIuMTMtMi44Mi0yLjEzUzkuNiA1Ljc3IDkuMTggN0g1Yy0xLjEgMC0yIC45LTIgMnYxMGMwIDEuMS45IDIgMiAyaDE0YzEuMSAwIDItLjkgMi0yVjljMC0xLjEtLjktMi0yLTJ6bS03IDdjLTEuNjYgMC0zLTEuMzQtMy0zczEuMzQtMyAzLTMgMyAxLjM0IDMgMy0xLjM0IDMtMyAzeiIvPjxwYXRoIGQ9Ik05IDEzaC0ydi0yaDJ2MnoiLz48L3N2Zz4=',
calendar: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0id2hpdGUiPjxwYXRoIGQ9Ik0xOSA0aC0xVjJoLTJ2Mkg4VjJINlY0SDVjLTEuMSAwLTIgLjktMiAydjEzYzAgMS4xLjkgMiAyIDJoMTRjMS4xIDAgMi0uOSAyLTJWNmMwLTEuMS0uOS0yLTItMnptMCAxNUg1VjhoMTR2MTF6bS05LTVoMlY3aC0yeiIvPjwvc3ZnPg==',
star: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0id2hpdGUiPjxwYXRoIGQ9Ik0xMiAxNy4yN0w1LjIgMjFsMS40LTEuMjNMOSAxNi43NmwzLjQtNC45Nkw5IDE2Ljc2bDMuNCA0Ljk0IDEuNC0xLjIzTDEyIDE3LjI3eiIvPjxwYXRoIGQ9Ik0xMiA1LjE3TDkuNCA4LjQ4bDMuNi41Mi0yLjggMi44NS42NiAzLjk1TDEyIDEzLjIzbDEuMDQtMy45NS42Ni0zLjk1LTIuOC0yLjg1IDMuNi0uNTJMMTIgNS4xN3oiLz48L3N2Zz4=',
close: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0id2hpdGUiPjxwYXRoIGQ9Ik0xOSA2LjQxTDE3LjU5IDUgMTIgMTAuNTkgNi40MSA1IDUgNi40MSAxMC41OSAxMiA1IDE3LjU5IDYuNDEgMTkgMTIgMTMuNDEgMTcuNTkgMTkgMTkgMTcuNTkgMTMuNDEgMTJ6Ii8+PC9zdmc+'
};
// 默认收藏旅行数据
const DEFAULT_FAVORITES = [
{
id: '1',
title: '桂林山水之旅',
location: '广西桂林',
date: '2023-05-15',
image: 'https://picsum.photos/600/400?random=1',
type: 'mountain',
rating: 5,
description: '漓江风光美不胜收,阳朔西街夜景迷人'
},
{
id: '2',
title: '三亚海滩度假',
location: '海南三亚',
date: '2023-04-22',
image: 'https://picsum.photos/600/400?random=2',
type: 'beach',
rating: 4,
description: '亚龙湾沙滩细腻,海水清澈见底'
},
{
id: '3',
title: '北京文化探索',
location: '北京',
date: '2023-03-10',
image: 'https://picsum.photos/600/400?random=3',
type: 'city',
rating: 5,
description: '故宫博物院历史悠久,长城雄伟壮观'
},
{
id: '4',
title: '张家界森林公园',
location: '湖南张家界',
date: '2023-02-18',
image: 'https://picsum.photos/600/400?random=4',
type: 'forest',
rating: 4,
description: '天门山玻璃栈道惊险刺激,袁家界风景如画'
}
];
const TravelFavorites: React.FC = () => {
const [favorites, setFavorites] = useState(DEFAULT_FAVORITES);
const [selectedTravel, setSelectedTravel] = useState<any>(null);
const [modalVisible, setModalVisible] = useState(false);
// 获取旅行类型图标
const getTypeIcon = (type: string) => {
switch (type) {
case 'mountain': return ICONS.mountain;
case 'beach': return ICONS.beach;
case 'city': return ICONS.city;
case 'forest': return ICONS.forest;
default: return ICONS.mountain;
}
};
// 获取旅行类型颜色
const getTypeColor = (type: string) => {
switch (type) {
case 'mountain': return '#4361ee';
case 'beach': return '#4cc9f0';
case 'city': return '#f72585';
case 'forest': return '#2ec4b6';
default: return '#4361ee';
}
};
// 移除收藏
const removeFavorite = (id: string) => {
Alert.alert(
'取消收藏',
'确定要从收藏中移除此旅行吗?',
[
{ text: '取消', style: 'cancel' },
{
text: '移除',
style: 'destructive',
onPress: () => {
setFavorites(favorites.filter(travel => travel.id !== id));
Alert.alert('已移除', '旅行已从收藏中移除');
}
}
]
);
};
// 查看旅行详情
const viewTravelDetails = (travel: any) => {
setSelectedTravel(travel);
setModalVisible(true);
};
// 渲染星级评分
const renderRating = (rating: number) => {
return (
<View style={styles.ratingContainer}>
{[...Array(5)].map((_, i) => (
<Text
key={i}
style={[styles.star, i < rating ? styles.filledStar : styles.emptyStar]}
>
{decodeURIComponent(escape(atob(ICONS.star.split(',')[1])))}
</Text>
))}
</View>
);
};
return (
<SafeAreaView style={styles.container}>
<View style={styles.header}>
<Text style={styles.title}>❤️ 我的收藏</Text>
<Text style={styles.subtitle}>珍藏的美好旅行回忆</Text>
<View style={styles.statsContainer}>
<View style={styles.statBox}>
<Text style={styles.statNumber}>{favorites.length}</Text>
<Text style={styles.statLabel}>收藏旅行</Text>
</View>
</View>
</View>
<ScrollView contentContainerStyle={styles.content}>
{favorites.length === 0 ? (
<View style={styles.emptyContainer}>
<Text style={styles.emptyIcon}>{decodeURIComponent(escape(atob(ICONS.heart.split(',')[1])))}</Text>
<Text style={styles.emptyText}>暂无收藏</Text>
<Text style={styles.emptySubtext}>快去发现精彩旅行并收藏吧</Text>
</View>
) : (
favorites.map((travel) => (
<View key={travel.id} style={styles.travelCard}>
<View style={[styles.typeBadge, { backgroundColor: getTypeColor(travel.type) }]}>
<Text style={styles.typeIcon}>
{decodeURIComponent(escape(atob(getTypeIcon(travel.type).split(',')[1])))}
</Text>
</View>
<View style={styles.travelHeader}>
<Text style={styles.travelTitle}>{travel.title}</Text>
<TouchableOpacity
style={styles.removeButton}
onPress={() => removeFavorite(travel.id)}
>
<Text style={styles.removeIcon}>
{decodeURIComponent(escape(atob(ICONS.close.split(',')[1])))}
</Text>
</TouchableOpacity>
</View>
<View style={styles.travelInfo}>
<View style={styles.locationRow}>
<Text style={styles.locationIcon}>
{decodeURIComponent(escape(atob(ICONS.location.split(',')[1])))}
</Text>
<Text style={styles.travelLocation}>{travel.location}</Text>
</View>
<View style={styles.dateRow}>
<Text style={styles.dateIcon}>
{decodeURIComponent(escape(atob(ICONS.calendar.split(',')[1])))}
</Text>
<Text style={styles.travelDate}>{travel.date}</Text>
</View>
{renderRating(travel.rating)}
</View>
<Text style={styles.travelDescription}>{travel.description}</Text>
<TouchableOpacity
style={styles.viewButton}
onPress={() => viewTravelDetails(travel)}
>
<Text style={styles.viewButtonText}>查看详情</Text>
</TouchableOpacity>
</View>
))
)}
</ScrollView>
{/* 旅行详情模态框 */}
<Modal
animationType="slide"
transparent={true}
visible={modalVisible}
onRequestClose={() => setModalVisible(false)}
>
<View style={styles.modalOverlay}>
<View style={styles.modalContent}>
<View style={styles.modalHeader}>
<Text style={styles.modalTitle}>旅行详情</Text>
<TouchableOpacity onPress={() => setModalVisible(false)}>
<Text style={styles.closeButton}>×</Text>
</TouchableOpacity>
</View>
{selectedTravel && (
<View style={styles.modalBody}>
<View style={[styles.modalTypeBadge, { backgroundColor: getTypeColor(selectedTravel.type) }]}>
<Text style={styles.modalTypeIcon}>
{decodeURIComponent(escape(atob(getTypeIcon(selectedTravel.type).split(',')[1])))}
</Text>
</View>
<Text style={styles.modalTitleText}>{selectedTravel.title}</Text>
<View style={styles.modalInfoRow}>
<Text style={styles.modalLabel}>地点:</Text>
<Text style={styles.modalValue}>{selectedTravel.location}</Text>
</View>
<View style={styles.modalInfoRow}>
<Text style={styles.modalLabel}>日期:</Text>
<Text style={styles.modalValue}>{selectedTravel.date}</Text>
</View>
<View style={styles.modalInfoRow}>
<Text style={styles.modalLabel}>评分:</Text>
<View style={styles.modalRating}>
{renderRating(selectedTravel.rating)}
</View>
</View>
<View style={styles.modalInfoRow}>
<Text style={styles.modalLabel}>描述:</Text>
<Text style={styles.modalDescription}>{selectedTravel.description}</Text>
</View>
</View>
)}
<View style={styles.modalActions}>
<TouchableOpacity
style={[styles.modalButton, styles.modalRemoveButton]}
onPress={() => {
removeFavorite(selectedTravel?.id);
setModalVisible(false);
}}
>
<Text style={styles.modalButtonText}>取消收藏</Text>
</TouchableOpacity>
</View>
</View>
</View>
</Modal>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f8f9fa',
},
header: {
paddingTop: 30,
paddingBottom: 20,
paddingHorizontal: 20,
backgroundColor: '#ffffff',
borderBottomWidth: 1,
borderBottomColor: '#e9ecef',
},
title: {
fontSize: 24,
fontWeight: 'bold',
color: '#212529',
textAlign: 'center',
},
subtitle: {
fontSize: 14,
color: '#6c757d',
textAlign: 'center',
marginTop: 4,
},
statsContainer: {
flexDirection: 'row',
justifyContent: 'center',
marginTop: 15,
},
statBox: {
alignItems: 'center',
paddingHorizontal: 20,
paddingVertical: 10,
backgroundColor: '#e9ecef',
borderRadius: 12,
},
statNumber: {
fontSize: 20,
fontWeight: 'bold',
color: '#212529',
},
statLabel: {
fontSize: 14,
color: '#6c757d',
marginTop: 2,
},
content: {
padding: 16,
},
emptyContainer: {
alignItems: 'center',
justifyContent: 'center',
paddingVertical: 60,
},
emptyIcon: {
fontSize: 64,
color: '#ced4da',
marginBottom: 20,
},
emptyText: {
fontSize: 20,
fontWeight: '600',
color: '#495057',
marginBottom: 8,
},
emptySubtext: {
fontSize: 16,
color: '#6c757d',
},
travelCard: {
backgroundColor: '#ffffff',
borderRadius: 16,
padding: 20,
marginBottom: 16,
elevation: 3,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 8,
position: 'relative',
},
typeBadge: {
position: 'absolute',
top: -12,
right: 20,
width: 40,
height: 40,
borderRadius: 20,
alignItems: 'center',
justifyContent: 'center',
zIndex: 1,
},
typeIcon: {
fontSize: 20,
color: '#ffffff',
},
travelHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 15,
},
travelTitle: {
fontSize: 20,
fontWeight: 'bold',
color: '#212529',
flex: 1,
},
removeButton: {
padding: 8,
marginLeft: 10,
},
removeIcon: {
fontSize: 20,
color: '#adb5bd',
},
travelInfo: {
marginBottom: 15,
},
locationRow: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 8,
},
locationIcon: {
fontSize: 16,
color: '#4361ee',
marginRight: 8,
},
travelLocation: {
fontSize: 16,
color: '#495057',
fontWeight: '600',
},
dateRow: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 10,
},
dateIcon: {
fontSize: 16,
color: '#4cc9f0',
marginRight: 8,
},
travelDate: {
fontSize: 14,
color: '#6c757d',
},
ratingContainer: {
flexDirection: 'row',
marginTop: 5,
},
star: {
fontSize: 16,
marginRight: 2,
},
filledStar: {
color: '#ffd43b',
},
emptyStar: {
color: '#e9ecef',
},
travelDescription: {
fontSize: 14,
color: '#6c757d',
lineHeight: 20,
marginBottom: 20,
},
viewButton: {
backgroundColor: '#4361ee',
paddingVertical: 12,
borderRadius: 10,
alignItems: 'center',
},
viewButtonText: {
fontSize: 16,
fontWeight: '600',
color: '#ffffff',
},
modalOverlay: {
flex: 1,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
justifyContent: 'center',
alignItems: 'center',
},
modalContent: {
backgroundColor: '#ffffff',
width: '85%',
borderRadius: 20,
padding: 25,
maxHeight: '80%',
},
modalHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 20,
},
modalTitle: {
fontSize: 20,
fontWeight: 'bold',
color: '#212529',
},
closeButton: {
fontSize: 30,
color: '#adb5bd',
fontWeight: '200',
},
modalBody: {
marginBottom: 25,
},
modalTypeBadge: {
width: 50,
height: 50,
borderRadius: 25,
alignItems: 'center',
justifyContent: 'center',
alignSelf: 'center',
marginBottom: 15,
},
modalTypeIcon: {
fontSize: 24,
color: '#ffffff',
},
modalTitleText: {
fontSize: 22,
fontWeight: 'bold',
color: '#212529',
textAlign: 'center',
marginBottom: 20,
},
modalInfoRow: {
flexDirection: 'row',
marginBottom: 15,
alignItems: 'flex-start',
},
modalLabel: {
fontSize: 16,
fontWeight: '600',
color: '#495057',
width: 60,
},
modalValue: {
flex: 1,
fontSize: 16,
color: '#212529',
},
modalRating: {
flex: 1,
},
modalDescription: {
flex: 1,
fontSize: 16,
color: '#212529',
lineHeight: 22,
},
modalActions: {
flexDirection: 'row',
justifyContent: 'center',
},
modalButton: {
flex: 1,
paddingVertical: 15,
borderRadius: 12,
alignItems: 'center',
marginHorizontal: 5,
},
modalRemoveButton: {
backgroundColor: '#e63946',
},
modalButtonText: {
fontSize: 16,
fontWeight: 'bold',
color: '#ffffff',
},
});
export default TravelFavorites;
这段代码实现了一个旅行收藏管理界面,主要用于展示和管理用户收藏的旅行信息。从鸿蒙开发的角度来看,这个组件展现了现代移动应用开发的核心设计理念和实现方式。
在数据结构设计上,DEFAULT_FAVORITES数组采用了ID、标题、位置、日期、图片、类型、评分和描述的组合方式来定义旅行收藏对象。这种设计在鸿蒙应用开发中同样适用,鸿蒙的ArkTS语言支持类似的对象数组结构,可以使用interface来定义旅行收藏对象的类型结构,确保数据的一致性和类型安全。鸿蒙开发中推荐使用资源管理机制来处理图标,将图标文件放置在resources目录下,通过$r(‘app.media.icon_name’)方式引用。
在状态管理方面,React使用useState来维护组件状态,包括收藏列表、选中旅行、模态框显示状态等。鸿蒙开发中可以使用@State装饰器实现类似的状态管理机制,通过状态变量的变更来驱动UI的自动更新。鸿蒙的声明式UI框架同样具有高效的渲染机制,通过状态变化自动计算最小渲染代价来更新界面。
UI布局采用了卡片列表设计,通过ScrollView容器展示旅行收藏卡片。在鸿蒙开发中,可以使用Column和Row组合配合ForEach循环渲染来实现类似的列表布局效果。每个旅行卡片包含了图片展示区、标题信息区、位置详情区和操作按钮区,这种模块化的设计便于维护和扩展。

旅行类型图标处理根据旅行类型动态选择不同的图标和颜色,这种设计在鸿蒙应用中同样重要。鸿蒙支持通过条件渲染来实现类似的功能,根据旅行类型动态绑定不同的图标资源和样式属性。颜色管理方面,为不同旅行类型指定了特定的主题色,这在鸿蒙应用中可以通过资源文件统一管理颜色值。
模态框的实现体现了良好的用户体验设计,通过透明遮罩来突出操作焦点。鸿蒙系统提供了丰富的弹窗组件,可以实现更加原生和一致的用户交互体验。旅行详情查看功能可以通过鸿蒙的Sheet组件或者自定义弹窗来实现。
数据持久化方面,虽然代码中没有直接体现,但在实际应用中会将收藏数据存储在本地或云端。鸿蒙提供了多种数据存储方案,包括Preferences轻量级数据存储、KVStore分布式数据存储等,可以根据应用需求选择合适的存储方式。
在交互设计上,移除收藏操作提供了确认机制,防止误操作。鸿蒙系统有内置的AlertDialog组件,可以提供更加原生和一致的用户交互体验。星级评分展示通过循环渲染多个星星图标来实现,这种数据可视化在鸿蒙应用中可以通过ForEach循环结合条件渲染来实现。
打包
接下来通过打包命令npn run harmony将reactNative的代码打包成为bundle,这样可以进行在开源鸿蒙OpenHarmony中进行使用。

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

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

欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。
更多推荐



所有评论(0)