BottomSheetDialogFragment
가끔 어플리케이션을 사용하다가 어떤 버튼(ex.정렬 버튼)을 누르면 화면 밑에서 창이 올라와 필요한 서비스(ex.날짜순,오름차순,내림차순 등등)를 제공하는 경우가 있다.
이 때 사용되는 창이 BottomSheetDialogFragment 이다.
Gradle
implementation 'com.google.android.material:material:1.6.0'
Layout
BottomSheetDialog는 보통 아래에서 위로 올라오는 움직임을 갖는다. 이처럼 view의 특정한 움직임(Behavior)를 정의해줄 수 있는 layout이 필요한데 그것이 바로 CoordinatorLayout 이다.
따라서 BottomSheetDialogFragment에 보여질 layout을 CoordinatorLayout 을 사용해주는 것이 권장된다.
*CoordinatorLayout은 제대로 정리해보자.
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
</data>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/layout_bottom_sheet"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:padding="12dp"
app:behavior_peekHeight="0dp"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>
-> CoordinatorLayout 내에 ConstraintLayout은 layout_behavior 특성을 가질수 있게된다.
-> 따라서 ConstraintLayout내에 여러 특성을 설정할 수 있는데,
*행동의 속성
1.behavior_hideable : dialog를 아래로 드래그했을 때 뷰를 숨길지 여부. (bottomSheetDialog default는 true)
2.behavior_skipCollapsed : 뷰를 숨길 때 (=dialog를 아래로 드래그할 때) 접히는 상태를 무시할 지 여부.
3.behavior_draggable : '드래그' 를 통하여 뷰를 접을 지 펼칠 지 여부. (default는 true)
4.behavior_fitToContents : 펼쳐진 뷰의 높이가 부모content를 감쌀 것인지 여부. (default는 true, false인 경우 뷰가 펼쳐졌을 때 아래로 드래그할 경우 부모 컨테이너 높이의 '절반' 으로 먼저 접히고, 다시 드래그 하면 '완전' 접혀진다.)
5.behavior_halfExpandedRatio: 절반만 펼쳐졌을 경우 뷰의 높이를 결정. (default값은 0.5, fitToContent가 true일 경우 무효)
6.behavior_expandedOffset : 완전히 펼쳐진 상태일 때 뷰의 오프셋을 결정. (default값은 0dp, behavior_ftiToContents가 true라면 무효. 절반으로 접혔을 경우의 오프셋보다 커야함.)
7.behavior_peekHeight : 뷰가 접혔을 때 (Collapsed) 되었을 때의 기본 높이. (ex.아예 안보이게 할거면 0dp)
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/layout_bottom_sheet"
android:layout_width="match_parent"
android:layout_height="500dp"
app:behavior_peekHeight="0dp"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
<TextView
android:id="@+id/tv_dialog"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:text="여기는 bottom dialog 의 세상"
android:textAlignment="center"
android:textSize="32sp"
/>
<Button
android:id="@+id/btn_dialog"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/tv_dialog"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:text="dialog 버튼이다."
/>
<Button
android:id="@+id/btn_dialog_bot"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:text="나도 dialog 버튼이다."
/>
</androidx.constraintlayout.widget.ConstraintLayout>
->BottomSheetDialog에 쓰일 layout을 만들 때 layout의 높이를 구체적으로 지정해주는 것이 좋더라. (height = 500dp)
->그래야 BottomSheetDialog가 나타날 때, 개발자가 원하는 layout으로 구현하기 쉽다.
Fragment
보통 dialog를 만들때와 마찬가지로 BottomSheetDialog 또한 DialogFragment를 상속하는 Fragment를 생성하고 이를 이용하여 UI에서 dialog를 show() 한다.
따라서 BottomSheetDialog() 클래스를 상속하는 Fragment를 만든다.
- onCreateView()
class BottomSheetDialogFragment : BottomSheetDialogFragment() {
private lateinit var binding : FragmentBottomSheetBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_bottom_sheet, container, false)
return binding.root
}
...
}
다른 DialogFragment() 를 만들 때와 다른 점이 있다면 onCreateDialog()를 재정의 하지 않고, onCreateView를 사용하여 layout단계에서 만든 layout을 사용해줬다는 점이다. BottomSheetDialog에서 onCreateDialog를 사용한다면 어떻게 되는지도 만들어봐야겠다.
내가 생각하기엔 이 부분이 custom Dialog를 만드는 과정이라 생각한다.
- onViewCreated()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val bottomSheet = binding.layoutBottomSheet
val behavior = BottomSheetBehavior.from(bottomSheet!!)
behavior.state = BottomSheetBehavior.STATE_EXPANDED
behavior.addBottomSheetCallback(object:BottomSheetBehavior.BottomSheetCallback() {
override fun onStateChanged(bottomSheet: View, newState: Int) {
// if(newState == BottomSheetBehavior.STATE_COLLAPSED)
// behavior.state = BottomSheetBehavior.STATE_EXPANDED
}
override fun onSlide(bottomSheet: View, slideOffset: Float) {
if(slideOffset == 0.5F)
behavior.state = BottomSheetBehavior.STATE_HIDDEN
}
})
-> val bottomSheet = layout의 bottomSheet layout을 가져온다. (CoordinatorLayout 내의 layout)
-> val behavior = BottomSheetBehavior는 CoordinatorLayout의 하위 클래스로 CoordinatorLayout 내에 있는 layout (= BottomSheetLayout = val bottomSheet)으로부터 해당 클래스의 객체를 (from()) 가져올 수 있다.
-> behavior.state = BottomSheetDialog가 처음으로 나타날 때 *행동을 설정할 수 있다.
*행동의 상태
1.STATE_EXPANDED : 완전히 펼쳐진 상태
2.STATE_COLLAPSED : 접혀있는 상태
3.STATE_HIDDEN : 아래로 숨겨진 상태 (보이지 않는다.)
4.STATE_HALF_EXPANDED : 절반으로 펼쳐진 상태
5.STATE_DRAGGING : 드래깅되고 있는 상태
6.STATE_SETTLING : 드래그/스와이프 직후 고정된 상태
->behavior.addBottomSheetCallback() = BottomSheetBehavior.BottomSheetCallback() 을 매개변수로 받는다.
object : BottomSheetBehaivor.BottomSheetCallback() {}
- onStateChanged(bottomSheet: View, newState: Int) : bottomSheet의 state가 바뀔 때마다 호출
- onSlide(bottomSheet: View, slideOffset: Float) : bottomSheet의 '드래그' 되어질 때 호출.
Activity
binding.btnDialog.setOnClickListener {
showDialog()
}
fun showDialog() {
val newFragment = BottomSheetDialogFragment()
newFragment.show(supportFragmentManager, "botDialog")
}