BottomSheetBehavior
BottomSheetBehavior
는 CoordinatorLayout
에서 자식 뷰에 대한 플러그인 중 하나입니다. 이 옵션을 자식 뷰의 app:layout_behavior
에서 설정해주면 하단에서 펼쳐지는 방식으로 자식 뷰가 동작하게 됩니다. BottomSheetDialog
나 BottomSheetDialogFragment
도 같은 방식으로 동작합니다.
다음처럼 CoordinatorLayout
에서 사용할 수 있습니다.
layout_behavior
를 BottomSheetBehavior
로 설정해주는 것이 중요합니다. 그리고 해당 뷰를 이런 식으로 BottomSheetBehavior
에 연결하여 사용할 수 있습니다.
val behavior = BottomSheetBehavior.from(
findViewById<TextView>(R.id.textView_bottom_sheet)
)
Behavior 속성
app:layout_behavior
로 BottomSheetBehavior
를 설정했다면 다음과 같은 속성도 사용할 수 있습니다.
behavior_hideable
: 아래로 드래그했을 때 뷰를 숨길지 여부를 결정합니다.DialogBottomSheet
의 경우의 기본값은 true이고 그 외의 경우 기본값은 false입니다 (본 글에서 기본값은 false)behavior_skipCollapsed
: 뷰를 숨길 때 접히는 상태를 무시할 지 여부를 결정합니다. 기본값은 false이며behavior_hideable
이false
라면 효과가 없습니다.behavior_draggable
: 드래그하여 뷰를 접을 지 펼칠 지 여부를 결정합니다. 기본값은 true입니다.behavior_fitToContents
: 펼쳐진 뷰의 높이가 content를 감쌀 것인지의 여부를 결정합니다. false로 설정하게 되면 뷰가 펼쳐졌을 때 아래로 드래그할 경우 부모 컨테이너 높이의 절반으로 먼저 접히고 다시 드래그 하면 완전히 접혀집니다. 기본값은 true입니다.behavior_halfExpandedRatio
: 절반만 펼쳐졌을 경우 뷰의 높이를 결정합니다. 기본값은 0.5입니다.behavior_fitToContents
가 true라면 효과가 없습니다.behavior_expandedOffset
: 완전히 펼쳐진 상태일 때 뷰의 오프셋을 결정합니다. 마찬가지로behavior_fitToContents
가 true라면 효과가 없으며 절반으로 접혔을 경우의 오프셋보다 커야합니다. 기본값은 0dp입니다.behavior_peekHeight
: 뷰가 접힌 상태의 높이입니다. 기본값은auto
입니다.
Behavior 상태
BottomSheetBehavior
의 state
는 다음과 같습니다.
STATE_EXPANDED
: 완전히 펼쳐진 상태STATE_COLLAPSED
: 접혀있는 상태STATE_HIDDEN
: 아래로 숨겨진 상태 (보이지 않음)STATE_HALF_EXPANDED
: 절반으로 펼쳐진 상태STATE_DRAGGING
: 드래깅되고 있는 상태STATE_SETTLING
: 드래그/스와이프 직후 고정된 상태
다음과 같이 state
를 조정할 수 있습니다.
behavior.state = BottomSheetBehavior.STATE_EXPANDED
Behavior
상태 저장
BottomSheetBehavior
로 설정된 뷰가 파괴되고 다시 생성되는 경우 유지할 속성들을 다음과 같은 옵션들에서 선택하여 유지할 수 있습니다.
SAVE_PEEK_HEIGHT
:behavior_peekHeight
속성 유지SAVE_HIDEABLE
:behavior_hideable
속성 유지SAVE_SKIP_COLLAPSED
:behavior_skipCollapsed
속성 유지SAVE_FIT_TO_CONTENTS
:behavior_fitToContents
속성 유지SAVE_ALL
: 모든 속성 유지SAVE_NONE
: 기본값. 모든 속성 보존 X
다음처럼 사용할 수 있습니다.
behavior.saveFlags = BottomSheetBehavior.SAVE_FIT_TO_CONTENTS
BottomSheetCallback
이제 BottomSheetBehavior
의 콜백을 수신할 수 있습니다.
이 때 onSlide
의 slideOffset
범위는 -1.0부터 1.0의 범위를 가지며 숨겨진 상태는 -1.0, 접힌 상태는 0.0, 펼쳐진 상태는 1.0입니다.
콜백을 활용하여 이런 식으로 사용할 수 있습니다.
내부 동작
그렇다면 어떻게 이런 동작들이 가능한 걸까요? 기본적으로 BottomSheetBehavior
는 CoordinatorLayout
클래스 내부의 Behavior
라는 추상클래스를 상속받고 있습니다. 그래서 CoordinatorLayout
에서 자식 뷰에app:layout_behavior
라는 속성을 지정해줌으로써 상호작용할 수 있는 것입니다. 또한 뷰가 접힌 상태에서 레이아웃 인스펙터로 살펴보면 다음과 같은 모습을 하고 있습니다.
BottomSheetBehavior
로 설정된 뷰는 현재 보이지 않는 상태지만 이미 그려져있는 상태인 것을 알 수 있습니다.
Behavior
의 onLayoutChild()
메소드를 구현한 BottomSheetBehavior
클래스 내부의 onLayoutChild()
를 살펴보면 다음처럼 구현되어 있는 것을 확인할 수 있습니다.
offsetTopAndBottom()
이라는 메소드로 높이를 지정해주는데 이 메소드를 살펴보면 다음과 같습니다.
먼저 24번째 라인을 살펴보면 ViewParent
를 가져와서 invalidateChild
를 호출함으로써 자식뷰를 그려주고 있습니다.
이 때 STATE_HIDDEN
인 경우 부모 뷰의 높이 값을 가져와서 offset
인자로 넣어주었으므로 offset
은 0이 아니므로 2번째 라인으로 들어와서 31번째 mRenderNode.offsetTopAndBottom(offset);
라인을 거치게 됩니다. 이 때 부모 뷰의 높이 값을 offset
으로 주었으므로 offset
만큼 아래에 그려지게 됩니다.
그렇다면 STATE_COLLAPSED
인 경우는 어떨까요? STATE_COLLAPSED
일 경우에는 calculateCollapsedOffset()
메소드에서 계산된 offset을 가지고 자식 뷰가 그려질 위치를 계산하게 되는데 이 때 calculateCollapsedOffset()
에서는 부모 뷰의 높이와 우리가 지정한 fitToContentsOffset
, peekHeight
값을 가지고 offset
을 계산합니다.
Conclusion
BottomSheetBehavior
말고도 Behavior
를 상속받는 다른 Behavior
관련 클래스들이 존재하니 상황에 맞게 사용하면 굉장히 유용할 것 같습니다.