
Android Jetpack 라이브러리에 있는 Navigation 을 사용하여 Instagram BottomNavigation 을 따라 해보겠다.

보통 BottomNavigation 을 만드는 경우 하나의 Navigation 에 각 아이템 (카테고리, 검색, 홈, 내정보)의 Fragment 들을 추가한다. 하지만 이렇게 만들면 우리가 원하는 Instagram Style에 맞는 Stack 관리의 어려움을 겪을 것이다.
따라서 해당 BottomNavigation 에 ViewPager를 연결하고 각 Page 당 Navigation 을 생성할 것이다.
MVVM 디자인 패턴을 기반으로 코드를 작성했으며 내용은 큰 흐름을 기반으로 진행될 것이다. 자세한 코드는 github 주소를 남겨두겠다.
참고 페이지
Instagram Style Navigation Using Navigation Component
- Gradle 설정
- Xml 설정
- ViewPager 설정 및 Bottom Menu 연결
- Navigation 설정 ( 다음 챕터에서 )
// Navigation
implementation("androidx.navigation:navigation-fragment-ktx:$nav_version")
implementation("androidx.navigation:navigation-ui-ktx:$nav_version")
implementation("androidx.navigation:navigation-dynamic-features-fragment:$nav_version")
androidTestImplementation("androidx.navigation:navigation-testing:$nav_version")
implementation("androidx.navigation:navigation-compose:$nav_version")
<com.example.shopping.presentation.main.viewpager.NonSwipingViewPager
android:id="@+id/mainViewPager"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/bottomNavigation"/>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottomNavigation"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/mainViewPager"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:labelVisibilityMode="labeled"
app:menu="@menu/bottom_navigation_menu"
app:layout_behavior="tech.thdev.app.view.BottomNavigationBar"/>
private fun initViewpager() = with(binding) {
// setup main view pager
mainViewPager.addOnPageChangeListener(this@MainActivity)
mainViewPager.adapter = ViewPagerAdapter()
mainViewPager.post(this@MainActivity::checkDeepLink)
mainViewPager.offscreenPageLimit = fragments.size
mainViewPager.currentItem = 2
}
ViewPager 와 BottomNavigation 연결은 뷰 페이저의 페이지가 선택 됐을 때 로직과 네비게이션의 아이템이 선택 됐을 때 로직 설정으로 해결 해준다.
순서 :
1. 바텀 메뉴 클릭 ( onNavigationItemSeleted )
2. 프래그먼트 스택 로직 ( viewModel.menuSeleted )
3. viewPager.currentItem 변경 ( setItem )
4. 뷰 페이저 리스너 ( onPageSelected )
verride fun onNavigationItemSelected(item: MenuItem): Boolean {
if (!backPressedChecked) {
val position = indexToPage.values.indexOf(item.itemId)
if(position == 1){
startActivity(SearchActivity.newIntent(this))
return false
}
else viewModel.menuSelected(position)
} else {
backPressedChecked = false
}
return true
}
class MainViewModel: BaseViewModel() {
....
private val backStack = Stack<Int>()
....
fun menuSelected(position: Int) = viewModelScope.launch{
if(backStack.empty()){
_mainStateLiveData.value = MainState.SelectedFailure
}
else{
if (backStack.size > 1) {
if (backStack.contains(position)) {
do {
val state = backStack.peek()
backStack.pop()
} while (position != state)
}
}
_mainStateLiveData.value = MainState.SelectedSuccess(position)
}
}
....
}
private fun handleSelectedSuccess(state: MainState.SelectedSuccess){
if (mainViewPager.currentItem != state.position) setItem(state.position)
}
....
private fun setItem(position: Int) = with(binding) {
mainViewPager.currentItem = position
viewModel.push(position)
}
override fun onPageSelected(page: Int) = with(binding) {
val itemId = indexToPage[page] ?: R.id.home
if (bottomNavigation.selectedItemId != itemId) bottomNavigation.selectedItemId = itemId
}