제스처 응답 시스템
제스처 응답 시스템은 앱 내 제스처의 라이프사이클을 관리한다. 사용자의 의도를 파악하는 과정에서 터치는 여러 단계를 거칠 수 있다. 예를 들어, 앱은 터치가 스크롤인지, 위젯을 슬라이드하는 것인지, 아니면 탭인지를 판단해야 한다. 이러한 판단은 터치가 진행되는 동안에도 바뀔 수 있다. 또한 동시에 여러 터치가 발생할 수도 있다.
터치 응답 시스템은 컴포넌트가 부모나 자식 컴포넌트에 대한 추가 정보 없이도 이러한 터치 상호작용을 조정할 수 있도록 해준다.
모범 사례
앱을 사용자 친화적으로 만들기 위해 모든 동작은 다음과 같은 속성을 가져야 한다:
- 피드백/하이라이트: 사용자가 터치한 부분을 보여주고, 제스처를 끝냈을 때 어떤 일이 발생할지 알려준다.
- 취소 가능성: 동작을 수행하는 중에도 사용자가 손가락을 멀리 끌어서 중간에 취소할 수 있어야 한다.
이러한 기능은 사용자가 실수를 두려워하지 않고 자유롭게 실험하고 상호작용할 수 있게 해준다. 결과적으로 앱 사용이 더 편안해진다.
TouchableHighlight와 Touchable*
리스폰더 시스템(responder system)을 직접 사용하는 것은 복잡할 수 있다. 그래서 '탭 가능한(tappable)' 요소를 위해 추상화된 Touchable
구현체를 제공한다. 이는 리스폰더 시스템을 활용하며, 탭 인터랙션을 선언적으로 구성할 수 있게 해준다. 웹에서 버튼이나 링크를 사용할 곳이라면 어디든 TouchableHighlight
를 사용할 수 있다.
리스폰더 라이프사이클
뷰는 적절한 협상 메서드를 구현해 터치 리스폰더가 될 수 있다. 뷰가 리스폰더가 되길 원하는지 확인하는 두 가지 메서드가 있다:
View.props.onStartShouldSetResponder: evt => true,
- 터치가 시작될 때 이 뷰가 리스폰더가 되길 원하는가?View.props.onMoveShouldSetResponder: evt => true,
- 뷰가 리스폰더가 아닐 때 터치 이동이 발생할 때마다 호출된다. 이 뷰가 터치 응답성을 "요청"할 것인가?
뷰가 true를 반환하고 리스폰더가 되려고 시도하면 다음 중 하나가 발생한다:
View.props.onResponderGrant: evt => {}
- 뷰가 이제 터치 이벤트에 응답한다. 사용자에게 현재 상황을 강조하고 보여줄 적절한 시점이다.View.props.onResponderReject: evt => {}
- 다른 무언가가 현재 리스폰더이고, 이를 해제하지 않을 것이다.
뷰가 응답 중이라면 다음 핸들러가 호출될 수 있다:
View.props.onResponderMove: evt => {}
- 사용자가 손가락을 움직이고 있다.View.props.onResponderRelease: evt => {}
- 터치가 끝날 때 발생하며, "touchUp" 이벤트와 같다.View.props.onResponderTerminationRequest: evt => true
- 다른 무언가가 리스폰더가 되길 원한다. 이 뷰가 리스폰더를 해제해야 하는가? true를 반환하면 해제를 허용한다.View.props.onResponderTerminate: evt => {}
- 리스폰더가 뷰에서 제거되었다.onResponderTerminationRequest
호출 후 다른 뷰에 의해 제거될 수도 있고, iOS의 컨트롤 센터/알림 센터처럼 OS에 의해 묻지도 않고 제거될 수도 있다.
evt
는 다음과 같은 형태의 합성 터치 이벤트다:
nativeEvent
changedTouches
- 이전 이벤트 이후 변경된 모든 터치 이벤트의 배열identifier
- 터치의 IDlocationX
- 터치의 X 위치, 엘리먼트를 기준으로 한 상대적인 값locationY
- 터치의 Y 위치, 엘리먼트를 기준으로 한 상대적인 값pageX
- 터치의 X 위치, 루트 엘리먼트를 기준으로 한 상대적인 값pageY
- 터치의 Y 위치, 루트 엘리먼트를 기준으로 한 상대적인 값target
- 터치 이벤트를 받는 엘리먼트의 노드 IDtimestamp
- 터치의 시간 식별자, 속도 계산에 유용touches
- 화면에 있는 모든 현재 터치의 배열
Capture ShouldSet 핸들러
onStartShouldSetResponder
와 onMoveShouldSetResponder
는 버블링 패턴으로 호출되며, 가장 깊은 노드부터 먼저 호출된다. 이는 여러 View가 *ShouldSetResponder
핸들러에 대해 true
를 반환할 때 가장 깊은 컴포넌트가 응답자가 된다는 것을 의미한다. 대부분의 경우 이는 모든 컨트롤과 버튼이 사용 가능하도록 보장하기 때문에 바람직하다.
그러나 때로는 부모 컴포넌트가 응답자가 되도록 보장하고 싶을 수 있다. 이는 캡처 단계를 사용해 처리할 수 있다. 응답자 시스템이 가장 깊은 컴포넌트에서 버블링되기 전에 캡처 단계를 거치며, 이때 on*ShouldSetResponderCapture
가 호출된다. 따라서 부모 View가 터치 시작 시 자식이 응답자가 되는 것을 방지하려면, onStartShouldSetResponderCapture
핸들러를 추가하고 true
를 반환해야 한다.
View.props.onStartShouldSetResponderCapture: evt => true,
View.props.onMoveShouldSetResponderCapture: evt => true,
PanResponder
고수준 제스처 해석을 위해 PanResponder를 확인한다.