Android

[Android] Jitpack으로 라이브러리 배포 - 1

tenacy 2024. 8. 5. 19:23

https://github.com/tentenacy/DragHandleBottomSheet

DragHandleBottomSheet의 필요성

사이드 프로젝트 개발 중 Bottom Sheet 안에서 스크롤을 해야 하는 상황에 직면했다.

하지만 Bottom Sheet 컴포넌트 자체가 스크롤 기능을 가지고 있기 때문에,

Bottom Sheet 안에서 스크롤을 하는 거 자체가 중첩 스크롤인 상황이다.

그렇다면 NestedScrollView를 사용하여 중첩 스크롤을 구현하면 되지만,

나는 추가적으로, Bottom Sheet의 바디가 아닌 드래그 핸들을 통해서만 스크롤을 하고 싶었기에,

DragHandleBottomSheet라는 Custom Bottom Sheet을 개발하기로 했다.

참고로, 내 예전 NestedScrollView 사용 경험으로는 예기치 못한 스크롤 동작이 동반되는 경우가 조금 있었다.

DragHandleBottomSheet은 Bottom Sheet을 Not draggable한 상태로 만들어 드래그 핸들을 통해서 직접 Bottom Sheet의 위치를 애니메이션과 함께 컨트롤하기 때문에 그 안에 있는 뷰의 기본적인 스크롤 동작을 방해하지 않는다.

구현

Not draggable BSD

앞서 언급했듯이, Bottom Sheet Dialog(BSD)의 드래그 핸들을 통해서만 위치를 컨트롤할 것이기 때문에 Not draggable한 상태로 만든다.

SetOnTouchListener

그리고 드래그 핸들에 OnTouchListener를 설정하여 터치 이벤트를 감지한다. 이 때, 터치 좌표가 event를 통해 전달될 것이다.

event.action은 총 세 가지 상태만 사용할 것이다. ACTION_DOWN, ACTION_MOVE, ACTION_UP이다.

  • MotionEvent.ACTION_DOWN : 터치 이벤트 시작 시 최초 발생
  • MotionEvent.ACTION_MOVE : 터치하는 동안 발생
  • MotionEvent.ACTION_UP : 터치 이벤트 종료 시 발생

BSD의 드래그 동작은 ACTION_MOVE에서 수행되고, BSD의 EXPANDED 혹은 HIDDEN 동작은 ACTION_UP에서 수행된다.

드래그 동작

드래그 동작은 BSD의 y가 최소 지점과 최대 지점 안에서만 수행되게 구현하면 된다.

이는 마지막으로 터치한 y좌표를 저장하는 lastTouchY라는 변수를 설정하여, 변화율만큼 계속 BSD의 y좌표를 갱신한다.

STATE_EXPANDED or STATE_HIDDEN

기본 BSD의 EXPANDED 혹은 HIDDEN 동작은 하향 드래그 시 기준 지점을 벗어나지 못하면 BSD가 다시 올라오고, 기준 지점을 벗어나면 내려간다.

이 동작을 구현하기 위해서는 세 가지 준비물이 필요하다. BSD의 topy, BSD 내부 컨테이너의 높이이다.

BSD의 시작 위치 top에서 컨테이너의 높이의 특정 비율만큼을 기준 지점으로 설정하고, y가 이 지점을 벗어나는지 못 벗어나는지를 구현하는 것이다.

EXPANDED 애니메이션 직접 구현

BSD를 EXPANDED 혹은 HIDDEN 상태로 전환할 때 BottomSheetBehavior에서 제공하는 STATE_EXPANDEDSTATE_HIDDEN을 사용한다.

하지만, STATE_EXPANDED는 내부 구현이 외부에서 y좌표를 직접 컨트롤하는 경우 정상적으로 동작이 수행되지 않게 되어 있어서, 직접 애니메이션을 줄 수밖에 없었다.

기본 BSD의 EXPANDED 애니메이션을 관찰한 결과, Interpolator는 DecelerateInterpolator를 사용하는 거 같았다. 그래서 매개변수를 적절하게 조절하여 EXPANDED 애니메이션을 구현했다.

소스코드(Kotlin)

package com.tenutz.kiosksim.ui.base

import android.animation.ObjectAnimator
import android.annotation.SuppressLint
import android.os.Bundle
import android.view.MotionEvent
import android.view.View
import androidx.databinding.ViewDataBinding
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.tenutz.kiosksim.R

abstract class HandleDraggableBottomSheetDialogFragment<VB : ViewDataBinding>(layoutId: Int) :
    BaseBottomSheetDialogFragment<VB>(layoutId) {

    companion object {
        private const val SCROLL_VERTICAL_RATIO = 0.45
    }

    private var lastTouchY: Float = 0f

    @SuppressLint("ClickableViewAccessibility")
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        makeDraggableByHandle(view)
    }

    @SuppressLint("ClickableViewAccessibility")
    private fun makeDraggableByHandle(view: View) {
        //common resource id
        val dragHandle = view.findViewById<View>(R.id.constraint_drag_handle_container)

        bottomSheetBehavior.isDraggable = false

        dragHandle.setOnTouchListener { _, event ->
            when (event.action) {
                MotionEvent.ACTION_DOWN -> {
                    lastTouchY = event.rawY
                    true
                }

                MotionEvent.ACTION_MOVE -> {
                    val deltaY = event.rawY - lastTouchY

                    val maxY = binding.root.height.toFloat() + bottomSheet.top.toFloat()
                    val minY = bottomSheet.top.toFloat()

                    if (bottomSheet.y + deltaY in minY..maxY) {
                        bottomSheet.y += deltaY
                    }

                    lastTouchY = event.rawY

                    true
                }

                MotionEvent.ACTION_UP -> {
                    if (bottomSheet.y < binding.root.height.toFloat() * (1 - SCROLL_VERTICAL_RATIO) + bottomSheet.top) {
                        ObjectAnimator.ofFloat(bottomSheet, "translationY", 0f).apply {
                                interpolator = DecelerateInterpolator(2f)
                            duration = 300
                            start()
                        }
                    } else {
                        bottomSheetBehavior.state = BottomSheetBehavior.STATE_HIDDEN
                    }

                    true
                }

                else -> false
            }
        }

    }
}

 

라이브러리 배포

DragHandleBottomSheet을 라이브러리로 만들어 배포하면 유용하게 쓰일 거 같았다.

한 번도 배포해본 적 없지만, 이번 기회로 한 번 해보려고 한다.

라이브러리 배포에 관한 자세한 내용은 다음 글에서 계속 작성하겠다.

[Android] Jitpack으로 라이브러리 배포 - 2