틀린 부분이 있다거나, 보충해야할 부분이 있다면 댓글로 말씀해주세요!
플러터를 개발하다보면, GestureDetector
를 통해 내부의 위젯이 클릭 가능하도록 구현해야하는 경우가 있다.
가령, Row
가 있고, 해당 위젯은 클릭 가능한 위젯으로 만들기 위해 GestureDetector
로 감싸져있다. 또한, Row
위젯의 mainAxisAlignment
는 SpaceBetween
이며, 해당 위젯의 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
위젯을 클릭하면 GestureDetector
의 onTap
인 debugpPrint('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.
즉, GestureDetector
의 child
가 존재한다면 behavior
는 deferToChild
로, 그렇지 않을 경우는 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
가 존재한다. 따라서, behavior
는 deferToChild
로 지정되어있을 것이며, 이론 상으로는 Row
가 차지하는 전체 영역에 대해서는 클릭 이벤트가 동작하고 이에 따라 onTap
이 동작해야한다.
그러나, mainAxisAlignment
가 spaceBetween
으로 지정된 Row
는 두 자식 Text
위젯이 내부에 위치해 있을 때, 두 위젯 사이의 비어있는 공백 영역에 대해서는 리소스 낭비 방지 등의 사유로 굳이 렌더링 해야할 이유가 없기 때문에 Row
의 영역에 포함되지 않는 것으로 추측된다. 그러니까, behavior
가 deferToChild
인 GestureDetector
는 Row
내에서 렌더링되는 두 Text
위젯만 클릭 관심 영역인 것이다. (이 부분은 더 자세히 확인해보아야 할 것 같다.)
따라서, 최종적으로 비어있는 Row
의 영역까지도 GestureDetector
의 클릭 관심 영역으로 포함시킬 필요가 있다. 참 길게 풀어서 고찰해보았는데, 이 경우 behavior
를 HitTestBehavior.opaque
또는 HitTestBehavior.translucent
로 지정해주면 된다.
본인 또한 opaque
와 translucent
의 차이는 명확히 파악하지 못 했는데, opaque
가 무엇인지 궁금하다면 도움은 안 되겠지만 본 글의 번외 목차를 살펴보자.
GestureDetector
의behavior
를HitTestBehavior.opaque
또는HitTestBehavior.translucent
로 지정해주면 된다.
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
와는 다르게 시각적으로 뒤편에 위치한(?) 영역으로 이벤트가 전달되는 것을 방지한다고 한다. 음.. 이 부분도 잘은 모르겠다.
해당 경우는 모든 영역에 대하여 굳이 HitTestBehavior.translucent
를 지정해주지 않아도 onTap
이 동작한다!
위의 이미지는 기존 GestureDetector
의 child
인 Row
위젯을 Container
로 감싸고, color
를 Colors.grey
로 지정하였을 때의 화면이다.
'문제 해결 실마리' 파트에서 Row
가 spaceBetween
일 경우, 비어있는 공간에 대해서는 굳이 렌더링 할 이유가 없기 때문에 deferToChild
가 기본 이벤트 동작 방식이었던 GestureDetector
에 의해 하위 공백 요소에 대하여는 클릭 이벤트가 동작하지 않을 것이라고 추측하였는데, 100% 확신은 어렵지만 어느 정도 맞는 것 같다. 기존 GestureDetector
의 child
였던 Row
를 Container
로 감싼 후, 컨테이너의 색상을 지정해주었더니 모든 영역에 대하여 onTap
이 동작한다.
이는 Container
의 색상을 Colors.transparent
로 지정해도 동일하게 정상 동작한다. 투명한 색상도 결국은 색상이니까..
단, Container
의 color
가 null
일 경우는 onTap
이 동작하지 않는다. 이는 상기 사유와 동일하게, 역시 비어있는 공간은 굳이 렌더링되는 영역에 포함시키지 않아도 되기 때문이지 않을까 생각한다.
끗.