Ⅰ. 둘러보기 개요

둘러보기란 다른 사용자들이 생성한 플레이리스트를 탐색할 수 있는 페이지이다. 사용ㅈ는 다양한 맥락에서 만들어진 플레이리스트를 살펴보며 새로운 음악 취향을 발견할 수 있다.
상단에는 다음과 같은 카테고리 필터가 제공된다.
- 장소 : 카페, 이동 중, 집/실내 등
- 목표 : 활력, 집중, 위로 등
- 데시벨 : 조용함, 보통, 시끄러움 등
사용자는 이 카테고리를 선택하여 원하는 상황에 맞는 플레이리스트를 탐색할 수 있으며, 각 카테고리 안에서도 세부 필터를 통해 플레이리스트를 좁혀볼 수 있다.
또한 둘러보기 중 맘에 드는 플레이리스트를 발견하면 Spotify 딥링크를 통해 바로 재생할 수 있으며, 내 라이브러리레 저장할 수 있는 기능도 있다.
Ⅱ. 둘러보기 필터링 구현

둘러보기 페이지에서 장소', '목표', '소음'을 필터링하기 위해 별도의 바텀시트를 구현하였다.
// 선택 시 콜백을 실행하고 시트를 닫는다.
class ExploreBottomSheetFragment(val onItemSelected: (String) -> Unit) : BottomSheetDialogFragment() {
// ... (생략)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.tvOptionPlace.setOnClickListener { selectItem("장소") }
binding.tvOptionGoal.setOnClickListener { selectItem("목표") }
binding.tvOptionNoise.setOnClickListener { selectItem("소음") }
}
private fun selectItem(name: String) {
onItemSelected(name) // 부모 프래그먼트로 "장소", "목표" 등 전달
dismiss()
}
}
Ⅲ. 메인 탐색 로직(ExploreFragment)
| 둘러보기 페이지 | 스포티파이로 재생하기 |
|---|---|
![]() |
![]() |
1) 카테고리 탭과 드롭다운 연동
유저가 바텀시트에서 '목표'를 선택하면 상단 리사이클러뷰의 내용이 '집중', '휴식' 등으로 즉시 바뀐다.
드롭다운에서 메인 카테고리를 선택하면 updateCategoryTabs()가 호출되며, 해당 카테고리에 맞는 세부 필터 탭이 갱신된다. 이후 선택된 세부 카테고리를 기준으로 서버 API를 호출하여 플레이리스트 목록을 다시 로드한다.
binding.layoutDropdown.setOnClickListener {
val bottomSheet = ExploreBottomSheetFragment { selectedName ->
currentMainCategory = selectedName
binding.tvCurrentCategory.text = selectedName // 드롭다운 텍스트 변경
updateCategoryTabs(selectedName) // 상단 탭(세부 필터) 업데이트
}
bottomSheet.show(parentFragmentManager, "ExploreBottomSheet")
}
private fun updateCategoryTabs(type: String) {
val newTabs = getCategoryList(type)
// 기존에 선택된 탭이 새 리스트에 없으면 첫 번째 항목을 기본 선택
val tabToSelect = if (newTabs.contains(selectedSubCategory)) selectedSubCategory else newTabs[0]
selectedSubCategory = tabToSelect
categoryAdapter.updateData(newTabs, tabToSelect) // 어댑터 갱신
fetchPlaylists(type, tabToSelect) // 실제 데이터 로드 호출
}
2) 카테고리별 API 분기 처리
서버 API가 장소별, 목표별, 소음별로 나누어져 있기 때문에 이를 when문을 통해 분기 처리한다. 또한 UI는 한글이지만 서버는 영문 데이터를 요구하므로 translateToEnglish 함수를 거쳐 요청한다.
private fun fetchPlaylists(type: String, value: String) {
val englishValue = translateToEnglish(value)
val call = when(type) {
"장소" -> RetrofitClient.exploreApi.getExploreByLocation(englishValue)
"소음" -> RetrofitClient.exploreApi.getExploreByDecibel(englishValue)
"목표" -> RetrofitClient.exploreApi.getExploreByGoal(englishValue)
else -> return
}
call.enqueue(object : Callback<ExploreResponse> {
override fun onResponse(call: Call<ExploreResponse>, response: Response<ExploreResponse>) {
if (response.isSuccessful) {
val playlists = response.body()?.data?.playlists ?: emptyList()
playlistAdapter.updateData(playlists.toMutableList())
// 리스트 로드 후 각 아이템의 상세 정보(커버 등)를 가져온다.
fetchDetailsForItems(playlists.toMutableList())
}
}
// ... (생략)
})
}
3) 스크롤 상태 유지
둘러보기 리스트에서 상세 화면으로 갔다가 돌아왔을 때, 유저가 보던 스크롤 위치를 기억해야 한다. 'onSaveInstanceState'를 활용해 스크롤 상태를 저장하고 복구한다.
// 상세 페이지 이동 시 현재 스크롤 위치 저장
onDetailClick = { item ->
playlistScrollState = binding.rvPlaylists.layoutManager?.onSaveInstanceState()
// ... (내비게이션 로직)
}
// 데이터 로드 완료 후 스크롤 복구
private fun updateSinglePlaylist(index: Int, detailData: PlaceDetail, items: MutableList<PlaceDetail>) {
// ... (데이터 업데이트)
if (index == adapterList.size - 1) { // 마지막 아이템까지 로드되면
binding.rvPlaylists.post {
playlistScrollState?.let {
binding.rvPlaylists.layoutManager?.onRestoreInstanceState(it)
playlistScrollState = null // 복구 후 초기화
}
}
}
}
4) 이름 수정 후 저장하기
그대로 저장할 수도 있지만, 나만의 이름으로 바꿔 저장할 수 있게 바텀시트를 제공한다.
둘러보기에서 저장하기는 노래 추천 결과에서 라이브러리에 저장하는 API와 동일하다.
private fun showAddBottomSheet(item: PlaceDetail) {
// ... (바텀시트 설정)
view.findViewById<View>(R.id.btnAddLibrary).setOnClickListener {
val newName = etName.text.toString().trim()
if (newName.isNotEmpty()) {
savePlaylistToLibrary(item.id.toString(), newName) // 서버에 저장 요청
showCustomToast("내 라이브러리에 추가됐어요")
bottomSheetDialog.dismiss()
}
}
}
Ⅳ. 마무리 회고

