[Android] Instagram Style Navigation 구현 (1)

JunHyeong Lee·2022년 6월 19일

Android

목록 보기
2/4
post-thumbnail

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

보통 BottomNavigation 을 만드는 경우 하나의 Navigation 에 각 아이템 (카테고리, 검색, 홈, 내정보)의 Fragment 들을 추가한다. 하지만 이렇게 만들면 우리가 원하는 Instagram Style에 맞는 Stack 관리의 어려움을 겪을 것이다.

따라서 해당 BottomNavigation 에 ViewPager를 연결하고 각 Page 당 Navigation 을 생성할 것이다.

MVVM 디자인 패턴을 기반으로 코드를 작성했으며 내용은 큰 흐름을 기반으로 진행될 것이다. 자세한 코드는 github 주소를 남겨두겠다.

참고 페이지
Instagram Style Navigation Using Navigation Component

  1. Gradle 설정
  2. Xml 설정
  3. ViewPager 설정 및 Bottom Menu 연결
  4. Navigation 설정 ( 다음 챕터에서 )

1. Gradle

// 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")

2. Xml

  • activity_main.xml
  • BottomNavigation 과 ViewPager
<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"/>

3. ViewPager 설정 및 Bottom Menu 연결

  • ViewPager 초기화
 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 )

  • onNavigationItemSeleted
    - BackPressed 시 아이템이 선택됐을 때를 구분하기 위해 backPressedChecked 변수 사용
    - position 1 일 때는 새로운 검색창을 띄움
    - menuSelected 에서 stack 로직 처리
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
    }
  • menuSelected
    - 전에 아이템을 클릭했던 기록이 있다면 그 전까지 모두 삭제 ( 중복 해결 )

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)
          }
      }
      
  ....
}
  • setItem
    - viewPager 의 현재 페이지를 바꾸고 바뀔 페이지를 push 한다.
 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)
}
  • addOnPageChangeListener
    - 바뀐 페이지가 아이템의 Id와 다르면 아이템을 선택한다.
override fun onPageSelected(page: Int) = with(binding) {

        val itemId = indexToPage[page] ?: R.id.home
        if (bottomNavigation.selectedItemId != itemId) bottomNavigation.selectedItemId = itemId
    }
profile
Android Developer

0개의 댓글