[Android/Flutter 교육] 41일차

MSU·2024년 2월 26일

Android-Flutter

목록 보기
44/85
post-thumbnail

LBS 앱

Location-Based Service
위치 기반 서비스

Google Map
구글 클라우드 계정 준비

구글 클라우드는 주로 데이터를 제공하는 서비스

구글 Open API를 이용할 예정

구글 지도 사용 등록

https://console.cloud.google.com/

왼쪽 상단에 "프로젝트 선택" 을 누르기

기존에 프로젝트를 만들어둔게 있다면 프로젝트명이 표시된다.

상단의 새프로젝트를 눌러서 새 프로젝트 만들기

프로젝트 이름은 적당히 만든다.(추후 수정 불가)
위치는 조직 없음 그대로 두면 된다.

만들기 버튼을 누르면 정상적으로 새 프로젝트가 생성된 것을 확인

오른쪽 메뉴에서
API 및 서비스 > 라이브러리 클릭

많은 카테고리 중 지도 선택

Maps SDK for Android 선택

사용 버튼 클릭

결제 창이 나오는데 무료로 사용가능하므로 결제 등록을 해도 실제 결제가 되지는 않음

결제 계정 만들면 아래와 같이 나오는 답변 제출

설문조사 제출 후 API키가 발급되므로 키는 복사해서 잘 보관해두기

API 및 서비스에서 사용자 인증 정보 클릭

Maps API Key 클릭

애플리케이션 제한사항 설정에서 안드로이드 클릭

안드로이드 제한사항에 추가

앱 패키지 네임은 프로젝트 만들때 설정한 패키지 이름으로 넣어준다.

안드로이드 스튜디오의 터미널에서 gradlew signingReport를 입력 후 ctrl + enter를 누르면 아래와 같이 뜨는데 이 중 SHA1 값을 복사해서

완료를 누르면 추가가 된다.

마지막으로 저장해준다.

AndroidManifest.xml에 아래 내용 추가

        <meta-data android:name="com.google.android.geo.API_KEY"
            android:value="API 키값" />
    </application>

	<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>

</manifest>

tools > SDK Manager 클릭

SDK Tools 탭에서 Google Play Services 체크

build.gradle.kts (Module :app)에 내용 추가

dependencies {

    implementation("androidx.core:core-ktx:1.12.0")
    implementation("androidx.appcompat:appcompat:1.6.1")
    implementation("com.google.android.material:material:1.11.0")
    implementation("androidx.constraintlayout:constraintlayout:2.1.4")
    testImplementation("junit:junit:4.13.2")
    androidTestImplementation("androidx.test.ext:junit:1.1.5")
    androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")

    implementation("com.google.android.gms:play-services-maps:18.2.0")
    implementation("com.google.android.gms:play-services-location:21.1.0")
    
}

activity_main.xml 작성
툴바와 프래그먼트 추가

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <com.google.android.material.appbar.MaterialToolbar
        android:id="@+id/toolbarMap"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="?attr/colorPrimary"
        android:minHeight="?attr/actionBarSize"
        android:theme="?attr/actionBarTheme" />

    <fragment
        android:id="@+id/map_fragment"
        android:name="com.google.android.gms.maps.SupportMapFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

툴바에 설정할 Menu 리소스 파일 추가

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:id="@+id/main_menu_location"
        android:icon="@android:drawable/ic_menu_mylocation"
        android:title="현재위치"
        app:showAsAction="always" />
</menu>

MainActivity.kt 작성

class MainActivity : AppCompatActivity() {

    lateinit var activityMainBinding: ActivityMainBinding
    
    // 위치 정보를 관리하는 객체
    lateinit var locationManager: LocationManager
    // 위치 측정이 성공하면 동작할 리스너
    var gpsLocationListener : MyLocationListener? = null
    var networkLocationListener : MyLocationListener? = null
    
    // 구글 지도 객체를 담을 프로퍼티
    lateinit var mainGoogleMap:GoogleMap

    // 확인할 권한 목록
    val permissionList = arrayOf(
        Manifest.permission.ACCESS_FINE_LOCATION,
        Manifest.permission.ACCESS_COARSE_LOCATION
    )

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        MapsInitializer.initialize(this , MapsInitializer.Renderer.LATEST, null)

        activityMainBinding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(activityMainBinding.root)

