Cheondi
개발 · Unity

버튼 위에서는 왜 스크롤이 멈출까

스크롤 목록 안의 버튼이 드래그 이벤트를 가로채면서 모바일에서 목록이 움직이지 않던 문제를 따라간 기록입니다.

  • #unity
  • #event-system
  • #scroll-view

최근 내역 항목에는 작은 차트를 여는 버튼이 있었다. PC에서 마우스 휠로 목록을 움직일 때는 문제가 없었다. 그런데 모바일처럼 손가락으로 드래그하면 버튼 위에서 시작한 스크롤이 움직이지 않았다. 버튼이 없는 빈 공간에서는 잘 됐기 때문에 처음에는 ScrollRect 영역 크기를 의심했다.

RectTransform과 Raycast 영역을 바꿔 봐도 증상은 같았다. 문제는 크기가 아니라 이벤트였다. 버튼이 포인터 이벤트를 먼저 받고 있었고, 부모 ScrollRect는 드래그가 시작됐다는 사실을 알지 못했다.

클릭과 드래그가 같은 지점에서 시작했다

사용자가 버튼 위에 손가락을 대는 순간에는 클릭할지 스크롤할지 아직 알 수 없다. 짧게 떼면 클릭이고 일정 거리 이상 움직이면 드래그다. 그런데 버튼은 처음부터 자신의 이벤트로 처리하고 있었다.

Unity EventSystem의 호출 흐름을 따라가 보니 자식이 받은 드래그 이벤트를 부모에게 전달할 방법이 필요했다. 단순화하면 다음과 같은 형태였다.

public void OnBeginDrag(PointerEventData eventData)
{
    parentScroll.OnBeginDrag(eventData);
}

public void OnDrag(PointerEventData eventData)
{
    parentScroll.OnDrag(eventData);
}

public void OnEndDrag(PointerEventData eventData)
{
    parentScroll.OnEndDrag(eventData);
}

실제 구현에서는 부모를 찾는 방식과 클릭 취소 조건을 함께 봐야 했지만, 핵심은 자식 버튼이 부모 스크롤의 드래그 생명주기를 끊지 않게 하는 것이었다.

클릭까지 같이 발생하면 안 됐다

드래그가 전달되자 목록은 움직였다. 그런데 손가락을 뗄 때 버튼 클릭도 발생해 차트가 열리는 문제가 남았다. 스크롤하려고 움직였는데 상세 화면이 열리니 더 불편했다.

그래서 포인터가 움직인 거리를 기록하고 일정 기준을 넘으면 클릭으로 처리하지 않았다.

bool dragged;

public void OnDrag(PointerEventData data)
{
    dragged |= data.delta.sqrMagnitude > dragThreshold;
    parentScroll.OnDrag(data);
}

public void OnClick()
{
    if (!dragged)
        OpenMiniChart();
}

이 예시도 프로젝트에 맞게 조정해야 한다. 화면 배율이나 입력 장치에 따라 임계값을 다르게 느낄 수 있기 때문이다. 그래도 클릭과 드래그를 결과가 아니라 입력 과정으로 구분한다는 생각이 중요했다.

PC 테스트만으로는 발견하기 어려웠다

마우스 휠은 버튼 위에서도 부모 스크롤에 전달되기 때문에 에디터 테스트에서는 문제가 잘 보이지 않았다. 드래그 입력으로 직접 확인하거나 기기에서 만져 봐야 했다. 같은 UI라도 입력 장치가 달라지면 이벤트 경로가 달라질 수 있다는 걸 처음 체감했다.

이후 모바일 UI를 볼 때 단순히 버튼이 눌리는지만 확인하지 않는다. 버튼 가장자리에서 스크롤을 시작해 보고, 빠르게 드래그한 뒤 손을 떼 보고, 목록이 움직인 직후 클릭이 잘못 발생하지 않는지도 확인한다.

작은 불편 뒤에 이벤트 구조가 있었다

사용자 입장에서는 “여기서만 스크롤이 안 된다”는 작은 불편이다. 개발할 때는 자식과 부모 사이의 이벤트 전달, 클릭 판정, 입력 장치 차이를 함께 봐야 했다. 문제를 해결하고 나니 Unity UI가 단순한 오브젝트 배치가 아니라 이벤트가 흐르는 트리라는 게 조금 보이기 시작했다.