InteractiveViewer class
자식 위젯과의 팬 및 확대/축소 상호작용을 가능하게 하는 위젯입니다.
사용자는 드래그하여 팬 하거나 핀치 하여 확대/축소하여 어린이를 변형할 수 있습니다.
공식 문서 코드
폰으로 뉴스 같은 글자가 작고 많은 것을 볼 때 우리는 화면을 확대하여 크기를 키우곤 한다. 그러한 동작을 Flutter내부에서 구현하려면 꽤 성가실 것 같은 기분이 든다. 하지만 이러한 줌 인/아웃 동작을 아주 간단하게 실행해 주 켜는 위젯이 있는데 그것이 바로 InteractiveViewer 위젯이다.
이 InteractiveViewer 위젯은 사용자가 터치 제스처(예: 확대/축소, 드래그, 회전 등)를 통해 UI 요소와 상호작용할 수 있도록 해주는 위젯으로 주로 이미지나 복잡한 도면, 지도, 큰 레이아웃을 표시할 때 사용된다. 바로 알아보자.
하위 속성
| 속성명 | 타입 | 기본값 | 설명 |
| child | Widget? | – | 확대 및 이동이 적용될 자식 위젯 |
| builder | InteractiveViewerWidgetBuilder? | null | 변환 상태에 따라 동적으로 자식을 생성할 수 있는 빌더 함수 |
| clipBehavior | Clip | Clip.hardEdge | 위젯이 자식 위젯을 얼마나 클리핑할지를 결정 |
| panAxis | PanAxis | PanAxis.free | 패닝 가능한 방향을 설정 (자유, 수평, 수직, 정렬 등) |
| boundaryMargin | EdgeInsets | EdgeInsets.zero | 자식 위젯을 이동할 수 있는 경계 마진 |
| constrained | bool | true | 자식 위젯에 상위 위젯의 제약을 적용할지 여부 |
| panEnabled | bool | true | 사용자가 패닝(이동)할 수 있는지 여부 |
| scaleEnabled | bool | true | 사용자가 확대/축소할 수 있는지 여부 |
| trackpadScrollCausesScale | bool | false | 트랙패드 스크롤 동작이 확대/축소로 이어질지 여부 |
| scaleFactor | double | 200.0 | 스크롤 이벤트에 따른 확대 비율 |
| maxScale | double | 2.5 | 최대 확대 배율 |
| minScale | double | 0.8 | 최소 확대 배율 |
| interactionEndFrictionCoefficient | double | 0.0000135 | 패닝 후 관성 애니메이션의 마찰 계수 |
| onInteractionStart | GestureScaleStartCallback? | null | 사용자가 확대 또는 이동을 시작할 때 호출되는 콜백 |
| onInteractionUpdate | GestureScaleUpdateCallback? | null | 사용자가 확대 또는 이동 중일 때 호출되는 콜백 |
| onInteractionEnd | GestureScaleEndCallback? | null | 사용자가 확대 또는 이동을 마쳤을 때 호출되는 콜백 |
| transformationController | TransformationController? | null | 현재 변환 상태를 제어하고 추적하는 컨트롤러 |
| alignment | Alignment? | null | 자식 위젯의 기준점을 InteractiveViewer 내 어디에 둘지 지정 |
일단 이 InteractiveViewer 위젯을 사용하기 위해선 상호작용할 위젯을 child 파라미터에 넣어줘야 한다. 나는 네트워크 이미지를 예로 들어 사용했다.
InteractiveViewer(
child: Image.network('https://docs.flutter.dev/assets/images/dash/Dash.png'),
)
이렇게 하면 놀랍게도 바로 완성이다. 바로 줌 인/아웃을 해보자.

