본문 바로가기

Android

[Android] BottomNavigation의 이전 버전과 최신버전

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 등에 효율적으로 처리한다.
    • 간편성과 유지보수