반응형
Android 터치 이벤트 처리 방법: 발생 순서와 구현 완벽 분석
Android 앱 개발에서 사용자의 터치 입력을 올바르게 처리하는 것은 매우 중요합니다. 특히 복잡한 제스처나 커스텀 뷰를 구현할 때는 터치 이벤트의 발생 순서와 처리 메커니즘을 정확히 이해해야 합니다. 이 글에서는 Android에서 화면 터치 시 발생하는 이벤트 순서와 처리 방법을 초보자도 이해하기 쉽게 설명하겠습니다. 버튼 클릭부터 드래그, 멀티 터치, 제스처 감지까지 모든 터치 기반 상호작용의 기반이 되는 핵심 개념을 다룹니다.

목차
1. 터치 이벤트의 기본 개념
2. 터치 이벤트 발생 순서
3. MotionEvent 객체 분석
4. 터치 이벤트 전달 메커니즘
5. 자주 묻는 질문 (FAQ)
#1. 터치 이벤트의 기본 개념
Android에서 터치 이벤트는 사용자가 화면을 터치했을 때 발생하는 입력 이벤트입니다. 이 이벤트는 MotionEvent 객체로 표현되며, 터치의 종류(누르기, 움직이기, 떼기 등)와 좌표 정보를 담고 있습니다. 터치 이벤트는 Android UI 시스템의 기본이 되는 요소로, 버튼 클릭부터 복잡한 제스처까지 모든 터치 기반 상호작용의 기반이 됩니다.
. . . . .
1) 터치 이벤트의 구조
터치 이벤트를 처리하는 가장 기본적인 방법은 onTouchEvent() 메서드를 오버라이드하는 것입니다. 이 메서드는 View 클래스에 정의되어 있으며, 터치 이벤트가 발생할 때마다 자동으로 호출됩니다.
public boolean onTouchEvent(MotionEvent event) {
// 여기서 터치 이벤트를 처리
return super.onTouchEvent(event);
}
// 여기서 터치 이벤트를 처리
return super.onTouchEvent(event);
}
이 메서드는 boolean 값을 반환하는데, 이 반환값이 매우 중요합니다. true를 반환하면 해당 뷰가 이벤트를 처리했다는 의미이며, 이후의 터치 이벤트도 계속 받을 수 있습니다. false를 반환하면 이벤트를 처리하지 않았다는 의미로, 상위 뷰로 이벤트가 전달됩니다.
. . . . .
2) 터치 이벤트 처리의 중요성
터치 이벤트 처리를 정확히 이해하면 다음과 같은 기능을 구현할 수 있습니다.
(1) 커스텀 뷰 구현
드래그 가능한 버튼, 그림 그리기 앱, 제스처 인식 등 커스텀 UI 컴포넌트를 만들 수 있습니다. Android의 기본 뷰로는 구현할 수 없는 독특한 사용자 경험을 제공할 수 있습니다.
(2) 복잡한 제스처 처리
스와이프, 핀치 줌, 회전 등 복잡한 제스처를 인식하고 처리할 수 있습니다. 사진 뷰어, 지도 앱, 그림 그리기 앱 등에서 필수적인 기능입니다.
(3) 이벤트 충돌 해결
ScrollView 안의 커스텀 뷰처럼 여러 뷰가 동시에 터치 이벤트를 처리하려 할 때 발생하는 충돌을 해결할 수 있습니다.
#2. 터치 이벤트 발생 순서
Android에서 화면을 터치할 때 발생하는 이벤트는 일정한 순서를 따릅니다. 이 순서를 정확히 이해하는 것이 터치 이벤트를 올바르게 처리하는 첫걸음입니다.
. . . . .
1) 기본 터치 이벤트 순서
사용자가 화면을 한 번 터치하고 떼는 동안 발생하는 기본 이벤트 순서는 다음과 같습니다.
| 순서 | 이벤트 | 설명 |
|---|---|---|
| 1 | ACTION_DOWN | 사용자가 화면을 처음 터치했을 때 발생 |
| 2 | ACTION_MOVE | 터치한 상태에서 손가락을 움직일 때 발생 (여러 번 연속 발생 가능) |
| 3 | ACTION_UP | 사용자가 화면에서 손가락을 뺄 때 발생 |
이 기본 순서를 코드로 확인해보겠습니다.
override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
Log.d("TouchEvent", "손가락이 화면에 닿았습니다. (ACTION_DOWN)")
// true를 반환하여 이후 이벤트도 받겠다고 선언
return true
}
MotionEvent.ACTION_MOVE -> {
Log.d("TouchEvent", "손가락이 움직입니다. (ACTION_MOVE) x: ${event.x}, y: ${event.y}")
}
MotionEvent.ACTION_UP -> {
Log.d("TouchEvent", "손가락을 화면에서 뗐습니다. (ACTION_UP)")
}
}
return super.onTouchEvent(event)
}
when (event.action) {
MotionEvent.ACTION_DOWN -> {
Log.d("TouchEvent", "손가락이 화면에 닿았습니다. (ACTION_DOWN)")
// true를 반환하여 이후 이벤트도 받겠다고 선언
return true
}
MotionEvent.ACTION_MOVE -> {
Log.d("TouchEvent", "손가락이 움직입니다. (ACTION_MOVE) x: ${event.x}, y: ${event.y}")
}
MotionEvent.ACTION_UP -> {
Log.d("TouchEvent", "손가락을 화면에서 뗐습니다. (ACTION_UP)")
}
}
return super.onTouchEvent(event)
}
중요: ACTION_DOWN에서 true를 반환해야 이후의 ACTION_MOVE와 ACTION_UP 이벤트를 받을 수 있습니다. false를 반환하면 해당 터치 시퀀스의 이후 이벤트는 더 이상 받지 않습니다.
. . . . .
2) 멀티 터치 이벤트 순서
Android는 여러 손가락으로 동시에 화면을 터치하는 멀티 터치를 지원합니다. 멀티 터치 처리는 다음과 같은 추가 이벤트 액션을 사용합니다.
| 이벤트 | 발생 시점 |
|---|---|
| ACTION_POINTER_DOWN | 두 번째 이상의 손가락이 화면에 닿았을 때 |
| ACTION_POINTER_UP | 두 번째 이상의 손가락을 화면에서 뗐을 때 |
멀티 터치 이벤트를 처리하는 코드 예시입니다.
override fun onTouchEvent(event: MotionEvent): Boolean {
// 액션 마스크로 기본 액션 추출
val action = event.actionMasked
when (action) {
MotionEvent.ACTION_DOWN,
MotionEvent.ACTION_POINTER_DOWN -> {
// 새로운 손가락이 화면에 닿음
val pointerIndex = event.actionIndex
val pointerId = event.getPointerId(pointerIndex)
val x = event.getX(pointerIndex)
val y = event.getY(pointerIndex)
Log.d("MultiTouch", "손가락 #$pointerId DOWN 이벤트 발생: ($x, $y)")
return true
}
MotionEvent.ACTION_MOVE -> {
// 모든 활성 포인터의 위치 추적
for (i in 0 until event.pointerCount) {
val pointerId = event.getPointerId(i)
val x = event.getX(i)
val y = event.getY(i)
Log.d("MultiTouch", "손가락 #$pointerId MOVE: ($x, $y)")
}
}
MotionEvent.ACTION_UP,
MotionEvent.ACTION_POINTER_UP -> {
// 손가락을 화면에서 뗌
val pointerIndex = event.actionIndex
val pointerId = event.getPointerId(pointerIndex)
Log.d("MultiTouch", "손가락 #$pointerId UP 이벤트 발생")
}
MotionEvent.ACTION_CANCEL -> {
Log.d("MultiTouch", "터치 이벤트가 취소됨")
}
}
return super.onTouchEvent(event)
}
// 액션 마스크로 기본 액션 추출
val action = event.actionMasked
when (action) {
MotionEvent.ACTION_DOWN,
MotionEvent.ACTION_POINTER_DOWN -> {
// 새로운 손가락이 화면에 닿음
val pointerIndex = event.actionIndex
val pointerId = event.getPointerId(pointerIndex)
val x = event.getX(pointerIndex)
val y = event.getY(pointerIndex)
Log.d("MultiTouch", "손가락 #$pointerId DOWN 이벤트 발생: ($x, $y)")
return true
}
MotionEvent.ACTION_MOVE -> {
// 모든 활성 포인터의 위치 추적
for (i in 0 until event.pointerCount) {
val pointerId = event.getPointerId(i)
val x = event.getX(i)
val y = event.getY(i)
Log.d("MultiTouch", "손가락 #$pointerId MOVE: ($x, $y)")
}
}
MotionEvent.ACTION_UP,
MotionEvent.ACTION_POINTER_UP -> {
// 손가락을 화면에서 뗌
val pointerIndex = event.actionIndex
val pointerId = event.getPointerId(pointerIndex)
Log.d("MultiTouch", "손가락 #$pointerId UP 이벤트 발생")
}
MotionEvent.ACTION_CANCEL -> {
Log.d("MultiTouch", "터치 이벤트가 취소됨")
}
}
return super.onTouchEvent(event)
}
참고: 멀티 터치에서는 event.action이 아닌 event.actionMasked를 사용하여 액션을 추출해야 합니다. event.action에는 액션 타입 외에도 포인터 인덱스 정보가 포함되어 있기 때문입니다.
. . . . .
3) ACTION_CANCEL 이벤트
특수한 경우에 ACTION_CANCEL 이벤트가 발생할 수 있습니다. 이는 터치 시퀀스가 비정상적으로 종료되었을 때 발생하는 이벤트입니다.
(1) ACTION_CANCEL 발생 상황
① 부모 뷰가 터치 이벤트를 가로챘을 때
② 시스템이 터치 이벤트를 취소했을 때 (예: 전화 수신)
③ 손가락이 뷰의 영역을 벗어났을 때 (특정 조건에서)
② 시스템이 터치 이벤트를 취소했을 때 (예: 전화 수신)
③ 손가락이 뷰의 영역을 벗어났을 때 (특정 조건에서)
(2) ACTION_CANCEL 처리
ACTION_CANCEL은 ACTION_UP과 동일하게 처리해야 합니다. 진행 중이던 작업을 취소하고 초기 상태로 되돌려야 합니다.
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
// 터치 종료 시 수행할 작업
animate().scaleX(1f).scaleY(1f).setDuration(100).start()
performClick()
}
// 터치 종료 시 수행할 작업
animate().scaleX(1f).scaleY(1f).setDuration(100).start()
performClick()
}
#3. MotionEvent 객체 분석
MotionEvent 객체는 터치 이벤트에 관한 모든 정보를 담고 있는 컨테이너입니다. 이 객체를 제대로 이해하면 정교한 터치 처리 로직을 구현할 수 있습니다.
. . . . .
1) MotionEvent 주요 메서드
MotionEvent 객체가 제공하는 주요 메서드는 다음과 같습니다.
| 메서드 | 설명 |
|---|---|
| getAction() | 현재 이벤트의 액션 코드 (ACTION_DOWN, ACTION_MOVE, ACTION_UP 등) |
| getX(), getY() | 터치 포인트의 X, Y 좌표 (뷰 기준) |
| getRawX(), getRawY() | 터치 포인트의 X, Y 좌표 (화면 기준) |
| getPointerCount() | 현재 터치하고 있는 손가락 수 |
| getPointerId(int) | 특정 인덱스의 포인터 ID |
| getPressure() | 터치 압력 (지원되는 기기에서만) |
| getSize() | 터치 면적 (지원되는 기기에서만) |
| getEventTime() | 이벤트 발생 시간 (밀리초) |
. . . . .
2) 좌표 시스템 이해하기
MotionEvent는 두 가지 좌표 시스템을 제공합니다.
(1) 뷰 기준 좌표 (getX, getY)
뷰의 왼쪽 상단을 원점(0, 0)으로 하는 상대 좌표입니다. 뷰 내부에서 터치 위치를 파악할 때 사용합니다. 커스텀 뷰에서 그림을 그리거나 터치 영역을 판단할 때 주로 사용됩니다.
(2) 화면 기준 좌표 (getRawX, getRawY)
화면의 왼쪽 상단을 원점(0, 0)으로 하는 절대 좌표입니다. 뷰를 드래그하여 화면상의 위치를 변경할 때 사용합니다. 상태바를 포함한 화면 전체 기준입니다.
override fun onTouchEvent(event: MotionEvent): Boolean {
val viewX = event.x
val viewY = event.y
val screenX = event.rawX
val screenY = event.rawY
Log.d("Coordinates", "뷰 기준: ($viewX, $viewY)")
Log.d("Coordinates", "화면 기준: ($screenX, $screenY)")
return super.onTouchEvent(event)
}
val viewX = event.x
val viewY = event.y
val screenX = event.rawX
val screenY = event.rawY
Log.d("Coordinates", "뷰 기준: ($viewX, $viewY)")
Log.d("Coordinates", "화면 기준: ($screenX, $screenY)")
return super.onTouchEvent(event)
}
. . . . .
3) MotionEvent 정보 추출 예시
다음은 MotionEvent에서 기본 정보를 추출하는 실전 예시입니다.
fun extractMotionEventInfo(event: MotionEvent) {
val action = event.action
val x = event.x
val y = event.y
val pressure = event.pressure
val size = event.size
val eventTime = event.eventTime
Log.d("MotionEvent", """
액션: ${getActionString(action)}
좌표: ($x, $y)
압력: $pressure
크기: $size
시간: $eventTime
""".trimIndent())
}
fun getActionString(action: Int): String {
return when (action) {
MotionEvent.ACTION_DOWN -> "ACTION_DOWN"
MotionEvent.ACTION_MOVE -> "ACTION_MOVE"
MotionEvent.ACTION_UP -> "ACTION_UP"
MotionEvent.ACTION_CANCEL -> "ACTION_CANCEL"
else -> "기타 액션: $action"
}
}
val action = event.action
val x = event.x
val y = event.y
val pressure = event.pressure
val size = event.size
val eventTime = event.eventTime
Log.d("MotionEvent", """
액션: ${getActionString(action)}
좌표: ($x, $y)
압력: $pressure
크기: $size
시간: $eventTime
""".trimIndent())
}
fun getActionString(action: Int): String {
return when (action) {
MotionEvent.ACTION_DOWN -> "ACTION_DOWN"
MotionEvent.ACTION_MOVE -> "ACTION_MOVE"
MotionEvent.ACTION_UP -> "ACTION_UP"
MotionEvent.ACTION_CANCEL -> "ACTION_CANCEL"
else -> "기타 액션: $action"
}
}
#4. 터치 이벤트 전달 메커니즘
Android에서 터치 이벤트는 뷰 계층 구조를 따라 전달됩니다. 이 과정을 이해하는 것이 복잡한 뷰 구조에서 터치 이벤트를 올바르게 처리하는 핵심입니다.
. . . . .
1) 터치 이벤트 전달 과정
터치 이벤트는 다음과 같은 단계로 전달됩니다.
(1) 이벤트 생성 및 전달
① 사용자가 화면을 터치하면 시스템이 MotionEvent 생성
② 액티비티의 dispatchTouchEvent()로 이벤트 전달
③ 액티비티는 이벤트를 최상위 뷰의 dispatchTouchEvent()로 전달
④ 이벤트는 터치 지점에 있는 가장 하위 뷰까지 전달됨 (Top-Down 방식)
⑤ 각 뷰는 이벤트를 처리할지 결정 (onTouchEvent() 반환값으로)
⑥ 하위 뷰가 처리하지 않으면 상위 뷰로 이벤트가 다시 전달됨 (Bottom-Up 방식)
② 액티비티의 dispatchTouchEvent()로 이벤트 전달
③ 액티비티는 이벤트를 최상위 뷰의 dispatchTouchEvent()로 전달
④ 이벤트는 터치 지점에 있는 가장 하위 뷰까지 전달됨 (Top-Down 방식)
⑤ 각 뷰는 이벤트를 처리할지 결정 (onTouchEvent() 반환값으로)
⑥ 하위 뷰가 처리하지 않으면 상위 뷰로 이벤트가 다시 전달됨 (Bottom-Up 방식)
(2) dispatchTouchEvent() 내부 로직
View 클래스의 dispatchTouchEvent() 메서드는 다음과 같은 순서로 이벤트를 처리합니다.
// View 클래스 내부 (Android 프레임워크의 간소화된 버전)
public boolean dispatchTouchEvent(MotionEvent event) {
boolean result = false;
// 1. OnTouchListener가 있으면 먼저 호출
if (mOnTouchListener != null && mOnTouchListener.onTouch(this, event)) {
result = true;
}
// 2. OnTouchListener에서 처리되지 않았다면 onTouchEvent() 호출
else if (onTouchEvent(event)) {
result = true;
}
return result;
}
public boolean dispatchTouchEvent(MotionEvent event) {
boolean result = false;
// 1. OnTouchListener가 있으면 먼저 호출
if (mOnTouchListener != null && mOnTouchListener.onTouch(this, event)) {
result = true;
}
// 2. OnTouchListener에서 처리되지 않았다면 onTouchEvent() 호출
else if (onTouchEvent(event)) {
result = true;
}
return result;
}
이 로직에서 알 수 있는 중요한 사실은 OnTouchListener가 onTouchEvent()보다 먼저 호출된다는 것입니다.
. . . . .
2) onTouchEvent() 메서드 구현
onTouchEvent() 메서드는 View 클래스에 정의된 메서드로, 터치 이벤트를 직접 처리할 수 있습니다. 커스텀 뷰를 만들 때 이 메서드를 오버라이드하여 터치 동작을 구현합니다.
class CustomTouchView(context: Context, attrs: AttributeSet?) : View(context, attrs) {
private var lastTouchX = 0f
private var lastTouchY = 0f
override fun onTouchEvent(event: MotionEvent): Boolean {
val x = event.x
val y = event.y
when (event.action) {
MotionEvent.ACTION_DOWN -> {
// 터치 시작 지점 저장
lastTouchX = x
lastTouchY = y
return true
}
MotionEvent.ACTION_MOVE -> {
// 이동 거리 계산
val dx = x - lastTouchX
val dy = y - lastTouchY
// 뷰 위치 이동 (예: 드래그 기능)
translationX += dx
translationY += dy
lastTouchX = x
lastTouchY = y
}
MotionEvent.ACTION_UP -> {
// 터치 완료 시 수행할 작업
performClick()
}
}
return super.onTouchEvent(event)
}
// 접근성을 위해 performClick() 오버라이드
override fun performClick(): Boolean {
super.performClick()
return true
}
}
private var lastTouchX = 0f
private var lastTouchY = 0f
override fun onTouchEvent(event: MotionEvent): Boolean {
val x = event.x
val y = event.y
when (event.action) {
MotionEvent.ACTION_DOWN -> {
// 터치 시작 지점 저장
lastTouchX = x
lastTouchY = y
return true
}
MotionEvent.ACTION_MOVE -> {
// 이동 거리 계산
val dx = x - lastTouchX
val dy = y - lastTouchY
// 뷰 위치 이동 (예: 드래그 기능)
translationX += dx
translationY += dy
lastTouchX = x
lastTouchY = y
}
MotionEvent.ACTION_UP -> {
// 터치 완료 시 수행할 작업
performClick()
}
}
return super.onTouchEvent(event)
}
// 접근성을 위해 performClick() 오버라이드
override fun performClick(): Boolean {
super.performClick()
return true
}
}
. . . . .
3) OnTouchListener 사용하기
뷰의 서브클래스를 만들지 않고도 터치 이벤트를 처리하려면 OnTouchListener를 사용할 수 있습니다.
val myView = findViewById<View>(R.id.my_view)
myView.setOnTouchListener { v, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
Log.d("TouchListener", "View가 터치되었습니다")
true
}
MotionEvent.ACTION_MOVE -> {
Log.d("TouchListener", "View 위에서 손가락이 움직입니다")
true
}
MotionEvent.ACTION_UP -> {
Log.d("TouchListener", "View에서 손가락을 뗐습니다")
v.performClick()
true
}
else -> false
}
}
myView.setOnTouchListener { v, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
Log.d("TouchListener", "View가 터치되었습니다")
true
}
MotionEvent.ACTION_MOVE -> {
Log.d("TouchListener", "View 위에서 손가락이 움직입니다")
true
}
MotionEvent.ACTION_UP -> {
Log.d("TouchListener", "View에서 손가락을 뗐습니다")
v.performClick()
true
}
else -> false
}
}
OnTouchListener는 특히 기존 뷰의 터치 동작을 확장하거나 수정할 때 유용합니다.
. . . . .
4) 터치 이벤트 충돌 해결하기
복잡한 뷰 계층에서는 여러 뷰가 터치 이벤트를 처리하려고 할 때 충돌이 발생할 수 있습니다. 예를 들어, ScrollView 안에 있는 커스텀 뷰의 제스처 처리가 스크롤과 충돌할 수 있습니다.
(1) requestDisallowInterceptTouchEvent() 사용
자식 뷰에서 부모의 이벤트 가로채기를 금지할 수 있습니다.
override fun onTouchEvent(event: MotionEvent): Boolean {
// 부모 뷰가 이 터치 이벤트를 가로채지 못하도록 설정
parent.requestDisallowInterceptTouchEvent(true)
when (event.action) {
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
// 터치 시퀀스가 끝나면 부모의 가로채기 다시 허용
parent.requestDisallowInterceptTouchEvent(false)
}
}
return super.onTouchEvent(event)
}
// 부모 뷰가 이 터치 이벤트를 가로채지 못하도록 설정
parent.requestDisallowInterceptTouchEvent(true)
when (event.action) {
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
// 터치 시퀀스가 끝나면 부모의 가로채기 다시 허용
parent.requestDisallowInterceptTouchEvent(false)
}
}
return super.onTouchEvent(event)
}
(2) onInterceptTouchEvent() 오버라이드
부모 ViewGroup에서 특정 조건에서만 이벤트를 가로채도록 설정할 수 있습니다.
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// 수평 방향 이동이 감지되면 부모가 이벤트를 가로채지 않음
if (isHorizontalScrolling(ev)) {
return false;
}
return super.onInterceptTouchEvent(ev);
}
public boolean onInterceptTouchEvent(MotionEvent ev) {
// 수평 방향 이동이 감지되면 부모가 이벤트를 가로채지 않음
if (isHorizontalScrolling(ev)) {
return false;
}
return super.onInterceptTouchEvent(ev);
}
마무리
Android에서 터치 이벤트는 다음과 같은 순서로 발생합니다.
① ACTION_DOWN: 터치 시작
② ACTION_MOVE: 손가락 이동 (여러 번 발생 가능)
③ ACTION_UP: 터치 종료
② ACTION_MOVE: 손가락 이동 (여러 번 발생 가능)
③ ACTION_UP: 터치 종료
멀티 터치의 경우 ACTION_POINTER_DOWN과 ACTION_POINTER_UP 이벤트가 추가로 발생합니다. 터치 이벤트를 올바르게 처리하면 직관적이고 반응성 좋은 사용자 인터페이스를 구현할 수 있습니다.
터치 이벤트 처리의 핵심 포인트는 다음과 같습니다.
① ACTION_DOWN에서 true를 반환해야 이후 이벤트를 받을 수 있습니다
② MotionEvent 객체는 터치의 모든 정보를 담고 있습니다
③ 터치 이벤트는 뷰 계층을 따라 Top-Down, Bottom-Up으로 전달됩니다
④ OnTouchListener는 onTouchEvent()보다 먼저 호출됩니다
⑤ 멀티 터치 처리 시 event.actionMasked를 사용해야 합니다
⑥ requestDisallowInterceptTouchEvent()로 이벤트 충돌을 해결할 수 있습니다
② MotionEvent 객체는 터치의 모든 정보를 담고 있습니다
③ 터치 이벤트는 뷰 계층을 따라 Top-Down, Bottom-Up으로 전달됩니다
④ OnTouchListener는 onTouchEvent()보다 먼저 호출됩니다
⑤ 멀티 터치 처리 시 event.actionMasked를 사용해야 합니다
⑥ requestDisallowInterceptTouchEvent()로 이벤트 충돌을 해결할 수 있습니다
복잡한 제스처나 사용자 정의 컨트롤을 만들 때는 GestureDetector와 ScaleGestureDetector 같은 도우미 클래스를 활용하면 더욱 쉽게 구현할 수 있습니다. 터치 이벤트의 발생 순서와 전달 메커니즘을 정확히 이해하고, 상황에 맞는 적절한 처리 방법을 선택하여 사용하시기 바랍니다.
#5. 자주 묻는 질문 (FAQ)
1) Q: ACTION_DOWN에서 false를 반환하면 어떻게 되나요?
A: ACTION_DOWN에서 false를 반환하면 해당 터치 시퀀스의 이후 이벤트(ACTION_MOVE, ACTION_UP)를 더 이상 받지 않습니다. 시스템은 해당 뷰가 터치 이벤트에 관심이 없다고 판단하고, 상위 뷰로 이벤트를 전달합니다. 따라서 터치 이벤트를 계속 추적하려면 반드시 ACTION_DOWN에서 true를 반환해야 합니다.
. . . . .
2) Q: getX()와 getRawX()의 차이가 무엇인가요?
A: getX()는 뷰의 왼쪽 상단을 기준으로 한 상대 좌표이고, getRawX()는 화면의 왼쪽 상단을 기준으로 한 절대 좌표입니다. 뷰 내부에서 터치 위치를 파악할 때는 getX()를 사용하고, 뷰를 화면상에서 드래그할 때는 getRawX()를 사용합니다. 예를 들어 버튼을 눌렀는지 판단할 때는 getX()를, 플로팅 버튼을 이동시킬 때는 getRawX()를 사용합니다.
. . . . .
3) Q: 멀티 터치를 처리할 때 왜 actionMasked를 사용해야 하나요?
A: event.action에는 액션 타입 외에도 포인터 인덱스 정보가 함께 인코딩되어 있습니다. 멀티 터치 상황에서 event.action을 직접 사용하면 ACTION_DOWN과 같은 상수와 정확히 일치하지 않을 수 있습니다. event.actionMasked는 포인터 인덱스 정보를 제거하고 순수한 액션 타입만 반환하므로, 멀티 터치 처리 시 반드시 사용해야 합니다.
. . . . .
4) Q: OnTouchListener와 onTouchEvent() 중 어떤 것을 사용해야 하나요?
A: 커스텀 뷰를 만들 때는 onTouchEvent()를 오버라이드하고, 기존 뷰의 동작을 확장할 때는 OnTouchListener를 사용하는 것이 일반적입니다. OnTouchListener는 onTouchEvent()보다 먼저 호출되므로, 두 가지를 함께 사용할 때는 호출 순서를 고려해야 합니다. OnTouchListener에서 true를 반환하면 onTouchEvent()는 호출되지 않습니다.
. . . . .
5) Q: ScrollView 안의 커스텀 뷰에서 터치 이벤트가 제대로 작동하지 않습니다
A: ScrollView가 터치 이벤트를 가로채기 때문입니다. 커스텀 뷰의 onTouchEvent()에서 parent.requestDisallowInterceptTouchEvent(true)를 호출하여 부모의 이벤트 가로채기를 금지할 수 있습니다. ACTION_DOWN에서 이 메서드를 호출하고, ACTION_UP이나 ACTION_CANCEL에서 다시 false로 설정하여 부모의 스크롤 기능을 복원하세요.
. . . . .
6) Q: ACTION_CANCEL은 언제 발생하나요?
A: ACTION_CANCEL은 터치 시퀀스가 비정상적으로 종료되었을 때 발생합니다. 부모 뷰가 터치 이벤트를 가로챘을 때, 시스템이 터치 이벤트를 취소했을 때(전화 수신 등), 손가락이 뷰의 영역을 벗어났을 때 등의 상황에서 발생합니다. ACTION_CANCEL은 ACTION_UP과 동일하게 처리하여 진행 중이던 작업을 취소하고 초기 상태로 되돌려야 합니다.
. . . . .
7) Q: 터치 압력과 터치 면적을 활용할 수 있나요?
A: event.getPressure()와 event.getSize()를 사용하여 터치 압력과 면적을 얻을 수 있습니다. 하지만 모든 기기에서 지원되는 것은 아닙니다. 압력 감지를 지원하는 디스플레이나 스타일러스 펜을 사용하는 경우 정확한 값을 얻을 수 있지만, 일반 터치스크린에서는 근사값만 제공됩니다. 그림 그리기 앱에서 펜 압력에 따라 선 굵기를 조절하는 등의 고급 기능 구현에 활용할 수 있습니다.
. . . . .
8) Q: GestureDetector는 언제 사용해야 하나요?
A: 더블 탭, 롱 프레스, 플링(빠른 스와이프) 같은 복잡한 제스처를 인식해야 할 때 GestureDetector를 사용합니다. 직접 구현하면 복잡한 제스처 로직을 GestureDetector가 대신 처리해주므로 코드가 훨씬 간결해집니다. 확대/축소 제스처는 ScaleGestureDetector를 사용하면 됩니다. 이미지 뷰어, 지도 앱, 리스트 스와이프 등에서 필수적으로 사용됩니다.
. . . . .
9) Q: 터치 이벤트를 처리할 때 performClick()을 왜 호출해야 하나요?
A: 접근성(Accessibility) 지원을 위해서입니다. 시각 장애인이나 모터 장애가 있는 사용자는 화면 리더나 키보드를 통해 앱을 사용할 수 있는데, performClick()을 호출하면 이러한 보조 기술이 클릭 이벤트를 감지할 수 있습니다. Android Lint는 onTouchEvent()에서 클릭 동작을 처리하면서 performClick()을 호출하지 않으면 경고를 표시합니다. ACTION_UP 처리 시 반드시 호출하세요.
. . . . .
10) Q: 여러 손가락의 터치를 개별적으로 추적하려면 어떻게 해야 하나요?
A: 포인터 ID를 사용하여 각 손가락을 추적할 수 있습니다. event.getPointerId(index)로 특정 인덱스의 포인터 ID를 얻고, 이 ID를 키로 사용하여 Map이나 SparseArray에 각 손가락의 상태를 저장합니다. 포인터 인덱스는 손가락이 추가/제거될 때마다 변경될 수 있지만, 포인터 ID는 해당 손가락이 화면에서 떨어질 때까지 변하지 않으므로 개별 추적에 적합합니다.
긴 글 읽어주셔서 감사합니다.
끝.
끝.
반응형
'Development > Android' 카테고리의 다른 글
| [Android] Android Button 텍스트 밑줄 추가하는 4가지 방법 (0) | 2019.09.22 |
|---|---|
| [Android] Android ABI 완벽 가이드 : 초보 개발자를 위한 적용 및 관리 방법 (0) | 2019.09.22 |
| [Android] Android Intent Filter 개념과 실전 구현 방법 (0) | 2019.09.10 |
| [Android] Android Fragment 동적 UI 구축과 화면 크기별 최적화 방법 (0) | 2019.09.06 |
| [Android] Android Fragment 테스트 핵심 방법 (0) | 2019.09.06 |