        // 권한을 확인 받는다.
        requestPermissions(permissionList, 0)

        settingToolbar()
    }

    // 툴바 설정
    fun settingToolbar(){
        activityMainBinding.apply {
            toolbarMap.apply {
                // 타이틀
                title = "구글 지도"
                // 메뉴
                inflateMenu(R.menu.main_menu)
            }
        }
    }
    
    fun settingGoogleMap(){
        // MapFragment 객체를 가져온다.
        val supportMapFragment = supportFragmentManager.findFragmentById(R.id.map_fragment) as SupportMapFragment
        // 리스너를 설정한다.
        // 구글 지도 사용이 완료되면 동작한다.
        supportMapFragment.getMapAsync {
            // Snackbar.make(activityMainBinding.root, "구글 지도가 나타났습니다", Snackbar.LENGTH_SHORT).show()
            // 위치 정보를 관리하는 객체를 가지고 온다.
            locationManager = getSystemService(LOCATION_SERVICE) as LocationManager
            // 위치 정보 수집에 성공하면 동작할 리스너
            myLocationListener = MyLocationListener()
        }
    }

}

구글 지도 셋팅 메서드

    // 구글 지도 셋팅
    fun settingGoogleMap(){
        // MapFragment 객체를 가져온다.
        val supportMapFragment = supportFragmentManager.findFragmentById(R.id.map_fragment) as SupportMapFragment
        // 리스너를 설정한다.
        // 구글 지도 사용이 완료되면 동작한다.
        supportMapFragment.getMapAsync {
            // Snackbar.make(activityMainBinding.root, "구글 지도가 나타났습니다", Snackbar.LENGTH_SHORT).show()

            // 구글 지도 객체를 담아준다.
            mainGoogleMap = it
            
            // 위치 정보를 관리하는 객체를 가지고 온다.
            locationManager = getSystemService(LOCATION_SERVICE) as LocationManager
            // 위치 정보 수집에 성공하면 동작할 리스너
            // myLocationListener = MyLocationListener()

            // 단말기에 저장되어 있는 위치값을 가져온다.
            // 위치정보 사용 권한 허용 여부 확인
            val a1 = ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
            val a2 = ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED

            // 모든 권한이 허용되어 있다면
            if(a1 && a2){
                // 현재 위치를 가져오는동안 시간이 걸리기 때문에
                // 저장되어 있는 위치값을 가져온다.(없으면 null이 반환된다.)
                val location1 = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
                val location2 = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER)

                // 현재 위치를 지도에 표시한다.
                if(location1 != null){
                    setMyLocation(location1)
                } else if(location2 != null){
                    setMyLocation(location2)
                }
                // 저장된 위치값이 없다면 위의 코드는 실행 안되고 바로 현재 위치를 가져오게 됨

                // 현재 위치를 측정한다.
                getMyLocation()
            }
        }
    }

현재 위치를 가져오는 메서드

    // 현재 위치를 가져오는 메서드
    fun getMyLocation(){
        // 위치정보 사용 권한 허용 여부 확인
        val a1 = ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_DENIED
        val a2 = ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_DENIED

        if(a1 || a2){
            return
        }

        // GPS 프로바이더 사용이 가능하다면
        if(locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) == true){
            // 첫 번째 : 사용할 프로바이더
            // 두 번째 : 리스너의 메서드가 호출 될 시간 주기(ms). 0을 넣어주면 최대한 짧은 시간 주기
            // 세 번째 : 리스너의 메서드가 호출 될 거리 주기(m). 0을 넣어주면 최대한 짧은 거리 주기
            if(gpsLocationListener == null) {
                gpsLocationListener = MyLocationListener()
                locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0.0f, gpsLocationListener!!)
            }
        }
        // Network 프로바이더 사용이 가능하다면
        if(locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER) == true){
            if(networkLocationListener == null) {
                networkLocationListener = MyLocationListener()
                locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0.0f, networkLocationListener!!)
            }
        }
    }

