오늘은 잘 사용하면 UI도 다채롭고 재활용도 쉬운 BottomSheet를 정리한다.
우선 만들기전에 BottomSheet의 종류를 알아보자. BottomSheet는 두 종류가 있다.
Persistent bottomSheet
Modal bottomSheet
/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>
결과물
/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에서 설정해주면 하단에서 펼쳐지는 방식으로 자식 뷰가 동작한다.
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>
결과물
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를 숨긴다.BOTTOMSHEET 위로 올리기 버튼
을 클릭하면 Persistent BottomSheet를 behavior_peekHeight의 크기만큼 확장한다.
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>
<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>
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()
}
}
google에 bottomsheet를 검색하면 다양한 응용법들이 있다.
사람들이 어떤 방식으로 사용하는지 보고 적용하면 좋을듯하다.