AndroidX nagivation과 Material 라이브러리가 도입되기 전과 후의 Bottom Navigation을 다뤄보았다.
최신 BottomNavigation
- build.gradle.kts(:app)
implementation("com.google.android.material:material:1.12.0")
implementation("androidx.navigation:navigation-ui-ktx:2.7.7")
implementation("androidx.navigation:navigation-fragment-ktx:2.7.7")
implementation("androidx.navigation:navigation-runtime-ktx:2.7.7")
- res/menu/nav_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/fragment_home"
android:icon="@drawable/ic_launcher_background"
android:title="@string/nav_menu_home" />
<item
android:id="@+id/fragment_map"
android:icon="@drawable/ic_launcher_background"
android:title="@string/nav_menu_map" />
<item
android:id="@+id/fragment_setting"
android:icon="@drawable/ic_launcher_background"
android:title="@string/nav_menu_setting" />
</menu>
- res/navigation/nav_graph.xml
코드를 보면 대략 미리 BottomNavigation에 사용할 fragment들을 설정해놓은 것을 볼 수 있고,
각각 Fragment마다 연결된 Kotlin파일(android:name)과 xml파일(app:layout)도 미리 설정되어있는 것을 볼 수 있다.
'app:startDestination'을 통해 초기 fragment화면을 무엇으로 할지 선언이 가능하다.
<?xml version="1.0" encoding="utf-8"?>
<navigation
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/nav_graph"
app:startDestination="@id/fragment_home">
<fragment
android:id="@+id/fragment_home"
android:name="com.cavss.artravel.ui.view.screen.home.HomeFragment"
app:layout="@id/fragment_home"
app:title="@string/nav_menu_home" /> <!-- 네비게이션 바에 표시되는 프래그먼트 제목 -->
<fragment
android:id="@+id/fragment_map"
android:name="com.cavss.artravel.ui.view.screen.map.MapFragment"
app:layout="@id/fragment_map"
app:title="@string/nav_menu_map" />
<fragment
android:id="@+id/fragment_setting"
android:name="com.cavss.artravel.ui.view.screen.setting.SettingFragment"
app:layout="@id/fragment_setting"
app:title="@string/nav_menu_setting" />
</navigation>
- res/layout/activity_main.xml
화면 상부에는 FragmentContainerView를 사용하였고,
화면 하부에는 이전과 동일하게 BottomNavigationView를 사용하였다.
달라진 점은 이전에는 FrameLayout을 통해 Fragment들을 화면전환 하였는데, 이번에는FragmentcontainerView를 사용하여 화면전환을 한다는 것이다.
FragmentContainerView에서는 'app:navGraph'를 통해 화면에 나타날 fragment들을 미리 설정가능하다.
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragmnet"
android:layout_width="match_parent"
android:layout_height="0dp"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:name="androidx.navigation.fragment.NavHostFragment"
app:layout_constraintBottom_toTopOf="@id/bottom_navigation"/>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_navigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:itemIconTint="@color/white"
app:itemTextColor="@color/white"
app:itemActiveIndicatorStyle="@android:color/transparent"
app:labelVisibilityMode="selected"
app:menu="@menu/nav_menu"
app:layout_constraintEnd_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
- res/layout/fragment_home.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
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"
xmlns:app="http://schemas.android.com/apk/res-auto">
<TextView
android:id="@+id/movetothird_frag"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:textSize="20dp"
android:text="home fragment" />
</androidx.constraintlayout.widget.ConstraintLayout>
- res/layout/fragment_map.xml
- res/layout/fragment_setting.xml
"fragment_map.xml"과 "fragment_setting.xml"의 구성은 "fragment_home.xml"과 같아서 생략한다.
-MainActivity.kt
- NavHostFragment
- Navigation Component 아키텍쳐
- Fragment를 호스팅해서 Navigatio Graph를 표시하는 등 관리함.
- 일반적으로 FragmentContainerView 내에 배치되어 사용되어 Fragment간의 이동을 처리함
- FragmentContainerView
- Android Jetpack
- HavHostFragment와 같이 Fragment를 호스팅하는 뷰이며 XML 레이아웃 파일에서 Fragment를 정의하고 화면에 표시함.
- NavHostFragment와 달리 Fragment간의 이동이나 관리 기능을 갖지 않음.
- NavController
- 이전에는 FragmentActivity의 supportFragmentManager를 통하여 beginTransaction()과 commit()으로 Fragment간의 화면이동을 했다면 이제는 NavController의 'navController.navigate()'를 이용하여 Fragment간의 화면이동을 진행한다.
- NavController는 NavhostFragment와 같이 사용된다.
private lateinit var bottomNavigationView: BottomNavigationView
private lateinit var navHostFragment: NavHostFragment
private lateinit var navController: NavController
private fun setBottomNavigation(){
try{
// 하단 네비게이션 설정
bottomNavigationView = findViewById(R.id.bottom_navigation)
navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragmnet) as NavHostFragment
navController = navHostFragment.navController
NavigationUI.setupWithNavController(
bottomNavigationView,navController
)
bottomNavigationView.setOnItemSelectedListener {item ->
when(item.itemId) {
R.id.fragment_home -> {
if (navController.currentDestination?.id != R.id.fragment_home) {
navController.navigate(R.id.fragment_home)
}
true
}
R.id.fragment_map -> {
if (navController.currentDestination?.id != R.id.fragment_map) {
navController.navigate(R.id.fragment_map)
}
true
}
R.id.fragment_setting -> {
if (navController.currentDestination?.id != R.id.fragment_setting) {
navController.navigate(R.id.fragment_setting)
}
true
}
else -> false
}
}
}catch (e:Exception){
Log.e("mException", "MainActivity, setBottomNavigation // Exception : ${e.localizedMessage}")
}
}
그렇다면 이전 BottomNavigation은 어떻게 했을까?
- res/menu/bottomnavi_menu.xml
최신 버전과 이전 버전의 'menu' xml파일은 동일하다
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:icon="@drawable/aaaaa"
android:title="@string/aaaaa"/>
<item
android:icon="@drawable/bbbbb"
android:title="@string/bbbbbb"/>
</menu>
- res/layout/activity_main.xml
최신버전에서는 FragmentContainerView를 사용했지만, 이전버전에서는 FrameLayout을 사용하는 모습을 볼 수 있다.
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/main">
<FrameLayout
android:id="@+id/main_frame"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@id/bottom_navi_container"/>
<LinearLayout
android:id="@+id/bottom_navi_container"
android:layout_alignParentBottom="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/main_bottom_navi"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:menu="@menu/bottomnavi_menu"/>
</LinearLayout>
</RelativeLayout>
- res/layout/fragment_one.xml
이전과 최신버전의 fragment의 xml파일은 동일하다.
- MainActivity.kt
이전 버전에서는 주로 beginTransaction()과 commit()으로 화면전환을 하는 것을 볼 수 있다.
private fun setBottomNavigation(bottomNavi : BottomNavigationView){
val manager = (this as FragmentActivity).supportFragmentManager.beginTransaction()
bottomNavi.apply {
setOnItemSelectedListener { menuItem ->
when (menuItem.title) {
getString(R.string.aaaaa) -> {
manager.replace(binding.mainFrame.id, aFragment).commit()
true
}
getString(R.string.bbbbb) -> {
manager.replace(binding.mainFrame.id, bFragment).commit()
true
}
}
}
}
}
FrameLayout + beginTransaction() + commit() 방식과 FragmentContainerView + NavHostFragment + NavController 방식은 어떻게 다르지?
내게 있어서 선생님은 ChatGPT, Gemini 뿐이라 검색해보니 다음과 같은 차이가 있었다.
- FrameLayout
- 프래그먼트의 생성 및 제거 등을 직접 해야하기에 실수로 불필요한 Fragment 메모리를 유지할 가능성이 있다
- 높은 제어 방식
- FragmentContainerView
- Navigation라이브러리가 Fragment 생명주기를 관리하여 필요한 Fragment만 메모리에 유지
- Fragment 교체 시 Animation 등에 효율적으로 처리한다.
- 간편성과 유지보수
'Android' 카테고리의 다른 글
[Android] HashMap은 삽입 순서를 보장해줄까? (0) | 2025.01.17 |
---|---|
[Android] Osmdroid, 오픈소스 Map 라이브러리 사용 (0) | 2024.05.27 |
[Android] API 키 숨기기 (Giraffe 버전과 그 이전 버전) + Manifest사용 (0) | 2024.05.24 |
[Android] AES 암호화 (0) | 2023.07.06 |
[Android] 당신은 RecyclerView를 제대로 사용하고 있나? (0) | 2023.03.08 |