[Android/Kotlin] 플로팅 버튼이 추가된 바텀네비게이션 만들기

코코아의 개발일지·2023년 12월 8일
0

Android-Kotlin

목록 보기
19/31
post-thumbnail

✍🏻 요구사항 분석

썸네일 사진처럼 가운데에 플로팅 버튼이 박힌 바텀네비게이션을 만들고 싶었다.
처음 만들어보는 거라 많이 걱정됐지만, 되게 좋은 자료가 있어서 생각보다 쉽게 만들 수 있었다.


💻 코드 작성

1️⃣ menu 작성

바텀네비게이션을 만들기 위해 꼭 필요한 작업이다.

<?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 버튼이 가운데에 더 자연스럽게 들어갈 수 있도록 가운데에 아이템을 하나 더 만들어준다.

selector_btm_nav_home.xml

<?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 파일에는 바텀 아이템을 선택했을 때와, 선택하지 않았을 때의 아이콘을 넣어줘서 선택 상태에 따라 이미지가 변할 수 있도록 한다.


2️⃣ activity_main.xml 작성

1. 우선 xml의 기본 레이아웃을 CoordinatorLayout로 변경해야한다.

나도 이번에 처음 써봤다.

2. 바텀 앱바와 플로팅 액션 버튼을 추가해준다.

  • 바텀 앱바
<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

몇가지 예를 살펴보자면, 각각 이 정도의 차이가 있다. 디자인을 보며 원하는 대로 수정하면 될 듯하다.


3. BottomNavigationView 추가

바텀앱바 안에 바텀네이게이션 뷰를 추가해준다.

<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

위 코드로 비활성화해줄 수 있다. 바텀네비 아이템 선택에 대한 처리를 해줘야 할 경우, 아까 가운데 메뉴를 비워주었기 때문에 위 코드를 작성하면 오류없이 동작할 수 있다.

4. 프래그먼트가 바뀔 FrameLayout 추가

<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>

3️⃣ 코틀린 코드 작성

화면을 만들어줬으니 아이템, 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가 보여지도록, 그리고 플로팅 버튼을 클릭했을 때는 다이얼로그가 뜨도록 했다.
각각 그냥 원래 쓰던 바텀네비 화면 전환처럼, 그리고 일반적인 위젯 클릭 이벤트처럼 코드를 작성하면 된다.



📚 참고 자료

profile
우당탕탕 성장하는 개발자

0개의 댓글