[Kotlin] BottomSheet로 내리고 올릴 수 있는 뷰 만들기

park_sujeong·2022년 12월 12일
1

Android

목록 보기
8/13
post-thumbnail

오늘은 잘 사용하면 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개의 댓글