본문 바로가기
flutter/Widget of the Week

Flutter[플러터] / InteractiveViewer를 사용하여 자식 위젯과의 팬 및 확대/축소 상호작용을 가능하게 하기 (터치, 드래그, 줌 인/아웃 기능, 인터랙티브뷰어) InteractiveViewer (Flutter Widget of the Week)

by ch5c 2025. 7. 15.
반응형

InteractiveViewer class

자식 위젯과의 팬 및 확대/축소 상호작용을 가능하게 하는 위젯입니다.

사용자는 드래그하여 팬 하거나 핀치 하여 확대/축소하여 어린이를 변형할 수 있습니다.

https://youtu.be/zrn7V3bMJvg

공식 문서 코드

 


폰으로 뉴스 같은 글자가 작고 많은 것을 볼 때 우리는 화면을 확대하여 크기를 키우곤 한다. 그러한 동작을 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를 사용하여 InteractiveViewertransformationController파라미터 안에 넣어서 그 확대 값을 조정할 수 있다. 바로 만들어보자.

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);
  });
}

이제 만든 함수들을 버튼에서 호출시켜 주면 완성이다. 나는 ScaffoldfloatingActionButton파라미터를 사용했다.

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

반응형