반응형
배경
- Flutter를 개발하다 보면, 터치, 클릭, 제스처 이벤트를 처리하기 위해 GestureDetector, InkWell과 같은 위젯을 자주 사용하게 됩니다. 하지만 이러한 위젯을 사용했음에도 이벤트가 동작하지 않는 경우가 종종 발생합니다. 이럴 때 반드시 확인해야 할 핵심 요소가 바로 Hit Test입니다.
Hit Test
- 사용자의 터치나 클릭이 발생했을 때, 화면의 어떤 위젯(Widget)이 해당 이벤트를 처리해야 하는지 결정하는 과정
- 즉, 터치 포인트에서 "누가 이 이벤트를 받을 것인가?"를 찾는 과정입니다.
Hit Test 이벤트 과정
- 이벤트 발생
- 사용자가 화면을 터치하거나 마우스를 클릭하면, Flutter는 해당 좌표(x, y)를 기록합니다.
- 위젯 트리 순회 시작
- Flutter는 위젯 트리의 최상위부터 시작하여 하위로 내려가며 각 위젯이 해당 좌표를 포함하는지 확인합니다.
- 경계 검사 (Bounds Checking)
- 각 위젯의 hitTest() 메서드가 호출되어 다음을 확인합니다.
- 터치 포인트가 위젯의 경계(bounds) 내에 있는지
- 위젯이 현재 활성화되어 있는지
- 위젯이 터치 이벤트를 받을 수 있는 상태인지
- 각 위젯의 hitTest() 메서드가 호출되어 다음을 확인합니다.
- 자식 위젯 검사
- 상위 위젯이 터치 포인트를 포함한다면, 그 자식 위젯들도 동일한 과정을 거칩니다. 이는 재귀적으로 진행됩니다.
- Hit Test 결과 수집
- 터치 포인트를 포함하는 모든 위젯들이 HitTestResult 리스트에 추가됩니다.
- 일반적으로 가장 위에 있는(z-order 상 앞에 있는) 위젯이 우선순위를 갖습니다.
- 이벤트 디스패치
- Hit Test 결과를 바탕으로 적절한 위젯에게 터치 이벤트가 전달됩니다.
Hit Test가 중요한 이유
- 정확한 사용자 상호작용
- 사용자가 의도한 위젯이 터치 이벤트를 정확히 받을 수 있도록 보장합니다.
- 잘못된 Hit Test는 사용자 경험을 크게 해칠 수 있습니다.
- 성능 최적화
- 효율적인 Hit Test는 불필요한 이벤트 처리를 방지하여 앱의 성능을 향상시킵니다.
- 모든 위젯을 검사하는 대신 필요한 부분만 검사합니다.
- 복잡한 UI 구조 처리
- 스택(Stack), 오버레이(Overlay), 투명한 위젯 등이 겹쳐있는 복잡한 UI에서도 올바른 위젯이 이벤트를 받을 수 있게 합니다.
- 제스처 인식
- 단순한 탭뿐만 아니라 드래그, 핀치, 스와이프 등 복잡한 제스처도 올바른 위젯에서 인식될 수 있도록 합니다.
- 접근성 지원
- 스크린 리더나 기타 접근성 도구들이 올바른 위젯을 식별할 수 있도록 도와줍니다.
- 디버깅 및 개발
- Hit Test 결과를 통해 개발자는 UI 상호작용 문제를 디버깅하고 해결할 수 있습니다.
대표적인 예제
Center(
child: GestureDetector(
onTap: () => print('Tapped!'),
child: Container(
height: 40,
width: 200,
alignment: Alignment.center,
child: Text("눌러보세요"),
),
),
)
- 위 예제의 경우, Text 위젯은 Hit Test에서 true가 반환되어 터치 이벤트가 발생하는 반면에, Container 위젯은 Hit Test 에서 false가 반환되어 터치 이벤트가 발생하지 않습니다.
- 따라서 텍스트 영역만 터치 가능하고, Container의 빈 공간은 터치되지 않습니다.
- Container는 decoration이나 color가 있을 때만 hitTest 통과가 됩니다.
- Container의 hitTest는 다음과 같습니다.
@override
bool hitTest(BoxHitTestResult result, {required Offset position}) {
if (_decoration != null || _color != null) { // decoration이나 color가 있을 때만 hitTest 통과
if (super.hitTest(result, position: position)) {
return true;
}
}
return hitTestChildren(result, position: position);
}
- 따라서, 위 예제에서 Container 위젯에서 발생한 터치 이벤트를 GestureDetector에서 감지하기 위해서는 Container 위젯에 color 속성이나 decoration 속성을 추가해줘야 합니다.
Center(
child: GestureDetector(
onTap: () => print('Tapped!'),
child: Container(
height: 40,
width: 200,
color: Colors.transparent, // 이 한 줄 추가!
alignment: Alignment.center,
child: Text("눌러보세요"),
),
),
)
HitTestBehavior
- HitTestBehavior.deferToChild (기본값)
- 자식이 hit test에서 true가 반환되면, 이벤트 처리가 됩니다.
- 자식이 없으면 이벤트가 발생하지 않습니다.
- HitTestBehavior.opaque
- 항상 hit test를 통과합니다.
- 배경 없는 컨테이너도 터치 가능하게 만들고 싶을 때 사용합니다.
- HitTestBehavior.translucent
- 항상 hit test를 통과하지만, 하위 위젯으로도 이벤트 전파됩니다.
- 부모와 자식 모두 제스처를 감지해야 할 때 사용합니다.
- 단, 자식 이벤트가 먼저 발생합니다.
반응형