📌 Intro
카카오톡이나 인스타그램처럼 아래 탭이 있고 선택한 탭에 따라 화면이 변경하도록 하는 기능을 추가하고 싶었다. 이 기능을 탭 레이아웃과 뷰페이저로도 구현할 수 있지만 이 글에서는 바텀 네비게이션 바를 이용하여 기능을 구현해보려고 한다.
📌 1. dependencies 추가
1. build.gradle을 클릭하여 오픈한다.
2. dependencies 에 아래 코드를 추가해준다.
implementation 'com.google.android.material:material:1.6.1'
3. Sync Now를 클릭한다.
📌 2. Menu 폴더와 navi_menu.xml 파일 생성
위처럼 프로젝트에 Menu라는 이름의 폴더와 navi_menu.xml 파일을 생성해주도록 하자. xml파일 명은 본인이 원하는대로 작성해도 상관없다.
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/Monitor"
app:showAsAction="ifRoom"
android:enabled="true"
android:icon="@drawable/ic_launcher_foreground"
android:title="Monitor" />
<item
android:id="@+id/WhiteList"
app:showAsAction="ifRoom"
android:icon="@drawable/ic_launcher_foreground"
android:title="WhiteList" />
<item
android:id="@+id/Guide"
app:showAsAction="ifRoom"
android:icon="@drawable/ic_launcher_foreground"
android:title="WhiteList" />
</menu>
내가 생성한 navi_menu.xml파일은 위와 같다. 간단히 아래와 같이 생긴 네비게이션 바를 구성한 것이다. 3개의 탭을 구성하여 화면이 이동할 수 있도록 구성할 것이다.
📌 3. XML 파일 구성
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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=".Activity.MainActivity">
<FrameLayout android:layout_width="0dp"
android:layout_height="0dp"
android:id="@+id/fragment_container"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@+id/bnv_main"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"/>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:layout_width="0dp"
android:layout_height="?attr/actionBarSize"
android:id="@+id/bnv_main"
android:background="#FFFFFF"
app:menu="@menu/navi_menu"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
activity_main.xml 파일을 위와 같이 구성하자. 그래야 적당한 크기로 네비게이션 바가 구성된다.
만약 앞에서 xml파일명을 navi_menu가 아닌 다른 이름으로 지정했다면 app:menu="@menu/여기" 여기라고 표시된 위치에 지정한 이름을 적어주면 된다.
📌 4. Fragment 추가
프래그먼트를 추가해야 한다. 앞에서 탭을 3개 만들어주었으니 프래그먼트 역시 3개를 만들어줘야 한다.
위와 같이 이동하여 사용할 프래그먼트 3개를 만들어주도록 하자.
- WhiteList Fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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"
tools:context=".Fragment.WhiteListFragment">
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="WhiteList Fragment" />
</FrameLayout>
프래그먼트의 xml 레이아웃은 3개의 프래그먼트 모두 위 코드와 동일하다.
- WhiteListFragment.kt
package com.example.mobiletrafficanalysis.Fragment
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.example.mobiletrafficanalysis.R
class WhiteListFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_white_list, container, false)
}
}
모든 프래그먼트의 코드는 위와 동일하다.
다만 return inflater.inflate(R.layout.프래그먼트명, container, false) 프래그먼트 명만 달라질 것이다.
📌 MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val bnv_menu = findViewById<BottomNavigationView>(R.id.bnv_main)
// 1
bnv_menu.run{
setOnItemSelectedListener {
when(it.itemId){
R.id.Monitor -> { // 2
val monitorFragment = MonitorFragment()
supportFragmentManager.beginTransaction().replace(R.id.fragment_container, monitorFragment).commit() // 3
}
}
R.id.WhiteList -> {
val whiteListFragment = WhiteListFragment()
supportFragmentManager.beginTransaction().replace(R.id.fragment_container, whiteListFragment).commit()
}
}
R.id.Guide -> {
val guideFragment = GuideFragment()
supportFragmentManager.beginTransaction().replace(R.id.fragment_container, guideFragment).commit()
}
}
}
true
}
selectedItemId = R.id.Monitor // 4
}
}
}
MainActivity 코드 중 중요한 부분에 주석으로 번호를 붙였고, 각각에 대해 설명할 것이다.
1. 바텀 네비게이션뷰를 참조한 bnv_menu에 리스너를 등록한다. 간혹 구글링을 하다보면 setOnNavigationItemSelectedListener() 리스너를 사용한 코드를 볼 수 있는데 이 코드가 현재는 deprecated 되었다. 따라서 setOnItemSelectedListener() 리스너를 사용하도록 하자. 사용법에 있어서 크게 달라진 점은 없어보인다.
2. 선택된 아이템의 id에 따라 각각 다른 화면을 오픈한다. 여기서 itemId는 2번 navi_menu.xml 작성 시 추가해주었던 item들의 id중 하나가 될 것이다. 조금 더 쉽게 이야기하면 바텀 네비게이션 바의 각 탭들의 id가 되는 것이다.
3. 만약 선택된 아이템의 id가 Monitor라면 monitorFragment를 생성 및 초기화 해준 뒤에 replace()를 통해 프래그먼트를 교체한다. 이는 whiteListFragment 이든, guideFragment 이든 상관없이 같은 일을 한다.
4. 처음에 오픈되는 프래그먼트를 결정한다.
📌 실행 화면
📌 추가 내용
정리한 내용을 이용하여 바텀 네비게이션 바와 그에 맞는 프래그먼트를 띄울 수 있는 방법을 알아보았다. 그런데 위 내용을 그대로 따르면 네비게이션 탭을 선택할 때마다 프래그먼트를 새로 띄우는 것이기 때문에 이전에 보고있던 정보들은 날아가고 다시 처음부터 로드된다.
이를 해결하는 방법은 MainActivity의 setOnItemSelectedListener코드를 조금 수정해주면 된다.
bnv_menu.run{
setOnItemSelectedListener {
when(it.itemId){
// 모니터 프래그먼트 선택 시
R.id.Monitor -> {
// 처음 선택 시 초기화 및 추가
if (monitorFragment == null) {
monitorFragment = MonitorFragment()
supportFragmentManager.beginTransaction().add(R.id.fragment_container, monitorFragment!!).commit()
}
// 선택된 프래그먼트만 보이도록 변경
if(monitorFragment != null) supportFragmentManager.beginTransaction().show(monitorFragment!!).commit()
if(whiteListFragment != null) supportFragmentManager.beginTransaction().hide(whiteListFragment!!).commit()
if(guideFragment != null) supportFragmentManager.beginTransaction().hide(guideFragment!!).commit()
}
// 화이트리스트 프래그먼트 선택 시
R.id.WhiteList -> {
// 처음 선택 시 초기화 및 추가
if (whiteListFragment == null) {
whiteListFragment = WhiteListFragment()
supportFragmentManager.beginTransaction().add(R.id.fragment_container, whiteListFragment!!).commit()
}
// 선택된 프래그먼트만 보이도록 변경
if(monitorFragment != null) supportFragmentManager.beginTransaction().hide(monitorFragment!!).commit()
if(whiteListFragment != null) supportFragmentManager.beginTransaction().show(whiteListFragment!!).commit()
if(guideFragment != null) supportFragmentManager.beginTransaction().hide(guideFragment!!).commit()
}
// 가이드 프래그먼트 선택 시
R.id.Guide -> {
// 처음 선택 시 초기화 및 추가
if (monitorFragment == null) {
guideFragment = GuideFragment()
supportFragmentManager.beginTransaction().add(R.id.fragment_container, guideFragment!!).commit()
}
// 선택된 프래그먼트만 보이도록 변경
if(monitorFragment != null) supportFragmentManager.beginTransaction().hide(monitorFragment!!).commit()
if(whiteListFragment != null) supportFragmentManager.beginTransaction().hide(whiteListFragment!!).commit()
if(guideFragment != null) supportFragmentManager.beginTransaction().show(guideFragment!!).commit()
}
}
true
}
selectedItemId = R.id.Monitor
}
setOnItemSelectedListener() 에서 replace()를 사용했던 것과 다르게 add(), show(), hide()를 사용하여 탭에 맞는 화면을 보여주고 나머지 화면은 감출 수 있다.
[1] https://stickode.tistory.com/101
[2] https://hanyeop.tistory.com/425