[Flutter] GestureDetector 비어 있는 영역도 터치 가능하도록 하기 - HitTestBehavior

정지현·2023년 10월 1일
1
post-thumbnail

틀린 부분이 있다거나, 보충해야할 부분이 있다면 댓글로 말씀해주세요!

플러터를 개발하다보면, GestureDetector 를 통해 내부의 위젯이 클릭 가능하도록 구현해야하는 경우가 있다.

문제 상황

가령, Row 가 있고, 해당 위젯은 클릭 가능한 위젯으로 만들기 위해 GestureDetector 로 감싸져있다. 또한, Row 위젯의 mainAxisAlignmentSpaceBetween 이며, 해당 위젯의 children 에는 두 개의 자식 위젯(Text 위젯 두 개)이 존재한다.

이럴 경우, 다음과 같은 형태가 될 것이다. (해당 Row 위젯의 영역을 표현하기 위해 GestureDetector 를 파란색 색상을 지닌Container 로 감쌌으며, Row 위젯 내의 두 텍스트 위젯의 영역을 표현하기 위해 각각 노란색, 흰색 Container 로 감쌌다.)

코드는 다음과 같다.

Container(
          color: Colors.blue,
          /*
            관심 영역 (GestureDetector 의 클릭 범위)
           */
          child: GestureDetector(
              onTap: () {
                debugPrint('Tapped!');
              },
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  Container(
                    color: Colors.yellow,
                    child: const Text('left'),
                  ),
                  Container(
                    color: Colors.white,
                    child: const Text('right'),
                  ),
                ],
              )),
        ),

자! 이제 Row 위젯을 클릭하면 GestureDetectoronTapdebugpPrint('Tapped!') 가 호출될 것으로 기대된다.

문제 발생

그러나, Row 내의 두 Text 위젯을 클릭할 때는 정상적으로 onTap 함수가 호출되지만, Row 내의 두 자식 위젯 간 비어있는 영역을 클릭하면 어떠한 동작도 발생하지 않는다. 어떻게 해결해야할까?

문제 해결 실마리

이는, GestureDetector 의 기본 behavior 와 관련이 있다. 해당 API 의 behavior 내부 주석을 확인해보자.

This defaults to [HitTestBehavior.deferToChild] if [child] is not null
and [HitTestBehavior.translucent] if child is null.

즉, GestureDetectorchild 가 존재한다면 behaviordeferToChild 로, 그렇지 않을 경우는 translucent 로 동작한다고 한다.

그렇다면 deferToChild 는 무엇일까? 이 역시 내부 API 를 확인해보면,

Targets that defer to their children receive events within their bounds
only if one of their children is hit by the hit test.

확인해보면, 자식 위젯이 클릭 영역에 해당하는 경우에만 이벤트가 활성화된다고 한다.

상단의 설명에서 등장하는 translucent 도 역시 확인해보면,

Translucent targets both receive events within their bounds and permit
targets visually behind them to also receive events.

반투명한 이벤트 타겟(GestureDetector 내에 위치한 자식 위젯 정도가 되겠다 .) 이 클릭 가능한 영역에 대하여 클릭 이벤트를 전달받고, 시각적으로 뒤쪽에 위치한(?) 영역까지 이벤트를 수신한다고 되어있는데, 사실 해당 부분이 정확히 어떤 역할을 하는 것인지는 잘 모르겠다.

즉, 정리해보자면, 상기 코드에서는 GestureDetector 내에서 child 로 지정한 Row 가 존재한다. 따라서, behaviordeferToChild 로 지정되어있을 것이며, 이론 상으로는 Row 가 차지하는 전체 영역에 대해서는 클릭 이벤트가 동작하고 이에 따라 onTap 이 동작해야한다.

그러나, mainAxisAlignmentspaceBetween 으로 지정된 Row 는 두 자식 Text 위젯이 내부에 위치해 있을 때, 두 위젯 사이의 비어있는 공백 영역에 대해서는 리소스 낭비 방지 등의 사유로 굳이 렌더링 해야할 이유가 없기 때문에 Row 의 영역에 포함되지 않는 것으로 추측된다. 그러니까, behaviordeferToChildGestureDetectorRow 내에서 렌더링되는 두 Text 위젯만 클릭 관심 영역인 것이다. (이 부분은 더 자세히 확인해보아야 할 것 같다.)

따라서, 최종적으로 비어있는 Row 의 영역까지도 GestureDetector 의 클릭 관심 영역으로 포함시킬 필요가 있다. 참 길게 풀어서 고찰해보았는데, 이 경우 behaviorHitTestBehavior.opaque 또는 HitTestBehavior.translucent 로 지정해주면 된다.

본인 또한 opaquetranslucent 의 차이는 명확히 파악하지 못 했는데, opaque 가 무엇인지 궁금하다면 도움은 안 되겠지만 본 글의 번외 목차를 살펴보자.

문제 해결

GestureDetectorbehaviorHitTestBehavior.opaque 또는 HitTestBehavior.translucent 로 지정해주면 된다.

번외

HitTestBehavior.opaque

translucent 와 비슷한 역할을 하는 또 다른 Enum 도 존재하는데, opaque 이다. 이 역시 확인해보면,

Opaque targets can be hit by hit tests, causing them to both receive
events within their bounds and prevent targets visually behind them from
also receiving events.

투명한 이벤트 타겟에 대하여 클릭 가능한 영역에 대하여 클릭 이벤트를 전달받고, translucent 와는 다르게 시각적으로 뒤편에 위치한(?) 영역으로 이벤트가 전달되는 것을 방지한다고 한다. 음.. 이 부분도 잘은 모르겠다.

mainAxisAlignment 가 spaceBetween 인 Row 를 Container 로 감싸고 색상을 지정해준다면?

해당 경우는 모든 영역에 대하여 굳이 HitTestBehavior.translucent 를 지정해주지 않아도 onTap 이 동작한다!

위의 이미지는 기존 GestureDetectorchildRow 위젯을 Container 로 감싸고, colorColors.grey 로 지정하였을 때의 화면이다.

'문제 해결 실마리' 파트에서 RowspaceBetween 일 경우, 비어있는 공간에 대해서는 굳이 렌더링 할 이유가 없기 때문에 deferToChild 가 기본 이벤트 동작 방식이었던 GestureDetector 에 의해 하위 공백 요소에 대하여는 클릭 이벤트가 동작하지 않을 것이라고 추측하였는데, 100% 확신은 어렵지만 어느 정도 맞는 것 같다. 기존 GestureDetectorchild 였던 RowContainer 로 감싼 후, 컨테이너의 색상을 지정해주었더니 모든 영역에 대하여 onTap 이 동작한다.

이는 Container 의 색상을 Colors.transparent 로 지정해도 동일하게 정상 동작한다. 투명한 색상도 결국은 색상이니까..

단, Containercolornull 일 경우는 onTap 이 동작하지 않는다. 이는 상기 사유와 동일하게, 역시 비어있는 공간은 굳이 렌더링되는 영역에 포함시키지 않아도 되기 때문이지 않을까 생각한다.

끗.

profile
나를 성장시키는 좌절에 감사하고 즐기려고 노력 중

0개의 댓글