썸네일 사진처럼 가운데에 플로팅 버튼이 박힌 바텀네비게이션을 만들고 싶었다.
처음 만들어보는 거라 많이 걱정됐지만, 되게 좋은 자료가 있어서 생각보다 쉽게 만들 수 있었다.
바텀네비게이션을 만들기 위해 꼭 필요한 작업이다.
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/menu_home"
android:icon="@drawable/selector_btm_nav_home"
android:title="@string/menu_home"
app:showAsAction="ifRoom"/>
<item android:id="@+id/blank"
android:title=""
android:enabled="false"/>
<item
android:id="@+id/menu_list"
android:icon="@drawable/selector_btm_nav_list"
android:title="@string/menu_ingredient"
app:showAsAction="always"/>
</menu>
바텀네비에 총 아이콘이 2개 들어갈텐데, 가운데는 플로팅 버튼이 들어가야한다. 이처럼 실제 탭은 2개이지만, FAB 버튼이 가운데에 더 자연스럽게 들어갈 수 있도록 가운데에 아이템을 하나 더 만들어준다.
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/ic_btm_nav_home_selected" android:state_checked="true"/>
<item android:drawable="@drawable/ic_btm_nav_home_unselected" android:state_checked="false"/>
</selector>
이런 식으로 selector 파일에는 바텀 아이템을 선택했을 때와, 선택하지 않았을 때의 아이콘을 넣어줘서 선택 상태에 따라 이미지가 변할 수 있도록 한다.
CoordinatorLayout
로 변경해야한다.나도 이번에 처음 써봤다.
바텀 앱바
<com.google.android.material.bottomappbar.BottomAppBar
android:id="@+id/main_bottom_appBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:fabCradleMargin="5dp"
app:fabCradleRoundedCornerRadius="10dp">
</com.google.android.material.bottomappbar.BottomAppBar>
플로팅 액션 버튼
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/main_floating_add_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_plus"
android:clickable="true"
android:backgroundTint="@color/blue"
android:focusable="true"
app:layout_anchor="@id/main_bottom_appBar"/>
- 플로팅 액션 버튼을 바텀 앱바와 연결하는 부분
app:layout_anchor="@id/main_bottom_appBar"
- 바텀 앱바에서 플로팅 버튼과의 마진, 모서리 굴곡을 설정하는 부분
app:fabCradleMargin="5dp" app:fabCradleRoundedCornerRadius="10dp"
fabCradleMargin = 5dp, fabCradleRoundedCornerRadius = 10dp fabCradleMargin = 15dp, fabCradleRoundedCornerRadius = 10dp fabCradleMargin = 15dp, fabCradleRoundedCornerRadius = 20dp 몇가지 예를 살펴보자면, 각각 이 정도의 차이가 있다. 디자인을 보며 원하는 대로 수정하면 될 듯하다.
바텀앱바 안에 바텀네이게이션 뷰를 추가해준다.
<com.google.android.material.bottomappbar.BottomAppBar
android:id="@+id/main_bottom_appBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:fabCradleMargin="5dp"
app:fabCradleRoundedCornerRadius="10dp">
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/main_bottom_nav"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginEnd="16dp"
android:elevation="5dp"
android:background="@color/transparent"
app:itemBackground="@color/transparent"
app:itemRippleColor="@color/transparent"
app:itemIconSize="24dp"
app:menu="@menu/menu_bottom_nav"/>
</com.google.android.material.bottomappbar.BottomAppBar>
app:menu="@menu/menu_bottom_nav"
로 앞서 작성했던 menu 파일을 연결해준다.
android:background="@color/transparent"
로 백그라운드를 투명하게 설정해야하고,
android:layout_marginEnd="16dp"
로 앱바의 기본 leftmargin에 맞춰서 right 마진도 설정해준다.
이게 완성한 후에 xml에서 확인한 모습인데, 위의 사진처럼 음영이 지는 것을 확인할 수 있다.
이 부분은 코드에서
// 바텀네비게이션 음영 삭제
binding.mainBottomNav.background = null
를 통해 없애줄 수 있다.
그리고 앞서 blank로 설정해놓았던, FAB이 들어갈 자리에 위치한 바텀네비 아이템에 대해
// 자리를 비워놓은(플로팅 버튼) 아이템에 대해 비활성화
binding.mainBottomNav.menu.getItem(1).isEnabled = false
위 코드로 비활성화해줄 수 있다. 바텀네비 아이템 선택에 대한 처리를 해줘야 할 경우, 아까 가운데 메뉴를 비워주었기 때문에 위 코드를 작성하면 오류없이 동작할 수 있다.
<FrameLayout
android:id="@+id/main_frm"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/main_bottom_nav" />
매인 액티비티 내에서 프래그먼트를 바뀌게 할 코드이다.
그래서 최종적인 activity_main.xml의 코드는 다음과 같이 나온다.
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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"
tools:context=".ui.MainActivity">
<FrameLayout
android:id="@+id/main_frm"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/main_bottom_nav" />
<com.google.android.material.bottomappbar.BottomAppBar
android:id="@+id/main_bottom_appBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:fabCradleMargin="5dp"
app:fabCradleRoundedCornerRadius="10dp">
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/main_bottom_nav"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginEnd="16dp"
android:elevation="5dp"
android:background="@color/transparent"
app:itemBackground="@color/transparent"
app:itemRippleColor="@color/transparent"
app:itemIconSize="24dp"
app:menu="@menu/menu_bottom_nav"/>
</com.google.android.material.bottomappbar.BottomAppBar>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/main_floating_add_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_plus"
android:clickable="true"
android:backgroundTint="@color/blue"
android:focusable="true"
app:layout_anchor="@id/main_bottom_appBar"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
화면을 만들어줬으니 아이템, FAB 클릭에 따른 화면 전환 동작을 처리해줄 수 있다.
class MainActivity : BaseActivity<ActivityMainBinding>(ActivityMainBinding::inflate) {
private val manager = supportFragmentManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
showInit()
initBottomNav()
showAddDialog()
}
private fun showInit() {
val transaction = manager.beginTransaction()
.add(R.id.main_frm, HomeFragment())
transaction.commitAllowingStateLoss()
}
private fun showAddDialog() {
binding.mainFloatingAddBtn.setOnClickListener {
val dialog = AddDialog()
// 알림창이 띄워져있는 동안 배경 클릭 막기
dialog.isCancelable = false
dialog.show(this.supportFragmentManager, "AddDialog")
}
}
private fun initBottomNav() {
binding.mainBottomNav.itemIconTintList = null
// 바텀네비게이션 음영 삭제
binding.mainBottomNav.background = null
// 자리를 비워놓은(플로팅 버튼) 아이템에 대해 비활성화
binding.mainBottomNav.menu.getItem(1).isEnabled = false
binding.mainBottomNav.setOnItemSelectedListener {
when(it.itemId) {
R.id.menu_home -> {
HomeFragment().changeFragment()
Log.d("MainActivity", "HomeFragment")
}
R.id.menu_list -> {
IngredientFragment().changeFragment()
Log.d("MainActivity", "ListFragment")
}
}
return@setOnItemSelectedListener true
}
binding.mainBottomNav.setOnItemReselectedListener { } // 바텀네비 재클릭시 화면 재생성 방지
}
private fun Fragment.changeFragment() {
manager.beginTransaction().replace(R.id.main_frm, this).commitAllowingStateLoss()
}
바텀네비 아이템을 클릭했을 땐 각각 HomeFragment, IngredientFragment가 보여지도록, 그리고 플로팅 버튼을 클릭했을 때는 다이얼로그가 뜨도록 했다.
각각 그냥 원래 쓰던 바텀네비 화면 전환처럼, 그리고 일반적인 위젯 클릭 이벤트처럼 코드를 작성하면 된다.