BottomNavigationView와 Navigation Graph를 같이 쓰는 경우 아파치 소프트웨어 제단에서 만든 NavigationExtensions 클래스를 이용해서 구현하길 권장하고 있다. 그 이유는 시작점이 되는 Fragment에서 액티비티를 Finish 할 때 앱이 터진다고 하는데, 아직은 경험해보지 못해서 정확히는 모르겠고... 일단 권장하는 방식대로 앱을 구현해보자
NavigationExtensions 파일은 아래에서 다운 받아준다.
BottomNavigationView에서 사용할 만큼 Fragment를 생성해주자.
res 디렉터리에 navigation 디렉터리를 생성한다. 그 후에 Navigation Resource File을 생성하면 아래와 같은 메시지가 뜨는데, OK를 클릭한다.
그럼 자동으로 아래 라이브러리를 sync 및 build 해주는데, 이렇게 되면 아래에서 사용할 FragmentContainerView를 별다른 작업 없이 사용할 수 있다.
implementation 'androidx.navigation:navigation-ui:2.3.3'
이제 각 각 필요한 만큼 navigation 파일을 생성해주고 아래처럼 각 각의 파일에 시작할 프래그먼트를 지정해준다.
이제 다시 res 디렉터리에 menu 디렉터리를 생성하고 bottom_nav.xml을 생성한다. 이때 주의할 점은 menu 아이템들에 아이디 값과 navigation의 아이디 값이 같아야 한다는 거다. 이거 안 해주면 아래와 같은 에러가 뜨는데, 한 시간 가량 헤맸다...
이거와
이게 같아야 한다.
위에서 언급한 것처럼 Navigation Graph를 쓰기 위해 FragmentContainerView를 사용할 거고 BottomNavigationView에는 bottom_nav를 붙이고 labelVisibilityMode="labeled"를 통해 menu의 title을 선택하지 않아도 보일 수 있게 만들어준다.
activity_main.xml
<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=".ui.act.MainAct">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_container"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_nav"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
app:labelVisibilityMode="labeled"
app:menu="@menu/bottom_nav" />
</LinearLayout>
MainAct.kt
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import androidx.navigation.NavController
import com.google.android.material.bottomnavigation.BottomNavigationView
import com.yawtseb.bestway.R
import com.yawtseb.bestway.util.AnimationHelper.Companion.bottomPushDownAnimation
import com.yawtseb.bestway.util.AnimationHelper.Companion.bottomPushUpAnimation
import com.yawtseb.bestway.util.ShowMsg.Companion.showLog
import com.yawtseb.bestway.util.setupWithNavController
class MainAct : AppCompatActivity() {
private val showBottomNavFrags = arrayListOf<Int>(R.id.homeFrag, R.id.dietFrag, R.id.mealTicketFrag, R.id.userFrag)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if(savedInstanceState == null) setUpBottomNavigationBar()
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
setUpBottomNavigationBar()
}
private fun setUpBottomNavigationBar(){
bottomNavigationView = findViewById(R.id.bottom_nav)
val navGraphIds = listOf(R.navigation.navigation_home, R.navigation.navigation_diet, R.navigation.navigation_meal_ticket, R.navigation.navigation_user)
val controller = bottomNavigationView.setupWithNavController(
navGraphIds, supportFragmentManager, R.id.nav_host_container, intent
)
controller.observe(this, Observer { navController ->
navController.addOnDestinationChangedListener { _, destination, _ ->
if(showBottomNavFrags.contains(destination.id)) bottomNavigationView.visibility = View.VISIBLE
else bottomNavigationView.visibility = View.GONE
}
})
}
companion object{
private lateinit var bottomNavigationView: BottomNavigationView
fun showBottomNav(){
bottomPushUpAnimation(bottomNavigationView)
bottomNavigationView.visibility = View.VISIBLE
}
fun hideBottomNav(){
bottomPushDownAnimation(bottomNavigationView)
bottomNavigationView.visibility = View.GONE
}
}
}
navigation 파일들을 리스트로 묶어서 선언해주고 NavigationExtenstions.kt 파일의 setupWithNavController()에 각 파라미터 타입에 맞는 객체들을 넣어 controller를 생성한다.
val navGraphIds = listOf(R.navigation.navigation_home, R.navigation.navigation_diet,
R.navigation.navigation_meal_ticket, R.navigation.navigation_user
)
val controller = bottomNavigationView.setupWithNavController(
navGraphIds, supportFragmentManager, R.id.nav_host_container, intent
)
conroller는 NavController를 담고 있는 LiveData타입으로 lifecycler의 Observer를 사용해 NavController가 변할때 마다 감지해서 NavController를 반환해준다. 이때 Observer를 통해 navController에 리스너를 붙이고 BottomNavigationView를 상황에 맞게 컨트롤 해준다.
controller.observe(this, Observer { navController ->
navController.addOnDestinationChangedListener { _, destination, _ ->
if(showBottomNavFrags.contains(destination.id)) bottomNavigationView.visibility = View.VISIBLE
else bottomNavigationView.visibility = View.GONE
}
})
위에서 설명한 것처럼 showBottomNavFrags 리스트를 통해 BottomNavigationView의 보임 유무를 동적으로 처리하게 구현하였다. 이 리스트는 destination id가 showBottomNavFrags에 포함되는지 확인하여 처리하게 구현하였다.
private val showBottomNavFrags = arrayListOf<Int>(R.id.homeFrag, R.id.dietFrag, R.id.mealTicketFrag, R.id.userFrag)
마지막으로 Activity가 재시작될 때 상태값을 유지하기 위해서 아래와 같은 코드를 추가했다.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if(savedInstanceState == null) setUpBottomNavigationBar()
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
setUpBottomNavigationBar()
}
만약 Appbar, Navigation Graph, BottomNavigationView를 전부 사용하려면 onSupportNavigateUp()을 이용해서 아래와 같이 선언해주고 Observer에서 상황에 맞게 Appbar를 수정해주면 된다.
override fun onSupportNavigateUp(): Boolean {
return currentNavController?.value?.navigateUp() ?: false
}
아래는 참조한 사이트로 설명이 부족한 부분은 각 각의 페이지에서 보충하면 될거 같다.
참조한 사이트
'개발 > Android' 카테고리의 다른 글
[Android] 화면 크기 별 Layout 생성 (0) | 2021.03.05 |
---|---|
[Android] Serialize, Parcelable, Parcelize 정리 (0) | 2021.03.05 |
화면 회전에도 상태 값 유지하기 (0) | 2021.03.05 |
Parameter 'directory' is not a directory 에러 (0) | 2021.02.23 |
TODO 리스트 앱 (0) | 2021.02.15 |