위치 측정이 성공하면 동작하는 리스너

    // 위치 측정이 성공하면 동작하는 리스너
    inner class MyLocationListener() : LocationListener{
        // 위치가 측정되면 호출되는 메서드
        // location : 측정된 위치 정보가 담긴 객체
        override fun onLocationChanged(location: Location) {
            // 사용한 위치 정보 프로바이더로 분기한다.
            when(location.provider){
                // GPS 프로바이더 라면
                LocationManager.GPS_PROVIDER -> {
                    // GPS 리스너 연결을 해제해 준다.
                    locationManager.removeUpdates(gpsLocationListener!!)
                    gpsLocationListener = null
                }
                // NetworkProvider 라면
                LocationManager.NETWORK_PROVIDER -> {
                    // 네트워크 리스너 연결을 해제해 준다.
                    locationManager.removeUpdates(networkLocationListener!!)
                    networkLocationListener = null
                }
            }

            // 측정된 위치로 지도를 움직인다.
            setMyLocation(location)
        }
    }

지도의 위치를 설정하는 메서드

    // 지도의 위치를 설정하는 메서드
    fun setMyLocation(location:Location){
        // 위도와 경도를 출력한다.
        Snackbar.make(activityMainBinding.root, "provider : ${location.provider}, 위도 : ${location.latitude}, 경도 : ${location.longitude}",
            Snackbar.LENGTH_SHORT).show()

        // 위도와 경도를 관리하는 객체를 생성한다.
        val userLocation = LatLng(location.latitude, location.longitude)

        // 지도를 이동시키기 위한 객체를 생성한다.
        // 첫 번째 : 표시할 지도의 위치(위도 경도)
        // 두 번째 : 줌 배율
        val cameraUpdate = CameraUpdateFactory.newLatLngZoom(userLocation, 15.0f)

        // 카메라를 이동시킨다.
        // mainGoogleMap.moveCamera(cameraUpdate)
        mainGoogleMap.animateCamera(cameraUpdate)
    }

확대/축소 버튼

