React Native 칸반보드 드래그 앤 드롭 구현기 (1)

Published2025.12.29
Read Time10 min read
Related Project: Fly:On

React Native 칸반보드 드래그 앤 드롭 구현기(1)

서론

안녕하십니까. 오랜만에 다시 글을 쓰게 되었다. 여러 프로젝트와 취업준비로 인해 한동안 글을 쓰지 못했는데, ((사실 의지력 이슈로)) 지금부터라도! 다시 열심히 글을 쓰려고 한다!

관광데이터 공모전에서 다시 expo기반의 React Native로 프로젝트를 진행했었는데, 여기서 구현한 다양한 기능들 중 여행 일정을 관리하기 위해 드래그 앤 드롭을 사용하여 각 Day의 일정들을 드래그 앤 드롭으로 편집할 수 있어야 했다. 백문이 불여일견이라고 실제 구현했던 부분을 보여주겠다.

드래그 앤 드롭 구현 화면

다음과 같이 같은 Day 내에서의 요소의 터치를 통한 이동, 리스트간에서도 요소를 이동할 수 있어야 했고, 요소가 특정 경계선으로 갈 경우 자동 스크롤도 되어야 했다.

앞으로 이 기능을 구현할 때 필요했던 기술과 리스트 내에서 요소를 놓는 것을 구현했던 방법들을 보여줄 것이다.

아직 취준생이라 많이 부족해보이겠지만 모쪼록 잘 부탁드립니다!!

라이브러리 없이 Drag & Drop을 구현하기로 한 이유

사실... 라이브러리로 구현하는게 분명히 더 쉽고, 정신건강(?)상도 좋았을 것이다. 하지만, 그럼에도 직접 구현을 선택했던 이유가 있다.

우선, 기존에 나와있던 라이브러리를 살펴보자. (2025 8월~10월 기준으로 구현했을 때의 기준입니다.)

필요 기술들

react-beautiful-dnd

가장 유명한 Drag & Drop 라이브러리는 react-beautiful-dnd였다.
웹 환경에서는 이미 검증된 라이브러리이고, 칸반보드 구현 사례도 풍부했다. 오 이걸로 하면 되겠군!

하지만,

  • 웹 전용 라이브러리

  • React Native 환경에서는 사용 불가

아무리 구현 사례가 많아도, 이번 프로젝트에는 적용할 수 없는 선택지였다.


react-native-draggable-flatlist

다음으로 검토한 라이브러리는 제일 유명했던 react-native-draggable-flatlist였다.
React Native 환경에서 Drag & Drop을 지원하는 대표적인 라이브러리다.

단일 리스트 내에서 아이템 순서를 변경하는 용도로는 충분히 잘 동작했다.
하지만 실제 요구사항과 비교해보니 결정적인 차이가 있었다.

  • 단일 리스트 기준 설계

  • 여러 컬럼(Day) 간 이동을 전제로 하지 않음

  • 드래그 중 다른 리스트 위로 이동하는 시나리오가 구조적으로 맞지 않음

여러 개의 날짜 컬럼을 넘나드는 칸반보드 형태의 Drag & Drop을 구현하기에는
라이브러리의 기본 설계 자체가 맞지 않았다.

무리하게 커스터마이징할 수도 있었겠지만,
그 경우 라이브러리 내부 구현에 강하게 의존하게 되고
유지보수 비용이 급격히 증가할 가능성이 높았다.

그 외에도 Stack Overflow와 구글링을 통해 꾸준히 라이브러리를 찾아보았지만, 칸반보드 형태로 리스트 간 드래그 앤 드롭을 구현하는 최신 라이브러리가 없었고, 예제들 역시 매우 예전 것들 밖에 없었다.

그래서 직접 구현하자! 라는 결론에 도달하게 되었다.

PanResponder를 선택한 이유

라이브러리를 직접 구현하기로 결정한 뒤, 가장 먼저 고민한 건
어떤 도구로 드래그 입력을 받을 것인가였다.

