네이버 맵 api 를 이용하여 지도를 메인으로 숙소 목록을 서버에서 받아와 마커로 나타내고 하단에 viewPager를 이용해 좌우로 스크롤이 가능한 페이저를 두어 숙소를 살필 수 있게 하는 앱을 만든다.
주요 기능
- 네이버 맵 api 사용해 지도 보여주기
- Mock api에서 예약 가능한 숙소 목록 받아와 지도에 표시
- BottomSheetView를 활용해 예약 가능한 숙소 목록을 인터렉션하게 표시
- viewPager2 를 이용해 현재 보고 있는 숙소를 표시
- 숙소 버튼을 눌로 현재 보고 있는 숙소를 앱 외부로 공유
사용 기술
- Naver Map Api
- ViewPager2
- CoordinatorLayout
- BottomSheetBehavior
- Retrofit
- Glide
- Mocky
네이버 지도 api 사용하기
회원가입이 필요하며 결제수단을 등록해야만 네이버 맵을 사용할 수 있는 기능이 표시되기 때문에 꼭 결제수단을 등록해야 한다. 또한, 프로젝트 패키지명을 등록해주야 정상적으로 SDK를 받아올 수 있다.
https://www.ncloud.com/product/applicationService/maps
NAVER CLOUD PLATFORM
cloud computing services for corporations, IaaS, PaaS, SaaS, with Global region and Security Technology Certification
www.ncloud.com
settings.gradle
repositories {
maven {
url 'https://naver.jfrog.io/artifactory/maven/'
}
}
build.gradle
// 네이버 지도 sdk
implementation 'com.naver.maps:map-sdk:3.11.0'
// 위치 권한
implementation 'com.google.android.gms:play-services-location:18.0.0'
// 레트로핏
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
// 글라이드
implementation 'com.github.bumptech.glide:glide:4.12.0'
앱 수준 gradle 에서 앞으로 사용할 레트로핏과 글라이드, 현재 위치 정보를 사용하기 위한 구글 서비스도 추가해주었다.
레이아웃 구성
레이아웃은 가장 상단에 네이버 mapView가 위치하며, 그 아래에 viewPager2 를 이용해 지도에 나타난 숙소를 좌우로 스크롤할 수 있는 view, 그 아래엔 bottomSheetView를 두어 스크롤 하여 숙소목록을 자세히 볼 수 있도록 구성했다.
bottomSheetView의 경우 다른 xml로 뺀 뒤 activity_layout.xml 에 include 하였고, 네이버 맵의 현재 위치 기능을 사용하기 위해 LocationButtonView 를 추가했다.
예약한 숙소 목록 보여주기
layout/activity_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.naver.maps.map.MapView
android:id="@+id/mapView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="80dp" />
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/houseViewPager"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_gravity="bottom"
android:layout_marginBottom="120dp"
android:orientation="horizontal" />
<com.naver.maps.map.widget.LocationButtonView
android:id="@+id/currentLocationButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|start"
android:layout_margin="12dp" />
<include layout="@layout/bottom_sheet" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
bottomSheetView 의 속성으로 layout_behavior, behavior_peekHeight 를 설정해 아래에서 위로 스크롤 하여 당기면 100dp만큼 올라올 수 있도록 설정해주었다.
layout/bottom_sheet.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/top_radius_white_background"
app:behavior_peekHeight="100dp"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
<View <!-- 스와이프가 가능하다는 표시 -->
android:layout_width="30dp"
android:layout_height="3dp"
android:layout_marginTop="12dp"
android:background="#cccccc"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/bottomSheetTitleTextView"
android:layout_width="0dp"
android:layout_height="100dp"
android:layout_marginTop="16dp"
android:gravity="center"
android:text="여러개의 숙소"
android:textColor="@color/black"
android:textSize="15sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/lineView"
android:layout_width="0dp"
android:layout_height="1dp"
android:background="#cccccc"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/bottomSheetTitleTextView" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/lineView"
app:layout_constraintVertical_bias="1.0" />
</androidx.constraintlayout.widget.ConstraintLayout>
drawable/top_radius_white_background.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/white" />
<corners
android:topLeftRadius="30dp"
android:topRightRadius="30dp" />
</shape>
지도 띄우기
mainActivity
class MainActivity : AppCompatActivity() {
private val mapView: MapView by lazy {
findViewById<MapView>(R.id.mapView)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mapView.onCreate(savedInstanceState)
}
override fun onStart() {
super.onStart()
mapView.onStart()
}
override fun onRestart() {
super.onRestart()
mapView.onResume()
}
override fun onPause() {
super.onPause()
mapView.onPause()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
mapView.onSaveInstanceState(outState)
}
override fun onStop() {
super.onStop()
mapView.onStop()
}
override fun onDestroy() {
super.onDestroy()
mapView.onDestroy()
}
override fun onLowMemory() { // 메모리가 별로 없을 때 호출되는 함수
super.onLowMemory()
mapView.onLowMemory()
}
}
values/api_key.xml
<resources>
<string name="naver_map_client_id">io5obwevrd</string>
</resources>
앞서 만든 application 에서 인증정보/Client ID 을 작성해준다.
manifest/
<meta-data
android:name="com.naver.maps.map.CLIENT_ID"
android:value="@string/naver_map_client_id"/>
위와 같이 코드들을 작성하면 인증정보가 제대로 들어갔기 때문에 네이버 지도가 화면에 잘 뜬다.
Map 불러오기
class MainActivity : AppCompatActivity(), OnMapReadyCallback {
private lateinit var locationSource: FusedLocationSource
private lateinit var naverMap: NaverMap
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mapView.onCreate(savedInstanceState)
mapView.getMapAsync(this)
}
override fun onMapReady(map: NaverMap) {
naverMap = map
// 줌 범위 설정
naverMap.maxZoom = 10.0
naverMap.minZoom = 10.0
// 현재 보여지는 지도 위치
val cameraUpdate = CameraUpdate.scrollTo(LatLng(37.497885, 127.027512))
naverMap.moveCamera(cameraUpdate)
// 현재 위치 버튼
val uiSetting = naverMap.uiSettings
uiSetting.isLocationButtonEnabled = true
// 위치 권한 요청
locationSource = FusedLocationSource(this@MainActivity, LOCATION_PERMISSION_REQUEST_CODE)
naverMap.locationSource = locationSource
}
companion object {
private const val LOCATION_PERMISSION_REQUEST_CODE = 1000
}
}
getMapAsync 로 맵을 불러오며 메인 액티비티에 OnMapReadyCallback 을 걸어주고 this로 넘겨준다.
FusedLocationSource 를 사용해 현위치를 사용할 수 있도록 했다. 권한을 요청할 때 사용되는 요청코드를 따로 상수로 빼서 1000으로 지정했다.
- CameraUpdate : 현재 보여지고 있는 지도의 위치를 변경할 수 있음
- uiSettingSource : 현재 위치를 사용할 수 있음. (값을 true 로 주면 현위치 버튼이 활성화 됨)
값을 true 로 줘도 버튼 눌리지 않는다. 위치 권한이 필요하기 때문이다. 팝업을 띄워 동의를 받는 play-services
location 라이브러리를 사용해 구현해줬다.
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.INTERNET"/>
레트로핏도 사용할거라 인터넷 권한도 미리 추가해주었다.
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode != LOCATION_PERMISSION_REQUEST_CODE) {
return
}
if (locationSource.onRequestPermissionsResult(requestCode, permissions, grantResults)) {
if (!locationSource.isActivated) {
// 권한 설정 거부시 위치 추적 사용하지 않음
naverMap.locationTrackingMode = LocationTrackingMode.None
}
return
}
}
이후 onRequestPermissionResult 에서 해당 코드로 오는 결과를 걸러 현위치가 activated 되지 않았다면 위치 추적을 사용하지 않도록 설정했다.
Mocky 사용해 서버에서 json 받아와 Retrofit 을 이용해 서버에서 가져온 예약 가능한 목록 보여주기
mocky 를 사용해 작성해둔 json 파일을 서버에서 받아올 수 있게 해줬다.
items에 리스트로 각 항목은 id, title, price, lat, lng, imgUrl 을 가지고 있는 json 형식이다.
이미지url 의 경우 picsum 을 이용해 이미지 주소를 받아와 작성해주었다. 이후 해당 json을 가지고 레트로핏을 통해 받아와 처리해주도록 하겠다.
HouseService.kt
import retrofit2.Call
import retrofit2.http.GET
interface HouseService {
@GET("/v3/511c37d3-79c1-455f-9efb-98b5d594e640")
fun getHouseList(): Call<HouseDto>
}
HouseDto.kt
data class HouseDto(
val items: List<HouseModel>
)
HouseModel.kt
data class HouseModel(
val id: Int,
val title: String,
val price: String,
val lat: Double,
val lng: Double,
val imgUrl: String
)
retrofit 을 사용하기 위해 서비스 인터페이스를 정의해준다. 베이스 url을 제외한 url 주소를 get에 설정해주고 받아올 데이터 클래스 형식으로 dto로 넘겨 call해주는 getHouseList 함수를 정의했다.
데이터 클래스는 json 내부의 키를 바탕으로 작성해주었고, items가 HouseModel 리스트를 들고 있는 구조이다. 이미지의 경우 glide로 뿌려줄 것이다.
mainActivity
private fun getHouseListFromAPI() {
val retrofit = Retrofit.Builder()
.baseUrl("https://run.mocky.io")
.addConverterFactory(GsonConverterFactory.create())
.build()
retrofit.create(HouseService::class.java).also {
it.getHouseList()
.enqueue(object : Callback<HouseDto> {
override fun onResponse(call: Call<HouseDto>, response: Response<HouseDto>) {
if (response.isSuccessful.not()) {
// 실패 처리에 대한 구현
return
}
// 성공한 경우
response.body()?.let { dto ->
updateMarker(dto.items)
viewPagerAdapter.submitList(dto.items) // 새 리스트로 갱신
recyclerAdapter.submitList(dto.items)
bottomSheetTitleTextView.text = "${dto.items.size}개의 숙소"
}
}
override fun onFailure(call: Call<HouseDto>, t: Throwable) {
// 실패 처리에 대한 구현
}
})
}
}
getHouseListFromAPI 함수를 만들고 retrofit 을 사용한다.
baseUrl 은 mocky 주소를 베이스로 한다.
응답을 성공적으로 받아온 경우 body를 검사해서 dto로 받아와 처리해주도록 하고, List<HouseModel> 을 받아와 마커를 추가하고 뷰페이저, 리사이클러뷰에 각각 뿌려지도록 해주었다. (마커, 뷰페이저, 리사이클러뷰 추후 생성)
지도에 마커 표시하기
private fun updateMarker(houses: List<HouseModel>) {
houses.forEach { house ->
val marker = Marker()
marker.position = LatLng(house.lat, house.lng)
marker.map = naverMap
marker.tag = house.id
marker.icon = MarkerIcons.BLACK
marker.iconTintColor = Color.BLUE
}
}
숙소 목록을 받아왔다면 Marker() 를 사용해 지도 위에 마커를 표시할 수 있게 한다.
지도 위에 예약 가능한 숙소 목록 띄우기
숙소 목록을 띄우기 위해 viewPager 를 사용해 지도에 나타난 숙소를 좌우로 스크롤할 수 있게 한다.
위에서 이미 메인 레이아웃에 작성해두었다.
layout/item_house_detail_for_viewpager.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView 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:layout_marginStart="30dp"
android:layout_marginEnd="30dp"
android:background="@color/white"
app:cardCornerRadius="16dp"
tools:layout_height="100dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/thumbnailIamgeView"
android:layout_width="100dp"
android:layout_height="100dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/titleTextView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="12dp"
android:maxLines="2"
android:textColor="@color/black"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/thumbnailIamgeView"
app:layout_constraintTop_toTopOf="parent"
tools:text="강남역 최저가!!강남역 최저가!!강남역 최저가!!강남역 최저가강남역 최저가강남역 최저가강남역 최저가강남역 최저가" />
<TextView
android:id="@+id/priceTextView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:maxLines="1"
android:textColor="@color/black"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/thumbnailIamgeView"
app:layout_constraintTop_toBottomOf="@id/titleTextView"
tools:text="23,000원" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
HouseViewPagerAdapter 에 붙이기 위해 썸네일, 제목, 가격을 구성하는 레이아웃을 생성하였다.
HouseViewPagerAdapter.kt
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
class HouseViewPagerAdapter(val itemClicked: (HouseModel) -> Unit) :
ListAdapter<HouseModel, HouseViewPagerAdapter.ItemViewHolder>(differ) {
inner class ItemViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
fun bind(houseModel: HouseModel) {
val titleTextView = view.findViewById<TextView>(R.id.titleTextView)
val priceTextView = view.findViewById<TextView>(R.id.priceTextView)
val thumbnailImageView = view.findViewById<ImageView>(R.id.thumbnailIamgeView)
titleTextView.text = houseModel.title
priceTextView.text = houseModel.price
view.setOnClickListener {
itemClicked(houseModel)
}
Glide
.with(thumbnailImageView.context)
.load(houseModel.imgUrl)
.into(thumbnailImageView)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
val inflater = LayoutInflater.from(parent.context)
return ItemViewHolder(
inflater.inflate(
R.layout.item_house_detail_for_viewpager,
parent,
false
)
)
}
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
holder.bind(currentList[position])
}
companion object {
val differ = object : DiffUtil.ItemCallback<HouseModel>() {
override fun areItemsTheSame(oldItem: HouseModel, newItem: HouseModel): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: HouseModel, newItem: HouseModel): Boolean {
return oldItem == newItem
}
}
}
}
class MainActivity : AppCompatActivity(), OnMapReadyCallback {
private val viewPager: ViewPager2 by lazy {
findViewById(R.id.houseViewPager)
}
private val viewPagerAdapter = HouseViewPagerAdapter()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewPager.adapter = viewPagerAdapter
}
ViewPager2 를 이용해 하단에 위치한 ViewPager 에 뷰를 inflate 해서 아이템을 보여주고 메인 액티비티로 돌아가
viewPager를 가져와 onCreate() 에서 연결시켜준다.
bottomSheet에 예약 가능한 숙소 목록 띄우기
아래에 있는 bottomSheetView를 스와이프 하여 예약 가능한 숙소의 목록이 올라오도록 하는 코드를 구현할 것이다.
item_house.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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="wrap_content">
<ImageView
android:id="@+id/thumbnailImageView"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="24dp"
app:layout_constraintDimensionRatio="3:2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/titleTextView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="24dp"
android:textColor="@color/black"
android:textSize="20sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/thumbnailImageView"
tools:text="강남역!! 최저가!!!" />
<TextView
android:id="@+id/priceTextView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="24dp"
android:textColor="@color/black"
android:textSize="20sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/titleTextView"
tools:text="24,000원" />
</androidx.constraintlayout.widget.ConstraintLayout>
HouseListAdapter.kt
import android.content.Context
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.bumptech.glide.load.resource.bitmap.CenterCrop
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
class HouseListAdapter : ListAdapter<HouseModel, HouseListAdapter.ItemViewHolder>(differ) {
inner class ItemViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
fun bind(houseModel: HouseModel) {
val titleTextView = view.findViewById<TextView>(R.id.titleTextView)
val priceTextView = view.findViewById<TextView>(R.id.priceTextView)
val thumbnailImageView = view.findViewById<ImageView>(R.id.thumbnailImageView)
titleTextView.text = houseModel.title
priceTextView.text = houseModel.price
Glide
.with(thumbnailImageView.context)
.load(houseModel.imgUrl)
.transform(CenterCrop(), RoundedCorners(dpToPx(thumbnailImageView.context, 12)))
.into(thumbnailImageView)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
val inflater = LayoutInflater.from(parent.context)
return ItemViewHolder(inflater.inflate(R.layout.item_house, parent, false))
}
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
holder.bind(currentList[position])
}
private fun dpToPx(context: Context, dp: Int): Int {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
dp.toFloat(),
context.resources.displayMetrics
).toInt()
}
companion object {
val differ = object : DiffUtil.ItemCallback<HouseModel>() {
override fun areItemsTheSame(oldItem: HouseModel, newItem: HouseModel): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: HouseModel, newItem: HouseModel): Boolean {
return oldItem == newItem
}
}
}
}
200*200 사이즈의 이미지를 가져왔기 때문에 목록에서 보일 때 정사각형으로 뜨게 된다. 2:3 크기로 보여주기 위해 glide에서 설정 값을 추가해주었다.
transform() 을 통해 이미지를 변형시키고, roundCorners 를 통해 모서리 부분을 라운드로 바꿔주었다.
roundCorners 는 픽셀 단위로 설정해줘야 하기 때문에 dp를 px로 변환해주는 dpToPx() 함수를 만들어주었다.
bottom_sheet.xml 에 작성해둔 recyclerView를 가져와 메인 액티비티에 onCreate() 에 뷰를 연결시켜준다.
viewPager.adapter = viewPagerAdapter
recyclerView.adapter = recyclerAdapter
recyclerView.layoutManager = LinearLayoutManager(this)
네이버 로고와 현재 위치 버튼 보이기
위에서 uiSetting.isLocationButtonEnabled = true 로 주었던 것을 수정해준다.
앱을 실행하면 네이버 로고와 현재 위치 버튼이 bottomSheetView 에 가려 보이지 않게 되기 때문에 메인 레이아웃의 mapView 에서 marginBottom 값을 80dp로 주어 로고가 보이도록 하고 LocationButtonView를 추가하여 네이버 맵의 현재 위치 기능을 사용할 수 있게 하였다.
<com.naver.maps.map.widget.LocationButtonView
android:id="@+id/currentLocationButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|start"
android:layout_margin="12dp" />
위에서 uiSetting.isLocationButtonEnabled = true 로 주었던 것을 false로 수정해주고 현재 위치 버튼을 map으로 가져와 naverMap으로 연결시켜준다.
// 현재 위치 버튼
val uiSetting = naverMap.uiSettings
uiSetting.isLocationButtonEnabled = false
currentLocationButton.map = naverMap
ViewPager 와 Marker 연결
viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
val selectedHouseModel = viewPagerAdapter.currentList[position]
val cameraUpdate = CameraUpdate.scrollTo(LatLng(selectedHouseModel.lat, selectedHouseModel.lng))
.animate(CameraAnimation.Easing)
naverMap.moveCamera(cameraUpdate)
}
})
viewPager 와 Marker 를 연결시켜 해당 뷰페이저를 클릭했을 때 마커가 있는 곳으로 카메라가 이동하는 기능을 구현하였다.
private fun updateMarker(house: List<HouseModel>) {
house.forEach { house ->
val marker = Marker()
marker.onClickListener = this
마커에 클릭 리스너를 추가해준다.
class MainActivity : AppCompatActivity(), OnMapReadyCallback, Overlay.OnClickListener {
// 지도 marker 클릭 시
override fun onClick(overlay: Overlay): Boolean {
overlay.tag // overlay : 마커의 총집합, tag : id 값
// firstOrNull : 먼저 나오는 아이템 반환, 없으면 Null 반환
val selectedModel = viewPagerAdapter.currentList.firstOrNull{
it.id == overlay.tag
}
selectedModel?.let {
// selectedModel이 널이 아닌 경우 포지션을 찾았다면 포지션의 위치 이동
val position = viewPagerAdapter.currentList.indexOf(it) // 포지션 값을 indexOf 로 찾음
viewPager.currentItem = position
}
return true
}
viewPager 와 Marker 를 연결시켜 마커를 클릭했을 때 해당 위치의 뷰페이저가 뜰 수 있도록 기능을 구현하였다.
메인 액티비티에서 Overlay.OnClickListener 를 구현하여 마커에 클릭 리스너로 this를 전달해주었다. 마커를 클릭하면 뷰페이저의 리스트에서 해당 아이디를 갖는 숙소를 찾은 뒤 해당 숙소를 현재의 아이템 포지션으로 지정해주도록 하였다.
공유하기
class HouseViewPagerAdapter(val itemClicked: (HouseModel) -> Unit): ListAdapter<HouseModel, HouseViewPagerAdapter.ItemViewHolder>(diffUtil) {
private val viewPagerAdapter = HouseViewPagerAdapter(itemClicked = {
val intent = Intent()
.apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_TEXT, "[지금 이 가격에 예약하세요!] ${it.title} ${it.price} 사진보기 : ${it.imgUrl}")
type = "text/plain"
}
startActivity(Intent.createChooser(intent, null))
})
뷰페이저의 항목이 클릭된 경우 chooser 를 통해 다른 앱으로 공유할 수 있도록 하는 함수를 하나 정의해주고, 해당 함수를 클릭 리스너로 달아주면 putExtra 로 설정된 텍스트가 공유할 수 있도록 작동한다.
완성 화면
'project' 카테고리의 다른 글
중고거래 앱 (bottomNavigationView, Firebase, Fragment) (0) | 2022.01.14 |
---|---|
틴더 앱 (Firebase, Swipe Animation 라이브러리) (1) | 2022.01.12 |
음악 스트리밍 앱 (ExoPlayer, Retrofit, androidx.contraintLayout.widget.Group) (0) | 2022.01.10 |
유튜브 (MotionLayout, ExoPlayer, Retrofit, Mocky) (0) | 2022.01.06 |