오늘은 잘 사용하면 UI도 다채롭고 재활용도 쉬운 BottomSheet를 정리한다.





BottomSheet 종류

우선 만들기전에 BottomSheet의 종류를 알아보자. BottomSheet는 두 종류가 있다.

Persistent bottomSheet

  • 화면상에 존재하면서 위아래로 슬라이드 할 수 있다.
  • 미리보기가 가능하다.

Modal bottomSheet

  • dialog식으로 불러낸다.





Persistent BottomSheet 구현


  1. /app/res/drawable 디렉토리에 BottomSheet의 background을 그려줄 xml파일을 추가한다.


/app/res/drawable/background_bottom_sheet.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">

    <solid
        android:color="@color/black"/>

    <corners
        android:topLeftRadius="40dp"
        android:topRightRadius="40dp" />

    <padding
        android:left="10dp"
        android:right="10dp"
        android:top="10dp"
        android:bottom="10dp" />

</shape>


결과물


  1. /app/res/layout 디렉토리에 BottomSheet의 view를 그리는 xml파일을 추가한다.

    /app/res/layout/bottom_sheet.xml

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/bottom_sheet_layout"
        android:layout_width="match_parent"
        android:layout_height="300dp"
        android:background="@drawable/background_bottom_sheet"
        app:behavior_hideable="true"
        app:behavior_peekHeight="50dp"
        android:orientation="vertical"
        android:padding="10dp"
        android:clickable="true"
        android:focusable="true"
        app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
    
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:textColor="@color/white"
            android:text="Persistent BottomSheet"
            android:textStyle="bold"
            android:layout_margin="10dp"
            android:textSize="20sp" />
    
        <Button
            android:id="@+id/expand_persistent_button"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text="확장하기"
            android:textSize="15sp" />
    
        <Button
            android:id="@+id/hide_persistent_button"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text="숨기기"
            android:textSize="15sp"  />
    
        <Button
            android:id="@+id/show_modal_button"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text="Modal BottomSheet 열기"
            android:textSize="15sp"  />
    
    </LinearLayout>


    결과물

    관련 옵션
    - app:behavior_hideable : bottomSheet 숨기기 가능 유무
    - app:behavior_peekHeight : 미리보기 상태로 제일 처음 bottomSheet의 크기
    - app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior" : CoordinatorLayout에서 자식 뷰에 대한 플러그인 중 하나다. 이 옵션을 자식 뷰의 app:layout_behavior에서 설정해주면 하단에서 펼쳐지는 방식으로 자식 뷰가 동작한다.


  1. activity_main.xml에 1,2번에서 만든 BottomSheet를 include해준다.

    /app/java/res/activity_main.xml

    <?xml version="1.0" encoding="utf-8"?>
    <layout>
    <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=".MainActivity">
    
        <Button
            android:id="@+id/show_button"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"
            android:text="bottomSheet 위로 올리기" />
    
        <include
            layout="@layout/bottom_sheet" />
    
    </androidx.coordinatorlayout.widget.CoordinatorLayout>
    </layout>


    결과물


  1. MainActivity.kt에 BottomSheet 초기화 및 이벤트를 넣어준다. 자세한 설명은 주석 확인

    /app/java/패키지/MainActivity.kt
    class MainActivity : AppCompatActivity() {

        // 데이터 바인딩
        private var _binding: ActivityMainBinding? = null
        private val binding get() = _binding!!

        // BottomSheet layout 변수
        private val bottomSheetLayout by lazy { findViewById<LinearLayout>(R.id.bottom_sheet_layout) }
        private val bottomSheetExpandPersistentButton by lazy { findViewById<Button>(R.id.expand_persistent_button) }
        private val bottomSheetHidePersistentButton by lazy { findViewById<Button>(R.id.hide_persistent_button) }
        private val bottomSheetShowModalButton by lazy { findViewById<Button>(R.id.show_modal_button) }

        // bottomSheetBehavior
        private lateinit var bottomSheetBehavior: BottomSheetBehavior<LinearLayout>

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            _binding = DataBindingUtil.setContentView(this, R.layout.activity_main)

            initializePersistentBottomSheet()
            persistentBottomSheetEvent()

        }

        override fun onResume() {
            super.onResume()

            binding.showButton.setOnClickListener {
                // BottomSheet의 peek_height만큼 보여주기
                bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
            }
        }

        override fun onDestroy() {
            super.onDestroy()
            _binding = null
        }


        // Persistent BottomSheet 초기화
        private fun initializePersistentBottomSheet() {

            // BottomSheetBehavior에 layout 설정
            bottomSheetBehavior = BottomSheetBehavior.from(bottomSheetLayout)

            bottomSheetBehavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
                override fun onStateChanged(bottomSheet: View, newState: Int) {

                    // BottomSheetBehavior state에 따른 이벤트
                    when (newState) {
                        BottomSheetBehavior.STATE_HIDDEN -> {
                            Log.d("MainActivity", "state: hidden")
                        }
                        BottomSheetBehavior.STATE_EXPANDED -> {
                            Log.d("MainActivity", "state: expanded")
                        }
                        BottomSheetBehavior.STATE_COLLAPSED -> {
                            Log.d("MainActivity", "state: collapsed")
                        }
                        BottomSheetBehavior.STATE_DRAGGING -> {
                            Log.d("MainActivity", "state: dragging")
                        }
                        BottomSheetBehavior.STATE_SETTLING -> {
                            Log.d("MainActivity", "state: settling")
                        }
                        BottomSheetBehavior.STATE_HALF_EXPANDED -> {
                            Log.d("MainActivity", "state: half expanded")
                        }
                    }

                }

                override fun onSlide(bottomSheet: View, slideOffset: Float) {
                }

            })

        }

        // PersistentBottomSheet 내부 버튼 click event
        private fun persistentBottomSheetEvent() {

            bottomSheetExpandPersistentButton.setOnClickListener {
                // BottomSheet의 최대 높이만큼 보여주기
                bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
            }

            bottomSheetHidePersistentButton.setOnClickListener {
                // BottomSheet 숨김
                bottomSheetBehavior.state = BottomSheetBehavior.STATE_HIDDEN
            }

            bottomSheetShowModalButton.setOnClickListener {
                // 추후 modal bottomSheet 띄울 버튼
            }

        }


    }