React Native에서 제스처를 다루는 선택지는 몇 가지가 있지만,
이번 구현에서는 PanResponder를 기반으로 하기로 했다.

PanResponder란?

PanResponder는 React Native에서 제공하는 기본 제스처 시스템이다.
터치 시작, 이동, 종료와 같은 이벤트를 세밀하게 제어할 수 있고,
외부 라이브러리 없이도 드래그 인터랙션을 구현할 수 있다.

const panResponder = PanResponder.create({
  onStartShouldSetPanResponder: () => true,
  onPanResponderMove: (evt, gestureState) => {
    // 터치 이동 처리
  },
  onPanResponderRelease: () => {
    // 터치 종료 처리
  },
});

사실 단순한 Drag & Drop이라면,
gestureState.dx / dy 값을 그대로 사용해서
카드를 움직이는 방식도 충분히 가능하다.

하지만 이 프로젝트에서는 그 방식이 맞지 않았다.


PanResponder를 "드래그 엔진"으로 쓰지 않기로 한 이유

칸반보드 형태의 Drag & Drop에서는 다음과 같은 상황이 동시에 발생한다.

  • 여러 개의 세로 리스트
  • 각 리스트의 독립적인 스크롤
  • 드래그 중 다른 컬럼으로 이동
  • 화면 끝에서 자동 스크롤

이 환경에서 PanResponder가 제공하는 gestureState.dx / dy 기반의 상대 좌표 시스템은 빠르게 한계를 드러냈다. 상대 좌표의 문제점

  • dx/dy는 제스처 시작점 기준 누적 이동량이라 스크롤이 개입되면 기준점이 틀어진다
  • 컬럼이 여러 개일수록 각각의 레이아웃 좌표를 추적하기 복잡해진다
  • Portal로 렌더링된 Floating Card는 화면 최상위에 위치하므로 부모 컴포넌트 기준의 상대 좌표와 기준이 어긋난다

결국 중요한 건 "제스처 시작점으로부터 얼마나 움직였는가"가 아니라 지금 손가락이 화면의 어디에 있는가였다. 절대 좌표 기반 접근 그래서 PanResponder의 gestureState.dx/dy를 버리고 evt.nativeEvent.pageX / pageY로 화면 절대 좌표만을 사용했다. 이 방식의 장점은 명확했다.

  • 스크롤 여부와 상관없이 항상 동일한 기준 (화면 좌상단 기준)
  • 여러 컬럼 간 이동에도 좌표가 일관됨
  • Portal에 렌더링된 Floating Card의 위치 계산과 자연스럽게 연결됨
  • 드롭 타겟 판별 시 모든 요소가 같은 좌표계를 공유

즉, PanResponder는 상대 이동량을 계산하는 도구가 아니라 현재 터치 위치를 전달하는 도구가 되었다. 역할을 명확히 나누다

이 시점에서 Drag & Drop 구조는 이렇게 나뉘었다.

  • PanResponder

    • 터치 시작 / 이동 / 종료 감지
    • 현재 손가락의 절대 좌표(pageX/pageY) 전달
  • 드래그 로직

    • 절대 좌표 기반 상대 이동량 계산
    • 드롭 위치 판별 (어느 컬럼, 어느 인덱스)
    • 컬럼 간 이동 계산
    • 스크롤 오프셋 보정
    • 데이터 업데이트
  • 시각적 표현

    • Portal을 통한 Floating Card 렌더링
    • 절대 좌표 기반 초기 위치 설정
    • Animated.Value로 이동량 반영
    • opacity, scale 등의 피드백 애니메이션

PanResponder가 모든 걸 책임지지 않게 되자, 구현은 오히려 단순해졌고 각 로직을 독립적으로 테스트하고 개선할 수 있었다.

이어서

다음 편에서는 이를 이용한 구현코드들을 보여줄 예정이다. 기대 부탁~