📅  最后修改于: 2023-12-03 14:58:44.504000             🧑  作者: Mango
React Native 是一个跨平台的移动应用开发框架,使用 JavaScript 和 React 构建 Native 应用程序。下面是面向初学者的 5 个令人惊叹的 React Native 项目创意,帮助你学习和掌握这个跨平台的框架。
待办事项应用是一个很好的起点,可以帮助你学习 React Native 的基础知识。你可以创建一个包含列表、添加、删除和更新功能的应用程序。这个项目创意可以帮助你熟悉 React Native 中的组件、状态和事件处理。
import React, { useState } from "react";
import {
SafeAreaView,
StatusBar,
StyleSheet,
Text,
TextInput,
TouchableOpacity,
View,
} from "react-native";
const App = () => {
const [todoItems, setTodoItems] = useState([]);
const [inputText, setInputText] = useState("");
const handleAddTodo = () => {
if (inputText) {
setTodoItems([...todoItems, { text: inputText, completed: false }]);
setInputText("");
}
};
const handleDeleteTodo = (index) => {
const newTodoItems = [...todoItems];
newTodoItems.splice(index, 1);
setTodoItems(newTodoItems);
};
const handleToggleComplete = (index) => {
const newTodoItems = [...todoItems];
newTodoItems[index].completed = !newTodoItems[index].completed;
setTodoItems(newTodoItems);
};
return (
<SafeAreaView style={styles.container}>
<StatusBar barStyle="dark-content" />
<TextInput
style={styles.input}
placeholder="Add a todo item"
value={inputText}
onChangeText={setInputText}
/>
<TouchableOpacity style={styles.addButton} onPress={handleAddTodo}>
<Text style={styles.addButtonText}>Add</Text>
</TouchableOpacity>
{todoItems.map((item, index) => (
<TouchableOpacity
key={index}
style={[
styles.todoItem,
item.completed ? styles.todoItemCompleted : null,
]}
onPress={() => handleToggleComplete(index)}
onLongPress={() => handleDeleteTodo(index)}
>
<Text style={styles.todoText}>{item.text}</Text>
</TouchableOpacity>
))}
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#FFFFFF",
padding: 20,
},
input: {
borderWidth: 1,
borderColor: "#BBBBBB",
borderRadius: 5,
padding: 10,
marginBottom: 10,
},
addButton: {
backgroundColor: "#008CBA",
borderRadius: 5,
padding: 10,
},
addButtonText: {
color: "#FFFFFF",
fontWeight: "bold",
},
todoItem: {
borderWidth: 1,
borderColor: "#BBBBBB",
borderRadius: 5,
padding: 10,
marginBottom: 10,
},
todoText: {
fontSize: 16,
},
todoItemCompleted: {
backgroundColor: "#DDDDDD",
},
});
export default App;
天气应用是一个更复杂的项目创意,它可以帮助你学习如何使用 React Native 的网络请求和组件布局。你可以利用网络 API 获取天气数据,并使用布局组件创建动态 UI。
import React, { useState, useEffect } from "react";
import {
SafeAreaView,
StatusBar,
StyleSheet,
Text,
TextInput,
TouchableOpacity,
View,
} from "react-native";
import { Ionicons } from "@expo/vector-icons";
import { Weather } from "./weather";
const App = () => {
const [loading, setLoading] = useState(true);
const [location, setLocation] = useState("");
const [weather, setWeather] = useState();
const handleGetWeather = () => {
setLoading(true);
fetch(
`https://api.openweathermap.org/data/2.5/weather?q=${location}&appid={YOUR_APP_ID}`
)
.then((response) => response.json())
.then((json) => {
setWeather(json);
setLoading(false);
})
.catch((error) => {
console.error(error);
setLoading(false);
});
};
useEffect(() => {
handleGetWeather();
}, []);
return (
<SafeAreaView style={styles.container}>
<StatusBar barStyle="dark-content" />
<View style={styles.locationContainer}>
<TextInput
style={styles.locationInput}
placeholder="Enter location"
value={location}
onChangeText={setLocation}
onSubmitEditing={handleGetWeather}
/>
<TouchableOpacity onPress={handleGetWeather}>
<Ionicons name="ios-search" size={24} color="black" />
</TouchableOpacity>
</View>
{loading ? (
<Text>Loading...</Text>
) : weather ? (
<Weather data={weather} />
) : (
<Text>No weather data available</Text>
)}
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#FFFFFF",
padding: 20,
},
locationContainer: {
flexDirection: "row",
alignItems: "center",
borderWidth: 1,
borderColor: "#BBBBBB",
borderRadius: 5,
padding: 10,
marginBottom: 20,
},
locationInput: {
flex: 1,
marginRight: 10,
},
});
export default App;
音乐播放器应用是一个介绍如何使用 React Native 组件与原生功能集成的项目创意。你可以探索如何使用 React Native 的 WebView 组件嵌入原生音乐播放器,并将播放列表与 Native 播放器同步。
import React, { useEffect, useRef, useState } from "react";
import {
SafeAreaView,
StatusBar,
StyleSheet,
Text,
TouchableOpacity,
View,
WebView,
} from "react-native";
import { Ionicons } from "@expo/vector-icons";
const playlist = [
{ title: "My Heart Will Go On", artist: "Celine Dion", url: "" },
{ title: "Lose Yourself", artist: "Eminem", url: "" },
{ title: "Shape of You", artist: "Ed Sheeran", url: "" },
];
const App = () => {
const [currentTrack, setCurrentTrack] = useState(0);
const [isPlaying, setIsPlaying] = useState(false);
const webViewRef = useRef();
const handlePlayPause = () => {
webViewRef.current.injectJavaScript(`
document.querySelector('#play-pause-button').click();
`);
setIsPlaying(!isPlaying);
};
const handleNextTrack = () => {
if (currentTrack < playlist.length - 1) {
setCurrentTrack(currentTrack + 1);
} else {
setCurrentTrack(0);
}
setIsPlaying(true);
};
const handlePreviousTrack = () => {
if (currentTrack > 0) {
setCurrentTrack(currentTrack - 1);
} else {
setCurrentTrack(playlist.length - 1);
}
setIsPlaying(true);
};
useEffect(() => {
webViewRef.current.injectJavaScript(`
document.querySelector('#playlist').innerHTML = ${JSON.stringify(
playlist
)};
document.querySelector('#play-pause-button').addEventListener('click', () => {
window.ReactNativeWebView.postMessage(JSON.stringify({
type: 'play-pause',
isPlaying: document.querySelector('#play-pause-button').className.includes('pause'),
trackIndex: ${currentTrack},
}));
});
document.querySelectorAll('.track-link').forEach((link, index) => {
link.addEventListener('click', () => {
window.ReactNativeWebView.postMessage(JSON.stringify({
type: 'select-track',
trackIndex: index,
}));
});
});
`);
}, [currentTrack]);
const handleWebViewMessage = (event) => {
const message = JSON.parse(event.nativeEvent.data);
switch (message.type) {
case "play-pause":
console.log(message.isPlaying, message.trackIndex);
setIsPlaying(message.isPlaying);
break;
case "select-track":
console.log(message.trackIndex);
setCurrentTrack(message.trackIndex);
setIsPlaying(true);
break;
default:
break;
}
};
return (
<SafeAreaView style={styles.container}>
<StatusBar barStyle="dark-content" />
<View style={styles.player}>
<TouchableOpacity onPress={handlePreviousTrack}>
<Ionicons name="ios-skip-backward" size={28} />
</TouchableOpacity>
<TouchableOpacity style={styles.playPauseButton} onPress={handlePlayPause}>
<Ionicons name={isPlaying ? "ios-pause" : "ios-play"} size={48} />
</TouchableOpacity>
<TouchableOpacity onPress={handleNextTrack}>
<Ionicons name="ios-skip-forward" size={28} />
</TouchableOpacity>
</View>
<WebView
ref={webViewRef}
source={{ uri: "https://www.example.com/music-player" }}
onMessage={handleWebViewMessage}
/>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#FFFFFF",
},
player: {
flexDirection: "row",
justifyContent: "center",
alignItems: "center",
padding: 20,
},
playPauseButton: {
marginHorizontal: 40,
},
});
export default App;
图片搜索应用是一个充满乐趣和实际应用的项目创意。你可以使用网络 API 和布局组件创建一个带有搜索和无限滚动搜索结果的图片搜索应用程序。
import React, { useRef, useState } from "react";
import {
ActivityIndicator,
FlatList,
Image,
SafeAreaView,
StatusBar,
StyleSheet,
Text,
TextInput,
TouchableOpacity,
View,
} from "react-native";
const App = () => {
const [searchQuery, setSearchQuery] = useState("");
const [searchResults, setSearchResults] = useState([]);
const [loading, setLoading] = useState(false);
const [page, setPage] = useState(1);
const flatListRef = useRef();
const handleSearch = () => {
if (searchQuery) {
setPage(1);
setLoading(true);
setSearchResults([]);
fetch(
`https://api.unsplash.com/search/photos?query=${searchQuery}&page=${page}&per_page=10&client_id={YOUR_CLIENT_ID}`
)
.then((response) => response.json())
.then((json) => {
setSearchResults(json.results);
setLoading(false);
})
.catch((error) => {
console.error(error);
setLoading(false);
});
}
};
const handleLoadMore = () => {
setPage(page + 1);
setLoading(true);
fetch(
`https://api.unsplash.com/search/photos?query=${searchQuery}&page=${page}&per_page=10&client_id={YOUR_CLIENT_ID}`
)
.then((response) => response.json())
.then((json) => {
setSearchResults([...searchResults, ...json.results]);
setLoading(false);
})
.catch((error) => {
console.error(error);
setLoading(false);
});
};
const renderItem = ({ item }) => (
<View style={styles.imageContainer}>
<Image source={{ uri: item.urls.small }} style={styles.image} />
<Text style={styles.imageTitle}>{item.user.name}</Text>
</View>
);
return (
<SafeAreaView style={styles.container}>
<StatusBar barStyle="dark-content" />
<View style={styles.searchContainer}>
<TextInput
style={styles.searchInput}
placeholder="Search for images"
value={searchQuery}
onChangeText={setSearchQuery}
onSubmitEditing={handleSearch}
/>
<TouchableOpacity style={styles.searchButton} onPress={handleSearch}>
<Text style={styles.searchButtonText}>Search</Text>
</TouchableOpacity>
</View>
{loading ? (
<View style={styles.loadingContainer}>
<ActivityIndicator />
</View>
) : searchResults.length > 0 ? (
<FlatList
ref={flatListRef}
data={searchResults}
keyExtractor={(item, index) => index.toString()}
renderItem={renderItem}
onEndReached={handleLoadMore}
onEndReachedThreshold={0.5}
/>
) : (
<View style={styles.emptyContainer}>
<Text>No results found</Text>
</View>
)}
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#FFFFFF",
padding: 10,
},
searchContainer: {
flexDirection: "row",
alignItems: "center",
borderWidth: 1,
borderColor: "#BBBBBB",
borderRadius: 5,
padding: 10,
marginBottom: 10,
},
searchInput: {
flex: 1,
},
searchButton: {
backgroundColor: "#008CBA",
borderRadius: 5,
padding: 10,
marginLeft: 10,
},
searchButtonText: {
color: "#FFFFFF",
fontWeight: "bold",
},
loadingContainer: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
emptyContainer: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
imageContainer: {
marginVertical: 10,
},
image: {
width: "100%",
height: 200,
},
imageTitle: {
fontSize: 16,
fontWeight: "bold",
marginTop: 5,
},
});
export default App;
手写笔记应用是一个使用 React Native 的 Gesture Responder 和 Canvas 组件的项目创意。你可以创建一个简单的手写笔记应用程序,并让用户使用手指或笔在屏幕上绘图。
import React, { useRef } from "react";
import {
Dimensions,
PanResponder,
SafeAreaView,
StatusBar,
StyleSheet,
View,
} from "react-native";
import { Svg, Path } from "react-native-svg";
const SCREEN_WIDTH = Dimensions.get("window").width;
const SCREEN_HEIGHT = Dimensions.get("window").height;
const App = () => {
const panResponderRef = useRef(
PanResponder.create({
onMoveShouldSetPanResponder: () => true,
onPanResponderMove: (event, gestureState) => {
const { dx, dy } = gestureState;
pathRef.current += `L${dx},${dy} `;
setPath(pathRef.current);
},
onPanResponderRelease: () => {
pathRef.current = `M0,0 `;
setPath(pathRef.current);
},
})
);
const pathRef = useRef("M0,0 ");
const [path, setPath] = useState(pathRef.current);
return (
<SafeAreaView style={styles.container}>
<StatusBar barStyle="dark-content" />
<Svg style={styles.canvas} width={SCREEN_WIDTH} height={SCREEN_HEIGHT}>
<Path d={path} stroke="#000000" strokeWidth="4" fill="none" {...panResponderRef.current.panHandlers} />
</Svg>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#FFFFFF",
},
canvas: {
backgroundColor: "#FFFFFF",
},
});
export default App;