Persistent BottomSheet 구현 결과

  • 확장하기 버튼을 클릭하면 Persistent BottomSheet를 원래 사이즈로 확장한다.
  • 숨기기 버튼을 클릭하면 Persistent BottomSheet를 숨긴다.
  • BOTTOMSHEET 위로 올리기 버튼을 클릭하면 Persistent BottomSheet를 behavior_peekHeight의 크기만큼 확장한다.





Modal BottomSheet 구현


  1. Modal BottomSheet가 생기고 사라질때 적용할 애니메이션을 /app/res/anim 디렉토리에 애니메이션 파일을 2개 추가한다.
    (anim 디렉토리가 없으면 디렉토리 생성 후 파일을 추가)

    modal_slide_in_bottom.xml

    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android">
    
        <translate
       	 android:duration="@android:integer/config_mediumAnimTime"
        	android:fromYDelta="100%p"
        	android:toYDelta="0" />
    
    </set>


    modal_slide_out_bottom.xml

    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android">
    
        <translate
        	android:duration="@android:integer/config_mediumAnimTime"
        	android:fromYDelta="0"
        	android:toYDelta="100%p" />
    
    </set>

  1. /app/res/layout 디렉토리에 Modal BottomSheet의 view를 그리는 xml파일을 추가한다.
  1. /app/res/values/themes 디렉토리에 있는 themes.xml, themes.xml (night) 두 파일에 Modal BottomSheet의 애니메이션을 style로 등록해준다.

    themes.xml, themes.xml (night)
    <resources xmlns:tools="http://schemas.android.com/tools">
        ...
        <style name="DialogAnimation">
            <item name="android:windowEnterAnimation">@anim/modal_slide_in_bottom</item>
            <item name="android:windowExitAnimation">@anim/modal_slide_out_bottom</item>
        </style>
    </resources>

  1. /app/java/패키지/MainActivity.kt에서 ModalBottomSheet에 관련된 코드를 넣는다.

    MainActivity.kt
    class MainActivity : AppCompatActivity() {
        ...

        override fun onCreate(savedInstanceState: Bundle?) {
            ...
        }

        override fun onResume() {
            ...
        }

        override fun onDestroy() {
            ...
        }

        // Persistent BottomSheet 초기화
        private fun initializePersistentBottomSheet() {
            ...
        }

        // PersistentBottomSheet 내부 버튼 click event
        private fun persistentBottomSheetEvent() {
            ...
            bottomSheetShowModalButton.setOnClickListener {
                // Modal BottomSheet 띄우기
                showModalBottomSheet()
            }
        }

        // Modal BottomSheet 띄우기
        private fun showModalBottomSheet() {

            val dialog: Dialog = Dialog(this)
            dialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
            dialog.setContentView(R.layout.modal_bottom_sheet)

            // modal_bottom_sheet.xml의 dismiss_button로 변수 초기화
            val dismissButton: Button = dialog.findViewById(R.id.dismiss_button)

            // dismiss_button 클릭 시 Modal BottomSheet 닫기
            dismissButton.setOnClickListener {
                dialog.dismiss()
            }

            // Modal BottomSheet 크기
            dialog.window?.setLayout(
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.WRAP_CONTENT
            )

            // Modal BottomSheet의 background를 제외한 부분은 투명
            dialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
            dialog.window?.attributes?.windowAnimations = R.style.DialogAnimation
            dialog.window?.setGravity(Gravity.BOTTOM)

            // Modal BottomSheet 보여주기
            dialog.show()
        }
    }





Modal BottomSheet 구현 결과










마치며

google에 bottomsheet를 검색하면 다양한 응용법들이 있다.
사람들이 어떤 방식으로 사용하는지 보고 적용하면 좋을듯하다.

profile
Android Developer

0개의 댓글

관련 채용 정보

Powered by GraphCDN, the GraphQL CDN