Flutter - Hit Test
2025. 7. 24. 18:16
반응형

배경

  • Flutter를 개발하다 보면, 터치, 클릭, 제스처 이벤트를 처리하기 위해 GestureDetector, InkWell과 같은 위젯을 자주 사용하게 됩니다. 하지만 이러한 위젯을 사용했음에도 이벤트가 동작하지 않는 경우가 종종 발생합니다. 이럴 때 반드시 확인해야 할 핵심 요소가 바로 Hit Test입니다.

 

 

 

Hit Test

  • 사용자의 터치나 클릭이 발생했을 때, 화면의 어떤 위젯(Widget)이 해당 이벤트를 처리해야 하는지 결정하는 과정
  • 즉, 터치 포인트에서 "누가 이 이벤트를 받을 것인가?"를 찾는 과정입니다.

 

 

 

Hit Test 이벤트 과정

  1. 이벤트 발생
    • 사용자가 화면을 터치하거나 마우스를 클릭하면, Flutter는 해당 좌표(x, y)를 기록합니다.
  2. 위젯 트리 순회 시작
    • Flutter는 위젯 트리의 최상위부터 시작하여 하위로 내려가며 각 위젯이 해당 좌표를 포함하는지 확인합니다.
  3. 경계 검사 (Bounds Checking)
    • 각 위젯의 hitTest() 메서드가 호출되어 다음을 확인합니다.
      • 터치 포인트가 위젯의 경계(bounds) 내에 있는지
      • 위젯이 현재 활성화되어 있는지
      • 위젯이 터치 이벤트를 받을 수 있는 상태인지
  4. 자식 위젯 검사
    • 상위 위젯이 터치 포인트를 포함한다면, 그 자식 위젯들도 동일한 과정을 거칩니다. 이는 재귀적으로 진행됩니다.
  5. Hit Test 결과 수집
    • 터치 포인트를 포함하는 모든 위젯들이 HitTestResult 리스트에 추가됩니다.
    • 일반적으로 가장 위에 있는(z-order 상 앞에 있는) 위젯이 우선순위를 갖습니다.
  6. 이벤트 디스패치
    • Hit Test 결과를 바탕으로 적절한 위젯에게 터치 이벤트가 전달됩니다.

 

 

 

Hit Test가 중요한 이유

  1. 정확한 사용자 상호작용
    • 사용자가 의도한 위젯이 터치 이벤트를 정확히 받을 수 있도록 보장합니다.
    • 잘못된 Hit Test는 사용자 경험을 크게 해칠 수 있습니다.
  2. 성능 최적화
    • 효율적인 Hit Test는 불필요한 이벤트 처리를 방지하여 앱의 성능을 향상시킵니다.
    • 모든 위젯을 검사하는 대신 필요한 부분만 검사합니다.
  3. 복잡한 UI 구조 처리
    • 스택(Stack), 오버레이(Overlay), 투명한 위젯 등이 겹쳐있는 복잡한 UI에서도 올바른 위젯이 이벤트를 받을 수 있게 합니다.
  4. 제스처 인식
    • 단순한 탭뿐만 아니라 드래그, 핀치, 스와이프 등 복잡한 제스처도 올바른 위젯에서 인식될 수 있도록 합니다.
  5. 접근성 지원
    • 스크린 리더나 기타 접근성 도구들이 올바른 위젯을 식별할 수 있도록 도와줍니다.
  6. 디버깅 및 개발
    • 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

  1. HitTestBehavior.deferToChild (기본값)
    • 자식이 hit test에서 true가 반환되면, 이벤트 처리가 됩니다.
    • 자식이 없으면 이벤트가 발생하지 않습니다.
  2. HitTestBehavior.opaque
    • 항상 hit test를 통과합니다.
    • 배경 없는 컨테이너도 터치 가능하게 만들고 싶을 때 사용합니다.
  3. HitTestBehavior.translucent
    • 항상 hit test를 통과하지만, 하위 위젯으로도 이벤트 전파됩니다.
    • 부모와 자식 모두 제스처를 감지해야 할 때 사용합니다.
    • 단, 자식 이벤트가 먼저 발생합니다.
반응형
jkh2801
jkh2801
jkh2801 개발 노트