Appearance
多页面路由
在开发个人主页/小工具APP时,多页面路由是一个重要的功能。在这一节中,我们将学习如何使用 React Navigation 实现多页面路由,为应用添加多个页面并实现页面间的导航。
1. 项目初始化
首先,让我们初始化一个新的 React Native 项目:
bash
npx react-native init ToolApp
cd ToolApp2. 安装必要的依赖
我们需要安装以下依赖:
bash
# 安装导航库
npm install @react-navigation/native @react-navigation/stack @react-navigation/bottom-tabs
# 安装导航依赖
npm install react-native-screens react-native-safe-area-context react-native-gesture-handler
# 安装UI组件库
npm install react-native-paper
# 安装图标库
npm install react-native-vector-icons
# 安装状态管理库
npm install @reduxjs/toolkit react-redux3. 创建导航结构
3.1 创建底部标签导航
js
// src/navigation/BottomTabNavigator.js
import React from 'react';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { Ionicons } from '@expo/vector-icons';
import HomeScreen from '../screens/HomeScreen';
import ToolsScreen from '../screens/ToolsScreen';
import ProfileScreen from '../screens/ProfileScreen';
const Tab = createBottomTabNavigator();
const BottomTabNavigator = () => {
return (
<Tab.Navigator
screenOptions={({ route }) => ({
tabBarIcon: ({ focused, color, size }) => {
let iconName;
if (route.name === 'Home') {
iconName = focused ? 'home' : 'home-outline';
} else if (route.name === 'Tools') {
iconName = focused ? 'grid' : 'grid-outline';
} else if (route.name === 'Profile') {
iconName = focused ? 'person' : 'person-outline';
}
return <Ionicons name={iconName} size={size} color={color} />;
},
tabBarActiveTintColor: '#007AFF',
tabBarInactiveTintColor: 'gray',
headerShown: false,
})}
>
<Tab.Screen name="Home" component={HomeScreen} options={{ title: '首页' }} />
<Tab.Screen name="Tools" component={ToolsScreen} options={{ title: '工具' }} />
<Tab.Screen name="Profile" component={ProfileScreen} options={{ title: '我的' }} />
</Tab.Navigator>
);
};
export default BottomTabNavigator;3.2 创建主导航栈
js
// src/navigation/AppNavigator.js
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import BottomTabNavigator from './BottomTabNavigator';
import CalculatorScreen from '../screens/CalculatorScreen';
import NotesScreen from '../screens/NotesScreen';
import NoteDetailScreen from '../screens/NoteDetailScreen';
import SettingsScreen from '../screens/SettingsScreen';
import AboutScreen from '../screens/AboutScreen';
const Stack = createStackNavigator();
const AppNavigator = () => {
return (
<NavigationContainer>
<Stack.Navigator
screenOptions={{
headerShown: false,
}}
>
<Stack.Screen name="Main" component={BottomTabNavigator} />
<Stack.Screen name="Calculator" component={CalculatorScreen} />
<Stack.Screen name="Notes" component={NotesScreen} />
<Stack.Screen name="NoteDetail" component={NoteDetailScreen} />
<Stack.Screen name="Settings" component={SettingsScreen} />
<Stack.Screen name="About" component={AboutScreen} />
</Stack.Navigator>
</NavigationContainer>
);
};
export default AppNavigator;4. 创建页面组件
4.1 创建首页
js
// src/screens/HomeScreen.js
import React from 'react';
import { View, Text, StyleSheet, ScrollView, TouchableOpacity, Image } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import { Ionicons } from '@expo/vector-icons';
const HomeScreen = () => {
const navigation = useNavigation();
const tools = [
{
id: 'calculator',
name: '计算器',
icon: 'calculator',
description: '简单易用的计算器',
screen: 'Calculator',
},
{
id: 'notes',
name: '记事本',
icon: 'document-text',
description: '记录你的想法',
screen: 'Notes',
},
{
id: 'timer',
name: '计时器',
icon: 'time',
description: '精确计时工具',
screen: 'Timer',
},
{
id: 'weather',
name: '天气',
icon: 'partly-sunny',
description: '实时天气信息',
screen: 'Weather',
},
];
return (
<ScrollView style={styles.container} showsVerticalScrollIndicator={false}>
<View style={styles.header}>
<Text style={styles.greeting}>你好,欢迎使用小工具</Text>
<Text style={styles.subGreeting}>选择一个工具开始使用</Text>
</View>
<View style={styles.toolsGrid}>
{tools.map((tool) => (
<TouchableOpacity
key={tool.id}
style={styles.toolCard}
onPress={() => navigation.navigate(tool.screen)}
>
<View style={styles.toolIconContainer}>
<Ionicons name={tool.icon} size={32} color="#007AFF" />
</View>
<Text style={styles.toolName}>{tool.name}</Text>
<Text style={styles.toolDescription}>{tool.description}</Text>
</TouchableOpacity>
))}
</View>
<View style={styles.section}>
<Text style={styles.sectionTitle}>最近使用</Text>
<View style={styles.recentTools}>
{tools.slice(0, 2).map((tool) => (
<TouchableOpacity
key={tool.id}
style={styles.recentToolCard}
onPress={() => navigation.navigate(tool.screen)}
>
<Ionicons name={tool.icon} size={24} color="#007AFF" />
<Text style={styles.recentToolName}>{tool.name}</Text>
</TouchableOpacity>
))}
</View>
</View>
</ScrollView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5F5F5',
},
header: {
backgroundColor: 'white',
paddingHorizontal: 20,
paddingVertical: 30,
borderBottomLeftRadius: 20,
borderBottomRightRadius: 20,
marginBottom: 20,
},
greeting: {
fontSize: 24,
fontWeight: 'bold',
color: '#333',
marginBottom: 8,
},
subGreeting: {
fontSize: 16,
color: '#666',
},
toolsGrid: {
flexDirection: 'row',
flexWrap: 'wrap',
paddingHorizontal: 16,
marginBottom: 20,
},
toolCard: {
width: '48%',
backgroundColor: 'white',
borderRadius: 12,
padding: 16,
marginBottom: 16,
marginHorizontal: '1%',
elevation: 2,
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 1,
},
shadowOpacity: 0.1,
shadowRadius: 2,
},
toolIconContainer: {
width: 60,
height: 60,
borderRadius: 12,
backgroundColor: '#E3F2FD',
justifyContent: 'center',
alignItems: 'center',
marginBottom: 12,
},
toolName: {
fontSize: 16,
fontWeight: '600',
color: '#333',
marginBottom: 8,
},
toolDescription: {
fontSize: 14,
color: '#666',
},
section: {
paddingHorizontal: 16,
marginBottom: 20,
},
sectionTitle: {
fontSize: 18,
fontWeight: '600',
color: '#333',
marginBottom: 12,
},
recentTools: {
flexDirection: 'row',
},
recentToolCard: {
flex: 1,
backgroundColor: 'white',
borderRadius: 12,
padding: 16,
marginRight: 12,
flexDirection: 'row',
alignItems: 'center',
elevation: 2,
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 1,
},
shadowOpacity: 0.1,
shadowRadius: 2,
},
recentToolName: {
marginLeft: 12,
fontSize: 16,
color: '#333',
},
});
export default HomeScreen;4.2 创建工具页面
js
// src/screens/ToolsScreen.js
import React from 'react';
import { View, Text, StyleSheet, ScrollView, TouchableOpacity } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import { Ionicons } from '@expo/vector-icons';
const ToolsScreen = () => {
const navigation = useNavigation();
const toolCategories = [
{
id: 'calculators',
name: '计算工具',
icon: 'calculator',
tools: [
{ id: 'basic', name: '基础计算器', screen: 'Calculator' },
{ id: 'scientific', name: '科学计算器', screen: 'ScientificCalculator' },
{ id: 'converter', name: '单位转换', screen: 'Converter' },
],
},
{
id: 'productivity',
name: ' productivity',
icon: 'document-text',
tools: [
{ id: 'notes', name: '记事本', screen: 'Notes' },
{ id: 'todo', name: '待办清单', screen: 'Todo' },
{ id: 'reminder', name: '提醒', screen: 'Reminder' },
],
},
{
id: 'utilities',
name: '实用工具',
icon: 'grid',
tools: [
{ id: 'timer', name: '计时器', screen: 'Timer' },
{ id: 'stopwatch', name: '秒表', screen: 'Stopwatch' },
{ id: 'weather', name: '天气', screen: 'Weather' },
],
},
];
return (
<ScrollView style={styles.container} showsVerticalScrollIndicator={false}>
<View style={styles.header}>
<Text style={styles.title}>工具</Text>
<Text style={styles.subtitle}>选择一个工具类别</Text>
</View>
{toolCategories.map((category) => (
<View key={category.id} style={styles.category}>
<View style={styles.categoryHeader}>
<Ionicons name={category.icon} size={24} color="#007AFF" />
<Text style={styles.categoryName}>{category.name}</Text>
</View>
<View style={styles.toolsList}>
{category.tools.map((tool) => (
<TouchableOpacity
key={tool.id}
style={styles.toolItem}
onPress={() => navigation.navigate(tool.screen)}
>
<Text style={styles.toolName}>{tool.name}</Text>
<Ionicons name="chevron-forward" size={20} color="#999" />
</TouchableOpacity>
))}
</View>
</View>
))}
</ScrollView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5F5F5',
},
header: {
backgroundColor: 'white',
paddingHorizontal: 20,
paddingVertical: 30,
borderBottomLeftRadius: 20,
borderBottomRightRadius: 20,
marginBottom: 20,
},
title: {
fontSize: 24,
fontWeight: 'bold',
color: '#333',
marginBottom: 8,
},
subtitle: {
fontSize: 16,
color: '#666',
},
category: {
backgroundColor: 'white',
borderRadius: 12,
padding: 16,
marginHorizontal: 16,
marginBottom: 16,
elevation: 2,
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 1,
},
shadowOpacity: 0.1,
shadowRadius: 2,
},
categoryHeader: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 16,
},
categoryName: {
fontSize: 18,
fontWeight: '600',
color: '#333',
marginLeft: 12,
},
toolsList: {
borderTopWidth: 1,
borderTopColor: '#F0F0F0',
paddingTop: 12,
},
toolItem: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingVertical: 12,
borderBottomWidth: 1,
borderBottomColor: '#F0F0F0',
},
toolName: {
fontSize: 16,
color: '#333',
},
});
export default ToolsScreen;4.3 创建个人主页
js
// src/screens/ProfileScreen.js
import React from 'react';
import { View, Text, StyleSheet, ScrollView, TouchableOpacity, Image } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import { Ionicons } from '@expo/vector-icons';
const ProfileScreen = () => {
const navigation = useNavigation();
const menuItems = [
{
id: 'settings',
name: '设置',
icon: 'settings',
screen: 'Settings',
},
{
id: 'about',
name: '关于',
icon: 'information-circle',
screen: 'About',
},
{
id: 'feedback',
name: '反馈',
icon: 'mail',
screen: 'Feedback',
},
{
id: 'share',
name: '分享',
icon: 'share-social',
screen: 'Share',
},
];
return (
<ScrollView style={styles.container} showsVerticalScrollIndicator={false}>
<View style={styles.header}>
<View style={styles.profileInfo}>
<Image
source={{ uri: 'https://randomuser.me/api/portraits/men/32.jpg' }}
style={styles.avatar}
/>
<View style={styles.userInfo}>
<Text style={styles.userName}>用户</Text>
<Text style={styles.userEmail}>user@example.com</Text>
</View>
</View>
</View>
<View style={styles.menu}>
{menuItems.map((item) => (
<TouchableOpacity
key={item.id}
style={styles.menuItem}
onPress={() => navigation.navigate(item.screen)}
>
<View style={styles.menuItemLeft}>
<Ionicons name={item.icon} size={24} color="#007AFF" />
<Text style={styles.menuItemName}>{item.name}</Text>
</View>
<Ionicons name="chevron-forward" size={20} color="#999" />
</TouchableOpacity>
))}
</View>
<View style={styles.footer}>
<Text style={styles.version}>版本 1.0.0</Text>
</View>
</ScrollView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5F5F5',
},
header: {
backgroundColor: 'white',
paddingHorizontal: 20,
paddingVertical: 30,
borderBottomLeftRadius: 20,
borderBottomRightRadius: 20,
marginBottom: 20,
},
profileInfo: {
flexDirection: 'row',
alignItems: 'center',
},
avatar: {
width: 80,
height: 80,
borderRadius: 40,
},
userInfo: {
marginLeft: 20,
},
userName: {
fontSize: 20,
fontWeight: 'bold',
color: '#333',
marginBottom: 4,
},
userEmail: {
fontSize: 16,
color: '#666',
},
menu: {
backgroundColor: 'white',
borderRadius: 12,
marginHorizontal: 16,
marginBottom: 20,
elevation: 2,
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 1,
},
shadowOpacity: 0.1,
shadowRadius: 2,
},
menuItem: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingVertical: 16,
paddingHorizontal: 16,
borderBottomWidth: 1,
borderBottomColor: '#F0F0F0',
},
menuItemLeft: {
flexDirection: 'row',
alignItems: 'center',
},
menuItemName: {
fontSize: 16,
color: '#333',
marginLeft: 16,
},
footer: {
alignItems: 'center',
paddingVertical: 20,
},
version: {
fontSize: 14,
color: '#999',
},
});
export default ProfileScreen;5. 创建工具页面
5.1 创建计算器页面
js
// src/screens/CalculatorScreen.js
import React, { useState } from 'react';
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import { Ionicons } from '@expo/vector-icons';
const CalculatorScreen = () => {
const navigation = useNavigation();
const [display, setDisplay] = useState('0');
const [currentValue, setCurrentValue] = useState('');
const [operator, setOperator] = useState(null);
const [previousValue, setPreviousValue] = useState(null);
const handleNumber = (number) => {
if (display === '0' || display === 'Error') {
setDisplay(number.toString());
setCurrentValue(number.toString());
} else {
setDisplay(display + number.toString());
setCurrentValue(currentValue + number.toString());
}
};
const handleOperator = (op) => {
if (currentValue) {
if (previousValue) {
calculate();
}
setOperator(op);
setPreviousValue(currentValue);
setCurrentValue('');
setDisplay(display + ' ' + op + ' ');
}
};
const calculate = () => {
if (previousValue && currentValue && operator) {
let result;
const prev = parseFloat(previousValue);
const current = parseFloat(currentValue);
switch (operator) {
case '+':
result = prev + current;
break;
case '-':
result = prev - current;
break;
case '×':
result = prev * current;
break;
case '÷':
if (current === 0) {
setDisplay('Error');
setCurrentValue('');
setPreviousValue(null);
setOperator(null);
return;
}
result = prev / current;
break;
default:
return;
}
setDisplay(result.toString());
setCurrentValue(result.toString());
setPreviousValue(null);
setOperator(null);
}
};
const clear = () => {
setDisplay('0');
setCurrentValue('');
setPreviousValue(null);
setOperator(null);
};
const backspace = () => {
if (display.length > 1) {
setDisplay(display.slice(0, -1));
if (currentValue) {
setCurrentValue(currentValue.slice(0, -1));
}
} else {
setDisplay('0');
setCurrentValue('');
}
};
return (
<View style={styles.container}>
<View style={styles.header}>
<TouchableOpacity
style={styles.backButton}
onPress={() => navigation.goBack()}
>
<Ionicons name="arrow-back" size={24} color="#333" />
</TouchableOpacity>
<Text style={styles.title}>计算器</Text>
<View style={styles.placeholder} />
</View>
<View style={styles.displayContainer}>
<Text style={styles.display}>{display}</Text>
</View>
<View style={styles.buttonsContainer}>
<TouchableOpacity style={[styles.button, styles.clearButton]} onPress={clear}>
<Text style={styles.clearButtonText}>C</Text>
</TouchableOpacity>
<TouchableOpacity style={[styles.button, styles.operatorButton]} onPress={backspace}>
<Ionicons name="backspace-outline" size={24} color="#333" />
</TouchableOpacity>
<TouchableOpacity style={[styles.button, styles.operatorButton]} onPress={() => handleOperator('%')}>
<Text style={styles.operatorButtonText}>%</Text>
</TouchableOpacity>
<TouchableOpacity style={[styles.button, styles.operatorButton]} onPress={() => handleOperator('÷')}>
<Text style={styles.operatorButtonText}>÷</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.button} onPress={() => handleNumber(7)}>
<Text style={styles.buttonText}>7</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.button} onPress={() => handleNumber(8)}>
<Text style={styles.buttonText}>8</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.button} onPress={() => handleNumber(9)}>
<Text style={styles.buttonText}>9</Text>
</TouchableOpacity>
<TouchableOpacity style={[styles.button, styles.operatorButton]} onPress={() => handleOperator('×')}>
<Text style={styles.operatorButtonText}>×</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.button} onPress={() => handleNumber(4)}>
<Text style={styles.buttonText}>4</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.button} onPress={() => handleNumber(5)}>
<Text style={styles.buttonText}>5</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.button} onPress={() => handleNumber(6)}>
<Text style={styles.buttonText}>6</Text>
</TouchableOpacity>
<TouchableOpacity style={[styles.button, styles.operatorButton]} onPress={() => handleOperator('-')}>
<Text style={styles.operatorButtonText}>-</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.button} onPress={() => handleNumber(1)}>
<Text style={styles.buttonText}>1</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.button} onPress={() => handleNumber(2)}>
<Text style={styles.buttonText}>2</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.button} onPress={() => handleNumber(3)}>
<Text style={styles.buttonText}>3</Text>
</TouchableOpacity>
<TouchableOpacity style={[styles.button, styles.operatorButton]} onPress={() => handleOperator('+')}>
<Text style={styles.operatorButtonText}>+</Text>
</TouchableOpacity>
<TouchableOpacity style={[styles.button, styles.zeroButton]} onPress={() => handleNumber(0)}>
<Text style={styles.buttonText}>0</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.button} onPress={() => handleNumber('.')}>
<Text style={styles.buttonText}>.</Text>
</TouchableOpacity>
<TouchableOpacity style={[styles.button, styles.equalsButton]} onPress={calculate}>
<Text style={styles.equalsButtonText}>=</Text>
</TouchableOpacity>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5F5F5',
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: 16,
paddingVertical: 16,
backgroundColor: 'white',
borderBottomWidth: 1,
borderBottomColor: '#E0E0E0',
},
backButton: {
padding: 8,
},
title: {
fontSize: 18,
fontWeight: '600',
color: '#333',
},
placeholder: {
width: 40,
},
displayContainer: {
backgroundColor: 'white',
paddingHorizontal: 20,
paddingVertical: 40,
alignItems: 'flex-end',
},
display: {
fontSize: 48,
fontWeight: '300',
color: '#333',
},
buttonsContainer: {
flex: 1,
flexDirection: 'row',
flexWrap: 'wrap',
padding: 10,
},
button: {
width: '25%',
height: '20%',
justifyContent: 'center',
alignItems: 'center',
},
zeroButton: {
width: '50%',
},
buttonText: {
fontSize: 24,
color: '#333',
},
clearButton: {
backgroundColor: '#F0F0F0',
},
clearButtonText: {
fontSize: 24,
color: '#007AFF',
},
operatorButton: {
backgroundColor: '#F0F0F0',
},
operatorButtonText: {
fontSize: 24,
color: '#333',
},
equalsButton: {
backgroundColor: '#007AFF',
},
equalsButtonText: {
fontSize: 24,
color: 'white',
},
});
export default CalculatorScreen;5.2 创建记事本页面
js
// src/screens/NotesScreen.js
import React, { useState, useEffect } from 'react';
import { View, Text, StyleSheet, FlatList, TouchableOpacity, TextInput, Alert } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import { Ionicons } from '@expo/vector-icons';
import AsyncStorage from '@react-native-async-storage/async-storage';
const NotesScreen = () => {
const navigation = useNavigation();
const [notes, setNotes] = useState([]);
const [title, setTitle] = useState('');
const [content, setContent] = useState('');
useEffect(() => {
loadNotes();
}, []);
const loadNotes = async () => {
try {
const notesStr = await AsyncStorage.getItem('notes');
if (notesStr) {
setNotes(JSON.parse(notesStr));
}
} catch (error) {
console.error('加载笔记失败:', error);
}
};
const saveNotes = async (newNotes) => {
try {
await AsyncStorage.setItem('notes', JSON.stringify(newNotes));
} catch (error) {
console.error('保存笔记失败:', error);
}
};
const handleAddNote = () => {
if (title.trim()) {
const newNote = {
id: Date.now().toString(),
title: title.trim(),
content: content.trim(),
createdAt: new Date().toISOString(),
};
const newNotes = [newNote, ...notes];
setNotes(newNotes);
saveNotes(newNotes);
setTitle('');
setContent('');
} else {
Alert.alert('提示', '请输入标题');
}
};
const handleNotePress = (note) => {
navigation.navigate('NoteDetail', { note });
};
const renderNote = ({ item }) => (
<TouchableOpacity
style={styles.noteCard}
onPress={() => handleNotePress(item)}
>
<Text style={styles.noteTitle} numberOfLines={1}>{item.title}</Text>
<Text style={styles.noteContent} numberOfLines={2}>{item.content}</Text>
<Text style={styles.noteDate}>{new Date(item.createdAt).toLocaleString()}</Text>
</TouchableOpacity>
);
return (
<View style={styles.container}>
<View style={styles.header}>
<TouchableOpacity
style={styles.backButton}
onPress={() => navigation.goBack()}
>
<Ionicons name="arrow-back" size={24} color="#333" />
</TouchableOpacity>
<Text style={styles.title}>记事本</Text>
<View style={styles.placeholder} />
</View>
<View style={styles.form}>
<TextInput
style={styles.titleInput}
placeholder="标题"
placeholderTextColor="#999"
value={title}
onChangeText={setTitle}
/>
<TextInput
style={styles.contentInput}
placeholder="内容"
placeholderTextColor="#999"
value={content}
onChangeText={setContent}
multiline
numberOfLines={3}
/>
<TouchableOpacity style={styles.addButton} onPress={handleAddNote}>
<Text style={styles.addButtonText}>添加笔记</Text>
</TouchableOpacity>
</View>
<FlatList
data={notes}
renderItem={renderNote}
keyExtractor={item => item.id}
contentContainerStyle={styles.notesList}
showsVerticalScrollIndicator={false}
ListEmptyComponent={
<View style={styles.emptyContainer}>
<Ionicons name="document-text-outline" size={64} color="#E0E0E0" />
<Text style={styles.emptyText}>还没有笔记</Text>
<Text style={styles.emptySubText}>添加你的第一条笔记</Text>
</View>
}
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5F5F5',
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: 16,
paddingVertical: 16,
backgroundColor: 'white',
borderBottomWidth: 1,
borderBottomColor: '#E0E0E0',
},
backButton: {
padding: 8,
},
title: {
fontSize: 18,
fontWeight: '600',
color: '#333',
},
placeholder: {
width: 40,
},
form: {
backgroundColor: 'white',
padding: 16,
marginBottom: 16,
},
titleInput: {
fontSize: 18,
fontWeight: '600',
color: '#333',
marginBottom: 8,
paddingVertical: 8,
borderBottomWidth: 1,
borderBottomColor: '#E0E0E0',
},
contentInput: {
fontSize: 16,
color: '#333',
marginBottom: 16,
paddingVertical: 8,
minHeight: 80,
},
addButton: {
backgroundColor: '#007AFF',
paddingVertical: 12,
borderRadius: 8,
alignItems: 'center',
},
addButtonText: {
color: 'white',
fontSize: 16,
fontWeight: '600',
},
notesList: {
padding: 16,
},
noteCard: {
backgroundColor: 'white',
padding: 16,
borderRadius: 8,
marginBottom: 12,
elevation: 2,
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 1,
},
shadowOpacity: 0.1,
shadowRadius: 2,
},
noteTitle: {
fontSize: 16,
fontWeight: '600',
color: '#333',
marginBottom: 8,
},
noteContent: {
fontSize: 14,
color: '#666',
marginBottom: 8,
},
noteDate: {
fontSize: 12,
color: '#999',
},
emptyContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
paddingVertical: 80,
},
emptyText: {
fontSize: 16,
color: '#999',
marginTop: 16,
marginBottom: 8,
},
emptySubText: {
fontSize: 14,
color: '#999',
},
});
export default NotesScreen;5.3 创建笔记详情页
js
// src/screens/NoteDetailScreen.js
import React, { useState, useEffect } from 'react';
import { View, Text, StyleSheet, TextInput, TouchableOpacity, Alert } from 'react-native';
import { useRoute, useNavigation } from '@react-navigation/native';
import { Ionicons } from '@expo/vector-icons';
import AsyncStorage from '@react-native-async-storage/async-storage';
const NoteDetailScreen = () => {
const route = useRoute();
const navigation = useNavigation();
const { note } = route.params;
const [title, setTitle] = useState(note.title);
const [content, setContent] = useState(note.content);
const handleSave = async () => {
try {
const notesStr = await AsyncStorage.getItem('notes');
if (notesStr) {
const notes = JSON.parse(notesStr);
const updatedNotes = notes.map(item =>
item.id === note.id
? { ...item, title: title.trim(), content: content.trim() }
: item
);
await AsyncStorage.setItem('notes', JSON.stringify(updatedNotes));
Alert.alert('成功', '笔记已保存');
navigation.goBack();
}
} catch (error) {
console.error('保存笔记失败:', error);
Alert.alert('错误', '保存笔记失败');
}
};
const handleDelete = async () => {
Alert.alert(
'删除笔记',
'确定要删除这篇笔记吗?',
[
{ text: '取消', style: 'cancel' },
{
text: '删除',
style: 'destructive',
onPress: async () => {
try {
const notesStr = await AsyncStorage.getItem('notes');
if (notesStr) {
const notes = JSON.parse(notesStr);
const updatedNotes = notes.filter(item => item.id !== note.id);
await AsyncStorage.setItem('notes', JSON.stringify(updatedNotes));
navigation.goBack();
}
} catch (error) {
console.error('删除笔记失败:', error);
Alert.alert('错误', '删除笔记失败');
}
},
},
],
{ cancelable: true }
);
};
return (
<View style={styles.container}>
<View style={styles.header}>
<TouchableOpacity
style={styles.backButton}
onPress={() => navigation.goBack()}
>
<Ionicons name="arrow-back" size={24} color="#333" />
</TouchableOpacity>
<Text style={styles.title}>编辑笔记</Text>
<TouchableOpacity style={styles.deleteButton} onPress={handleDelete}>
<Ionicons name="trash-outline" size={24} color="#FF3B30" />
</TouchableOpacity>
</View>
<View style={styles.content}>
<TextInput
style={styles.titleInput}
value={title}
onChangeText={setTitle}
placeholder="标题"
placeholderTextColor="#999"
/>
<TextInput
style={styles.contentInput}
value={content}
onChangeText={setContent}
placeholder="内容"
placeholderTextColor="#999"
multiline
textAlignVertical="top"
/>
</View>
<TouchableOpacity style={styles.saveButton} onPress={handleSave}>
<Text style={styles.saveButtonText}>保存</Text>
</TouchableOpacity>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5F5F5',
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: 16,
paddingVertical: 16,
backgroundColor: 'white',
borderBottomWidth: 1,
borderBottomColor: '#E0E0E0',
},
backButton: {
padding: 8,
},
title: {
fontSize: 18,
fontWeight: '600',
color: '#333',
},
deleteButton: {
padding: 8,
},
content: {
flex: 1,
backgroundColor: 'white',
margin: 16,
padding: 16,
borderRadius: 8,
},
titleInput: {
fontSize: 20,
fontWeight: '600',
color: '#333',
marginBottom: 16,
paddingVertical: 8,
borderBottomWidth: 1,
borderBottomColor: '#E0E0E0',
},
contentInput: {
flex: 1,
fontSize: 16,
color: '#333',
paddingVertical: 8,
},
saveButton: {
backgroundColor: '#007AFF',
margin: 16,
paddingVertical: 16,
borderRadius: 8,
alignItems: 'center',
},
saveButtonText: {
color: 'white',
fontSize: 16,
fontWeight: '600',
},
});
export default NoteDetailScreen;6. 更新 App.js
js
// App.js
import React from 'react';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { Provider } from 'react-redux';
import { store } from './src/redux/store';
import AppNavigator from './src/navigation/AppNavigator';
export default function App() {
return (
<Provider store={store}>
<SafeAreaProvider>
<AppNavigator />
</SafeAreaProvider>
</Provider>
);
}7. 总结
通过本章节的学习,我们掌握了如何使用 React Navigation 实现多页面路由,包括:
- 如何创建底部标签导航
- 如何创建主导航栈
- 如何创建首页、工具页面和个人主页
- 如何创建计算器和记事本等工具页面
- 如何实现页面间的导航
这些技能将帮助我们构建更加复杂、功能丰富的 React Native 应用。
8. 下一步
接下来,我们将学习如何实现表单提交功能,进一步增强应用的功能。
