해당 PR
바로가기
Dialog 가 collapse/expand 될 때 하단에 항상 고정되어 있는 뷰를 만들어보자
나는 RecyclerView 하위에 EditText 뷰를 두고, EditText 뷰가 항상 하단에 고정되어 있는 모습을 원했다.
그래서 editText 영역의 ConstraintLayout bottom to bottom 을 parent 로 해줬다.
또한 recyclerview 의 bottom 을 editText 뷰의 top 으로 설정하고, top 을 “댓글” TextView 의 bottom 으로 제약을 걸어준 후, height 를 wrap content 로 주었다. (height 을 0으로 설정하고 싶었는데 그러면 아이템이 아예 안뜨는 문제가 있다 ㅠㅠ 이 문제점도 정리하기)
내가 예상하기로는, 처음 다이얼로그가 show 될 때 화면의 반만 차지하기 때문에, EditText 가 하단에 붙고, RecyclerView 의 높이가 알아서 줄어들어 있을 줄 알았다.
그러나 내가 원하던대로 작동하지 않았다.
RecyclerView 의 높이가 알아서 줄어들지 않았고, EditText 뷰는 리사이클러뷰의 내용이 길게 표시되었기 때문에 화면에 보이지 않았다.
EditText 의 bottom 을 parent 에 제대로 제약을 걸어줬고, RecyclerView 의 Top 과 Bottom 또한 제약을 제대로 걸어줬기 때문에 왜 RecyclerView 의 높이가 조절되지 않는지 의문이었다. (이 문제로 삽질 오지게 했다.) .
그러나 실제로 LayoutInspector 를 본 결과, 처음 다이얼로그가 show 됐을 때 디스플레이 크기의 1/2 의 창 크기가 생성되는게 아니었다.
아래 캡쳐화면 처럼 전체 다이얼로그 화면의 밑 부분은 아래에 가려져 있고, dialog 를 위로 드래그 했을 때 가려져있던 전체화면이 뜨는 형식으로 동작을 하는 것이었다… 하….
해결 방법은 이제 간단하다.
하단 EditText 영역을 리사이클러뷰와는 별도의 레이아웃 파일로 생성해서
Dialog 가 show 될 때 Dialog 뷰 위에 동적으로 addView 되도록 구현해주었다.
그러면 Dialog 가 접히거나 펼쳐질 때 하단 뷰가 항상 보일 것이다!
단, 이렇게 동적으로 뷰를 추가해줄 때는 주의할 점이 있다.
현재 코드에서 하단 뷰 객체를 단 한번만 생성하고 Dialog 뷰가 띄워질때마다 동적으로 추가해주고 있다.
private fun ViewGroup.addBottomFixedItemView(view: View) {
val layoutParams = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.WRAP_CONTENT,
Gravity.BOTTOM
)
addView(view, layoutParams)
}
다이얼로그를 처음 열었을 때는 아무 문제 없다.
그러나 다이얼로그를 닫고 다시 열었을 때 아래와 같은 에러 로그가 찍힌다.
java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
해당 뷰가 이미 어떤 뷰에 add 되어있기 때문에 또 다시 add 를 할 수 없다는 오류다.
로그에서 알려주는대로 addView 하기 전에 removeView() 를 해줘야 된다.
다이얼로그가 닫혔을 때 실행되는 함수인 onCancel 에서 뷰를 제거하는게 자연스러운 것 같아 onCancel 함수에서 removeView 를 해주었다.
override fun onCancel(dialog: DialogInterface) {
super.onCancel(dialog)
(bottomSheetDialogParentContainer as ViewGroup).removeView(bottomFixedItemView)
}
그러면 이제 더 이상 오류가 발생하지 않는다!
리사이클러뷰에 아이템이 하나도 없거나, 하나만 있어도 collapse 상태에서는 최소한의 뷰 크기를 유지하고, 다이얼로그를 위로 스와이프 한다면 전체화면이 되게 해보자.
처음에는 Contraint Layout 의 내부 아이템의 높이와 상관없이 뷰를 일정한 비율로 유지하게 하기 위하여 app:layout_constraintDimensionRatio="W, 10:9"
를 이용하여 최소 화면 크기를 유지하도록 구현했었다.
그러나 문제점은 이렇게 화면 크기를 고정해버리면 다이얼로그가 전체 화면으로 스와이프가 안된다.
BottomSheetDialogFragment 는 내부적으로 frameLayout안에 우리가 만든 dialog layout xml을 넣어서 다이얼로그를 띄운다. 그래서 다이얼로그의 parent를 참조해서 frameLayout의 크기를 코드상에서 match parent로 바꿔주니깐 전체화면으로 스와이프가 가능하다!
private fun BottomSheetDialog.initShowListener() {
setOnShowListener {
bottomSheetDialogParent =
findViewById<FrameLayout>(com.google.android.material.R.id.design_bottom_sheet)
?: return@setOnShowListener
bottomSheetDialogParentContainer =
findViewById<FrameLayout>(com.google.android.material.R.id.container)
?: return@setOnShowListener
bottomSheetDialogParent.makeFullSize()
}
}
private fun View.makeFullSize() {
this.layoutParams.height = FrameLayout.LayoutParams.MATCH_PARENT
requestLayout()
}