// 구글 지도 셋팅
    fun settingGoogleMap(){
        // MapFragment 객체를 가져온다.
        val supportMapFragment = supportFragmentManager.findFragmentById(R.id.map_fragment) as SupportMapFragment
        // 리스너를 설정한다.
        // 구글 지도 사용이 완료되면 동작한다.
        supportMapFragment.getMapAsync {
            // Snackbar.make(activityMainBinding.root, "구글 지도가 나타났습니다", Snackbar.LENGTH_SHORT).show()

            // 구글 지도 객체를 담아준다.
            mainGoogleMap = it

            // 확대 축소 버튼
            mainGoogleMap.uiSettings.isZoomControlsEnabled = true

오른쪽 하단에 버튼이 생성된다.

현재 위치 표시 및 현재 위치 이동 버튼

    // 구글 지도 셋팅
    fun settingGoogleMap(){
        // MapFragment 객체를 가져온다.
        val supportMapFragment = supportFragmentManager.findFragmentById(R.id.map_fragment) as SupportMapFragment
        // 리스너를 설정한다.
        // 구글 지도 사용이 완료되면 동작한다.
        supportMapFragment.getMapAsync {
            // Snackbar.make(activityMainBinding.root, "구글 지도가 나타났습니다", Snackbar.LENGTH_SHORT).show()

            // 구글 지도 객체를 담아준다.
            mainGoogleMap = it

            // 확대 축소 버튼
            mainGoogleMap.uiSettings.isZoomControlsEnabled = true

            // 현재 위치로 이동시키는 버튼을 둔다.
            mainGoogleMap.isMyLocationEnabled = true

맵에 현재 위치를 나타내는 아이콘과 오른쪽 상단에 버튼이 생성된다.
현재 위치를 새롭게 측정하는게 아니라 이미 측정해서 저장된 위치값을 가지고 맵을 이동시키는 것임

오른쪽 상단에 현재 위치로 이동하는 버튼을 없애고자 한다면 아래와 같이 셋팅해주면 된다.

			// 현재 위치
            mainGoogleMap.isMyLocationEnabled = true
            // isMyLocationEnabled에 true를 넣어주면 현재 위치를 표시하는 것 뿐만 아니라
            // 현재 위치로 이동하는 버튼도 표시된다.
            // 이 버튼을 제거하겠다면
            mainGoogleMap.uiSettings.isMyLocationButtonEnabled = false

맵 타입

    // 구글 지도 셋팅
    fun settingGoogleMap(){
        // MapFragment 객체를 가져온다.
        val supportMapFragment = supportFragmentManager.findFragmentById(R.id.map_fragment) as SupportMapFragment
        // 리스너를 설정한다.
        // 구글 지도 사용이 완료되면 동작한다.
        supportMapFragment.getMapAsync {
            // Snackbar.make(activityMainBinding.root, "구글 지도가 나타났습니다", Snackbar.LENGTH_SHORT).show()

            // 구글 지도 객체를 담아준다.
            mainGoogleMap = it

            // 확대 축소 버튼
            mainGoogleMap.uiSettings.isZoomControlsEnabled = true

            // 현재 위치로 이동시키는 버튼을 둔다.
            mainGoogleMap.isMyLocationEnabled = true
            // isMyLocationEnabled에 true를 넣어주면 현재 위치를 표시하는 것 뿐만 아니라
            // 현재 위치로 이동하는 버튼도 표시된다.
            // 이 버튼을 제거하겠다면
            mainGoogleMap.uiSettings.isMyLocationButtonEnabled = false

            // 지도의 타입
            // 데이터 사용량 : NONE < NORMAL < TERRAIN < SATELLITE < HYBRID
            // 지도 뜨는 속도 : HYBRID > SATELLITE > TERRAIN > NORMAL > NONE
            // mainGoogleMap.mapType = GoogleMap.MAP_TYPE_NONE
            // 기본
            mainGoogleMap.mapType = GoogleMap.MAP_TYPE_NORMAL
            // mainGoogleMap.mapType = GoogleMap.MAP_TYPE_TERRAIN
            // mainGoogleMap.mapType = GoogleMap.MAP_TYPE_SATELLITE
            // mainGoogleMap.mapType = GoogleMap.MAP_TYPE_HYBRID

NONE

mainGoogleMap.mapType = GoogleMap.MAP_TYPE_NONE 

NORMAL

mainGoogleMap.mapType = GoogleMap.MAP_TYPE_NORMAL 

TERRAIN

mainGoogleMap.mapType = GoogleMap.MAP_TYPE_TERRAIN 

SATELLITE

mainGoogleMap.mapType = GoogleMap.MAP_TYPE_SATELLITE 

HYBRID

mainGoogleMap.mapType = GoogleMap.MAP_TYPE_HYBRID 

데이터 사용량

NONE < NORMAL < TERRAIN < SATELLITE < HYBRID

지도 뜨는 속도

HYBRID > SATELLITE > TERRAIN > NORMAL > NONE

마커 찍기

	// 현재 사용자 위치를 표시하기 위한 마커
    var myMarker:Marker? = null
     


    // 지도의 위치를 설정하는 메서드
    fun setMyLocation(location:Location){
        // 위도와 경도를 출력한다.
        Snackbar.make(activityMainBinding.root, "provider : ${location.provider}, 위도 : ${location.latitude}, 경도 : ${location.longitude}",
            Snackbar.LENGTH_SHORT).show()

        // 위도와 경도를 관리하는 객체를 생성한다.
        val userLocation = LatLng(location.latitude, location.longitude)

        // 지도를 이동시키기 위한 객체를 생성한다.
        // 첫 번째 : 표시할 지도의 위치(위도 경도)
        // 두 번째 : 줌 배율
        val cameraUpdate = CameraUpdateFactory.newLatLngZoom(userLocation, 15.0f)

        // 카메라를 이동시킨다.
        // mainGoogleMap.moveCamera(cameraUpdate)
        mainGoogleMap.animateCamera(cameraUpdate)

        // 현재 위치를 담은 마커를 생성한다.
        val markerOptions = MarkerOptions()
        markerOptions.position(userLocation)

        // 이미 마커가 있다면 제거한다.
        if(myMarker != null){
            myMarker?.remove()
            myMarker = null
        }

        // 마커를 지도에 표시해준다.
        myMarker = mainGoogleMap.addMarker(markerOptions)
    }

마커 이미지 변경

        // 마커의 이미지를 변경
        val markerBitmap = BitmapDescriptorFactory.fromResource(R.drawable.my_location)
        markerOptions.icon(markerBitmap)

        // 마커를 지도에 표시해준다.
        myMarker = mainGoogleMap.addMarker(markerOptions)
    }




※ 출처 : 멋쟁이사자 앱스쿨 2기, 소프트캠퍼스 
profile
안드로이드공부

0개의 댓글