정말 쉽고 간단한데 바로 동작이 잘 된다.
근데 이제 여기서 줌 인/아웃을 막아야 할 상황이 생길 수도 있다. 그럴 땐 바로 scaleEnabled파라미터의 값을 false로 설정해 주게 되면 효과적으로 줌 인/아웃 동작이 기능을 하지 않게 된다.
InteractiveViewer(
scaleEnabled: false,
child: Image.network('https://docs.flutter.dev/assets/images/dash/Dash.png'),
)
근데 굳이 왜 이처럼 동작을 막는 것일까. 답은 간단하다. 확대/축소 버튼 같은 것으로 크기 조절을 일정하게 조정하려고 막는 것이다. 그렇다면 필연적으로 확대 값을 조정할 수 있을 것이다. 어떻게 조정할 수 있을까?
바로 TransformationController를 사용하여 InteractiveViewer의 transformationController파라미터 안에 넣어서 그 확대 값을 조정할 수 있다. 바로 만들어보자.
final TransformationController _controller = TransformationController();
가장 먼저 트랜스포메이션컨트롤러를 _controller로 인스턴스화시켜 준다.
그 후 사용은 방금 말했던 것처럼 transformationController파라미터 안에 넣어주면 된다.
InteractiveViewer(
transformationController: _controller,
scaleEnabled: false,
child: Image.network('https://docs.flutter.dev/assets/images/dash/Dash.png'),
)
이렇게만 해줘도 기본적인 설정은 끝난 것이다. 이제 완벽하게 컨트롤러에 의해서만 줌 인/아웃 기능이 동작될 것이다.
그렇다면 이제 실질적인 동작을 하는 함수를 만들어줘야 할 것이다.
먼저 값을 조절할 변수를 하나 만들어준다.
double _scale = 1.0;
이제 이 값을 가지고 조정해 줄 것이다.
바로 함수를 만들어 줄 것인데 먼저 확대 동작부터 만들어 주겠다.
난 20%씩 확대하게 제작할 것이기 때문에 _scale *= 1.2를 해주면 간단하게 값을 조절할 수 있다.
_scale *= 1.2;
이제 그다음은 실질적으로 컨트롤러에 바뀐 값을 반영을 해줘야 하는데 Matrix4.identity()를 사용해 주면 된다.
Matrix4.identity()은 기본 행렬 (확대/회전/이동 없음)을 생성해 주는데 거기다 .scale(_scale)을 적용하면, 그 비율만큼 확대되게 된다. 즉, 지금까지 확대했던 누적 값을 반영한 행렬을 직접 세팅해 주게 되는 것이다.
_controller.value = Matrix4.identity()..scale(_scale);
이제 이러면 완성이다.
void _zoomIn() {
setState(() {
_scale *= 1.2;
_controller.value = Matrix4.identity()..scale(_scale);
});
}
똑같이 축소 함수도 하나 만들어준다. 이건 뭐 *만 /으로 바꿔주면 된다.
void _zoomOut() {
setState(() {
_scale /= 1.2;
_controller.value = Matrix4.identity()..scale(_scale);
});
}
이제 만든 함수들을 버튼에서 호출시켜 주면 완성이다. 나는 Scaffold의 floatingActionButton파라미터를 사용했다.
floatingActionButton: Column(
mainAxisSize: MainAxisSize.min,
children: [
FloatingActionButton(
onPressed: _zoomIn,
child: Icon(Icons.add),
),
SizedBox(height: 10),
FloatingActionButton(
onPressed: _zoomOut,
child: Icon(Icons.remove),
),
],
)

위의 GIF에서 보이는 것처럼 직접 손가락으로 줌 인/ 아웃하는 동작은 안 먹는 반변에 확대/축소 버튼을 누르면 잘 동작하는 것을 볼 수 있다. 이제 여기에서 추가로 드래그도 막을 수 있다. 근데 굳이 이것까지..?
panEnabled: false,
또한 이러한 동작의 여부와 관련된 파라미터 말고도 boundaryMargin 파라미터를 사용하여 자식 위젯을 이동할 수 있는 경계 마진을 설정해 줄 수도 있고
boundaryMargin: EdgeInsets.all(20),

확대가 좀 덜 된다 싶으면 최대 확대 배율을 높여줄 수도 있다.
maxScale: 10,
이렇게 해주면 최대 10배까지 확대 되게 된다. (기본값: 2.5)

또한 당연하지만 최대가 있으면 최소 배율도 있다. (기본값: 0.8)
minScale: 1,
이제 아주 중요한 것이다. 아까 위의 GIF를 보면 확대/축소 버튼으로 줌 인/아웃을 할 때 왼쪽 상단(0, 0)에서부터 줌 인/아웃 동작이 실행되는 것을 볼 수 있는데 이 동작이 실행되는 위치를 바꿔줄 수 있다. 바로 alignment 파라미터로 말이다.
InteractiveViewer(
scaleEnabled: false,
alignment: Alignment.center,
transformationController: _controller,
child: Image.network('https://docs.flutter.dev/assets/images/dash/Dash.png'),
)
이렇게만 바꿔줘도 아까보다 훨씬 동작이 자연스러워질 것이다. (근데 특이점은 중심으로 설정하니까 아예 왼쪽 상단은 드래그로 접근조차 안 된다. 즉, 건드리지 마라)

이렇게 InteractiveViewer 위젯을 사용하여 줌 인/아웃 기능을 간단하게 구현해 보았다. 이 위젯을 사용해 보니 상상 이상으로 활용도가 높을 것 같은 느낌적인 느낌이 드는 것 같다. 암튼 도움이 되었길 바라며 마치겠다.