일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- React
- 정치인
- 데이터분석
- 머신러닝
- 사이드프로젝트
- 강화학습
- coding
- 딥러닝
- Instagrame clone
- 클론코딩
- TeachagleMachine
- expo
- 카트폴
- Ros
- 전국국밥
- ReactNative
- pandas
- selenium
- JavaScript
- kaggle
- 크롤링
- App
- 조코딩
- 리액트네이티브
- FirebaseV9
- redux
- clone coding
- python
- 앱개발
- 강화학습 기초
- Today
- Total
qcoding
7)[사이드프로젝트]App개발-전국국밥_지역별 국밥찾기 페이지 본문
1) 사용되는 구성 npm
- 여기서 해당 페이지는 react-native-maps / react-native-maps-clustering / styled-component/ expo-location 가 주로 사용되었다.
"dependencies": {
"@react-navigation/bottom-tabs": "^6.0.9",
"@react-navigation/drawer": "^6.1.8",
"@react-navigation/native": "^6.0.6",
"@react-navigation/native-stack": "^6.2.5",
"@types/styled-components": "^5.1.19",
"@types/styled-components-react-native": "^5.1.3",
"expo": "~44.0.2",
"expo-app-loading": "~1.3.0",
"expo-location": "~14.0.1",
"expo-status-bar": "~1.2.0",
"expo-web-browser": "~10.1.0",
"react": "17.0.1",
"react-dom": "17.0.1",
"react-native": "0.64.3",
"react-native-gesture-handler": "^2.2.0",
"react-native-map-clustering": "^3.4.2",
"react-native-maps": "^0.29.4",
"react-native-modal": "^13.0.0",
"react-native-push-notification": "^8.1.1",
"react-native-reanimated": "^1.13.3",
"react-native-safe-area-context": "3.3.2",
"react-native-screens": "~3.10.1",
"react-native-splash-screen": "^3.3.0",
"react-native-web": "0.17.1",
"realm": "^10.12.0",
"styled-components": "^5.3.3"
},
2) 구글맵 사용 환경셋팅
-> 구글맵 사용법은 아래의 블로그가 잘 정리되어 있어서 따라하면 좋을 것 같다.
-> 여기서 " 그리고 Gradle 빌드를 해주시면 끝입니다. " 라는 말이 나오는 데, 내 경우에는 따로 Gradle 빌드를 해주지 않아도 적용되었고, 아마 npm start 명령어나 npm run android 명령어 사용 시 자동으로 빌드가 되어서 그런 것 같다.
한가지 중요한 것은 안드로이드 에뮬레이터 사용시에는 아래의 사진에서 Play Store에 표시가 된 경우에만 지도가 표시가 된다. 그렇지 않으면 서비스에 연결할 수 없다는 표시가 나면서 에뮬레이터가 지도가 표시 되지 않는다.
3) expo-location API 사용
-> 앱 사용 시 사용자의 위치를 기준으로 맵을 띄우기 위하여 처음에 사용자 정보를 받아온다. 나는 Create React Native App 을 사용하기 때문에 expo 에서 제공하는 API가 사용가능하다. 만일 react native cli를 사용한다면 동일한 기능을하는 geolocation-serive를 사용하면 된다.
https://dev-yakuza.posstree.com/ko/react-native/react-native-geolocation-service/
4) 페이지 구성
해당 페이지는 전체가 FlatList로 되어있으며, 상단의 지도와 지역선택의 전체가 ListHeaderComponent={} 에 들어가 있으며, stickyHeaderIndices={[0]} 옵션을 주어서 화면에 고정되게 하였다. css상에서 zIndex를 높여서 항상위에 가도록 구성하였다.
FlatList내에서는 각 Item들이 한행에 3개씩 되도록 numColumn={3}으로 변경하여 작성하였다.
// FlatList 코드
<FlatList
// horizontal={true}
contentContainerStyle={{backgroundColor:"white"}}
ref={listRef}
data={food_list[selRegion]}
keyExtractor={(item) => item.diningCode_url + ""}
stickyHeaderIndices={[0]}
ListHeaderComponent={
<View style={{marginTop:0,zIndex:3,backgroundColor:"white"}}>
<View style={{justifyContent:"center"}}>
<MapView
// style={{flex:1,width:"100%",height:"100%"}}
style={styles.map}
clusterColor="#ffaf40"
clusterTextColor="white"
extent={200}
animationEnabled={false}
ref={map_ref}
style={{height:SCREEN_HEIGHT*0.4}}
initialRegion={{
latitude: region.latitude,
longitude: region.longitude,
latitudeDelta: LATITUDE_DELTA*0.1,
longitudeDelta: LONGITUDE_DELTA*0.1,
}}
showsUserLocation={true}
showsMyLocationButton={true}
onRegionChange={() => {
}}
onRegionChangeComplete={() => {
// console.log("지도움직이는것 멈춤");
}}
provider={PROVIDER_GOOGLE}
provider="google">
{food_list[selRegion].map((food, index) => (
<Marker
coordinate={{
latitude: food.lat,
longitude: food.lon,
}}
title={food.name}
description={food.keyword}
// icon={require('../assets/alla.png')}
key={index}
onPress={()=>{
// setRegion({
// name: food.name,
// address: food.address,
// latitude: food.lat,
// longitude: food.lon,
// latitudeDelta: LATITUDE_DELTA*0.1,
// longitudeDelta: LONGITUDE_DELTA*0.1,
// })
}
}>
<Callout onPress={()=>{
navigation.navigate("Stack",{screen:"Detail",params:{item:food}})
}}></Callout>
</Marker>
))}
</MapView>
</View>
<ScrollView horizontal={true} showsHorizontalScrollIndicator={false}
contentContainerStyle={{height:50,alignItems:"center",paddingLeft:7}}
>
{region_list.map((region,index)=>{
// console.log(region,index)
return(
<Region index={index} region_list={region_list} key={index} region={region} selectRegion={selectRegion} setRegion={setRegion}></Region>
)
})}
</ScrollView>
</View>
}
numColumns={3}
columnWrapperStyle={{
justifyContent: "space-between",
}}
ItemSeparatorComponent={() => <View style={{ height: 10}} />}
renderItem={({item}) => {
return(
<FoodComponent setRegion={setRegion} item={item} scrollToTop={scrollToTop}></FoodComponent>
)
}}
>
</FlatList>
5) 기능별 주요 구성
1) 처음 시작 시 사용자 위치받아오기
--> expo location을 통해서 처음 시작 시 gps정보를 받아오고 이정보를 setRegion을 통해서 위도와 경도값을 넣어준다.
아래의 구글맵 로딩시에는 처음 위치를 해당 위치로 렌더링되게 한다.
// expo location
const getLocation=async()=>{
let { status } = await Location.requestForegroundPermissionsAsync();
if (status !== "granted") {
Alert.alert("내 주변 위치 음식점을 찾기위해 위치정보가 필요합니다.");
return;
}
let locationSuccess = false;
while (!locationSuccess) {
try {
console.log("try")
let { coords: { latitude, longitude }} = await Location.getCurrentPositionAsync({
accuracy: Location.Accuracy.High,
});
locationSuccess = true;
console.log("set map loading")
setRegion({
latitude: latitude,
longitude: longitude,
latitudeDelta: LATITUDE_DELTA,
longitudeDelta: LONGITUDE_DELTA,
});
}
catch (ex) {
console.log("retring....");
}
finally {
console.log("finally")
setisloading(false);
// 여기위치 맞나확인할것
SplashScreen.hide();
}
}
const unsubscribe = navigation.addListener('focus', () => {
console.log('focus작동함');
});
return unsubscribe;
}
useEffect(() => {
getLocation();
}, []);
2) 음식점 위치버튼 클릭 시 지도상에서 해당 위치로 이동
-> 이 기능에는 크게 2가지가 있다. 한개는 음식점마다 있는 지도 버튼 클릭 시 지도의 위도 경도가 바뀌는 애니메이션이고, 지금은 사용하지 않지만 맨처음 구성 시 listHeaderComponent를 위에다가 고정하지 않았을 때 전체 스크롤이 내려가 있는 상태에서 지도 화면으로 이동을 위해 스크롤이 올라가는 애니메이션이다.
전체적인 구성은 1) ref로 컴포넌트를 선택 2) ref.currnet 값이 존재할 때 지도를 이동하거나 , 스크롤을 맨 위로 올라가게 애니메이션을 적용하였다.
// 특정 컴포넌트를 지정하기 위해 ref 사용
const listRef = useRef(null);
const map_ref = useRef(null);
// scroll to top FlatList를 움직이는 함수
// 지도 클릭 시 맨위로 화면을 올려서 지도 위치에 맞게 보여줌
const scrollToTop=()=>listRef.current.scrollToOffset({ offset: 0, animated: true });
// 지도 움직이는 함수
const moveMapView = () => {
map_ref.current.animateToRegion(region, 1000);
};
// 지도 animation
if (map_ref.current) {
moveMapView();
}
// ref 적용
<FlatList
ref={listRef} />
// ref 적용
<MapView
ref={map_ref}
6) 전체코드
◆Food.js
-> 위에서 적용되어 있는 클러스터링을 사용하려면 <MapView> 컴포넌트를 사요할 때 'react-native-maps'가 아닌 'react-native-clustering"에서 import하면 된다. 클러스터 색이나 크기등은 공식문서를 통해 찾아가면서 변경하면된다.
import React,{useState,useContext,useEffect,useRef} from 'react'
import MapView ,{ PROVIDER_GOOGLE } from "react-native-map-clustering";
// import MapView ,{ PROVIDER_GOOGLE } from 'react-native-maps'
import { Marker, Callout, Circle} from "react-native-maps";
import { View, FlatList,StyleSheet,ScrollView,ActivityIndicator} from 'react-native'
import styled from 'styled-components/native';
import { DBContext } from './../Context';
import { Asset,useAssets } from 'expo-asset';
import Region from '../components/Region';
import {SCREEN_HEIGHT,LATITUDE_DELTA,LONGITUDE_DELTA} from '../util'
import FoodComponent from '../components/FoodComponent';
import * as Location from 'expo-location';
import SplashScreen from 'react-native-splash-screen'
const Food = ({navigation}) => {
// realm에서 자료 받아오기
const {customData:food_list}=useContext(DBContext);
const region_list=Object.keys(food_list);
const [isloading, setisloading] = useState(true);
const [selRegion,setSelRegion]=useState(`${region_list[0]}`);
const [region, setRegion] = useState({
name: "장소를 선택해주세요",
address: "장소를 선택해주세요",
latitude: 37.4822971,
longitude: 126.9030901,
latitudeDelta: LATITUDE_DELTA*0.1,
longitudeDelta: LONGITUDE_DELTA*0.1,
});
const listRef = useRef(null);
const map_ref = useRef(null);
// expo location
const getLocation=async()=>{
let { status } = await Location.requestForegroundPermissionsAsync();
if (status !== "granted") {
Alert.alert("내 주변 위치 음식점을 찾기위해 위치정보가 필요합니다.");
return;
}
let locationSuccess = false;
while (!locationSuccess) {
try {
console.log("try")
let { coords: { latitude, longitude }} = await Location.getCurrentPositionAsync({
accuracy: Location.Accuracy.High,
});
locationSuccess = true;
console.log("set map loading")
setRegion({
latitude: latitude,
longitude: longitude,
latitudeDelta: LATITUDE_DELTA,
longitudeDelta: LONGITUDE_DELTA,
});
}
catch (ex) {
console.log("retring....");
}
finally {
console.log("finally")
setisloading(false);
// 여기위치 맞나확인할것
SplashScreen.hide();
}
}
const unsubscribe = navigation.addListener('focus', () => {
console.log('focus작동함');
});
return unsubscribe;
}
useEffect(() => {
getLocation();
}, []);
// scroll to top FlatList를 움직이는 함수
// 지도 클릭 시 맨위로 화면을 올려서 지도 위치에 맞게 보여줌
const scrollToTop=()=>listRef.current.scrollToOffset({ offset: 0, animated: true });
const selectRegion=(region)=>{
return(
setSelRegion(region)
)
}
// 지도 움직이는 함수
const moveMapView = () => {
map_ref.current.animateToRegion(region, 1000);
};
// 지도 animation
if (map_ref.current) {
moveMapView();
}
return ( isloading?
<View style={{flex:1,justifyContent:"center",alignItems:"center"}}>
<ActivityIndicator size="large" color="blue"></ActivityIndicator>
</View>:<>
<FlatList
// horizontal={true}
contentContainerStyle={{backgroundColor:"white"}}
ref={listRef}
data={food_list[selRegion]}
keyExtractor={(item) => item.diningCode_url + ""}
stickyHeaderIndices={[0]}
ListHeaderComponent={
<View style={{marginTop:0,zIndex:3,backgroundColor:"white"}}>
<View style={{justifyContent:"center"}}>
<MapView
// style={{flex:1,width:"100%",height:"100%"}}
style={styles.map}
clusterColor="#ffaf40"
clusterTextColor="white"
extent={200}
animationEnabled={false}
ref={map_ref}
style={{height:SCREEN_HEIGHT*0.4}}
initialRegion={{
latitude: region.latitude,
longitude: region.longitude,
latitudeDelta: LATITUDE_DELTA*0.1,
longitudeDelta: LONGITUDE_DELTA*0.1,
}}
showsUserLocation={true}
showsMyLocationButton={true}
onRegionChange={() => {
}}
onRegionChangeComplete={() => {
// console.log("지도움직이는것 멈춤");
}}
provider={PROVIDER_GOOGLE}
provider="google">
{food_list[selRegion].map((food, index) => (
<Marker
coordinate={{
latitude: food.lat,
longitude: food.lon,
}}
title={food.name}
description={food.keyword}
// icon={require('../assets/alla.png')}
key={index}
onPress={()=>{
// setRegion({
// name: food.name,
// address: food.address,
// latitude: food.lat,
// longitude: food.lon,
// latitudeDelta: LATITUDE_DELTA*0.1,
// longitudeDelta: LONGITUDE_DELTA*0.1,
// })
}
}>
<Callout onPress={()=>{
navigation.navigate("Stack",{screen:"Detail",params:{item:food}})
}}></Callout>
</Marker>
))}
</MapView>
</View>
<ScrollView horizontal={true} showsHorizontalScrollIndicator={false}
contentContainerStyle={{height:50,alignItems:"center",paddingLeft:7}}
>
{region_list.map((region,index)=>{
// console.log(region,index)
return(
<Region index={index} region_list={region_list} key={index} region={region} selectRegion={selectRegion} setRegion={setRegion}></Region>
)
})}
</ScrollView>
</View>
}
numColumns={3}
columnWrapperStyle={{
justifyContent: "space-between",
}}
ItemSeparatorComponent={() => <View style={{ height: 10}} />}
renderItem={({item}) => {
return(
<FoodComponent setRegion={setRegion} item={item} scrollToTop={scrollToTop}></FoodComponent>
)
}}
>
</FlatList>
</>
)
}
const styles = StyleSheet.create({
map: {
...StyleSheet.absoluteFillObject,
height:'100%',
},
});
export default Food
◆FoodComponent.js ( FlatList의 renderItem에 사용되는 component )
옆에 보이는 그림이 FoodComponent이며 터치시에 아래의 지도이동 아이콘과 Detail 페이지 이동 아이콘이 접었다가 펴지면서 생기고,
1) 지도페이지 클릭 시 props로 받은 setRegion에 위도,경도,이름, 주소 등을 넣게 되고 위의 ref를 통해 지도상으로 이동한다.
2) Detail 페이지 이동 아이콘 클릭 시에는 navigation.navigate("Stack",{screen:"Detail",params:{item}})
} 를 통해 Stack Navigator에 있는 Detail 페이지로 이동하며 params를 통해 item 정보를 보내게 된다.
import React,{useState} from 'react'
import styled from 'styled-components/native';
import { FontAwesome } from '@expo/vector-icons';
import {LATITUDE_DELTA,LONGITUDE_DELTA} from '../util';
import { useNavigation } from '@react-navigation/native';
const FoodListContainer=styled.TouchableOpacity`
flex:0.31;
align-items:center;
justify-content:center;
padding:7px 5px;
background-color:rgba(200,200,200,0.4);
border-radius:20px;
/* border-width:2px;
border-color:red; */
`;
const FoodTitleText=styled.Text`
font-size:15px;
font-weight:900;
color:black;
`;
const FoodImage=styled.Image`
width:60px;
height:60px;
border-radius:30px;
`;
const FoodKeyword=styled.Text`
font-size:10px;
font-weight:200;
color:gray;
`;
const FoodScore=styled.Text`
font-size:10px;
font-weight:200;
color:gray;
`
const FoodBtnContainer=styled.View`
width:75px;
justify-content:space-between;
align-items:center;
flex-direction:row;
padding:5px 5px;
`;
const FoodBtn=styled.TouchableOpacity``;
const FoodComponent=({item,setRegion,scrollToTop})=>{
const [detail,setDetail]=useState(false)
// detail page로 가기 위한 navigation
const navigation=useNavigation();
const goToDetail=()=>{
navigation.navigate("Stack",{screen:"Detail",params:{item}})
}
return(
<FoodListContainer onPress={()=>{setDetail(prev=>!prev)}}>
<FoodImage source={{uri:item.img_url}} ></FoodImage>
<FoodTitleText>{item.name.length>8?`${item.name.slice(0,7)}...`:`${item.name}`}</FoodTitleText>
<FoodScore>{item.score>=90?`⭐⭐⭐⭐⭐`:item.score>=80?`⭐⭐⭐⭐`:item.score>=70?`⭐⭐⭐`:item.score>=60?`⭐⭐`:`⭐`}</FoodScore>
<FoodKeyword>
{item.recommend_food.length>8?`${item.recommend_food.slice(0,8)}...`:`${item.recommend_food}`}
</FoodKeyword>
{detail&&
<>
<FoodKeyword>{item.address.length>10?`${item.address.slice(0,11)}...`:`${item.address}`}</FoodKeyword>
<FoodBtnContainer>
<FoodBtn onPress={()=>{
return(
// scrollToTop(),
setRegion({
name: item.name,
address: item.address,
latitude: item.lat,
longitude: item.lon,
latitudeDelta: LATITUDE_DELTA*0.05,
longitudeDelta: LONGITUDE_DELTA*0.05,
})
)
}}>
<FontAwesome name="map-marker" size={35} color="#c0392b" />
</FoodBtn>
<FoodBtn onPress={()=>{
goToDetail();
}}>
<FontAwesome name="info-circle" size={35} color="#2980b9" />
</FoodBtn>
</FoodBtnContainer>
</>}
</FoodListContainer>
)
}
export default FoodComponent;
'[사이드 프로젝트]App개발 - 전국 국밥 찾기' 카테고리의 다른 글
9)[사이드프로젝트]App개발_전국국밥-내기할래 페이지 (0) | 2022.02.06 |
---|---|
8)[사이드프로젝트]App개발-전국국밥_realm를 사용한 list 저장 페이지 (2) | 2022.02.06 |
6) [사이드 프로젝트]App개발-전국국밥찾기 -> Drawer Navigator (0) | 2022.02.06 |
5) ReactNative를 통한 App개발 진행 상황 (0) | 2022.01.15 |
4) Excel 파일로 JSON 형태 변경 및 Firebase 데이터 저장 (0) | 2022.01.14 |