둘러보기 기능은 팀 프로젝트에서 다른 팀원이 주로 담당했던 부분이라 처음 코드를 이해하는 데에도 시간이 필요했다. 특히 드롭다운을 통해 메인 카테고리를 변경하고, 그에 맞는 세부 카테고리와 API를 동적으로 변경하는 구조가 처음에는 다소 복잡하게 느껴졌다.
또한 UI 구현 측면에서도 생각보다 복잡한 부분이 많았다. 플레이리스트 카드에는 대표 앨범 커버 1개와 보조 앨범 커버 3개를 함께 표시해야 했고, 플레이리스트에 포함된 아티스트 이름들도 동시에 표시해야 했다.
이번 기능을 통해 API 호출, 리스트 갱신, 데이터 처리 등에 대해 깊게 이해할 수 있게 되었다.
👾👉 DIP Android Github
📽️👉 풀시연영상 보러가기
'TAVE-16th' 카테고리의 다른 글
| [DIP/FE] 구글 플레이스토어 앱 출시하기- 14일 비공개 테스트 (0) | 2026.03.08 |
|---|---|
| [DIP/FE] 라이브러리 - Spotify에서 4분할 커버 가져오기 & 플리 삭제하기 (0) | 2026.03.08 |
| [DIP/FE] Playlist 추천 결과 - 리스트형 & 갤러리형 보기(ViewPager2) (0) | 2026.03.08 |
| [DIP/FE] 노래 추천받기(2) - Polling 방식으로 서버 요청 보내기 (0) | 2026.03.08 |
| [DIP/FE] 노래 추천받기(1) - 장소 선택, 데시벨 측정, 목표 설정 (0) | 2026.03.07 |


