개발노트

67. ValueListenableBuilder 로 변동되는 값을 가진 특정 위젯만 리빌드하기 (with ValueNotifier) 본문

앱 개발/Flutter

67. ValueListenableBuilder 로 변동되는 값을 가진 특정 위젯만 리빌드하기 (with ValueNotifier)

mroh1226 2024. 2. 16. 15:46
반응형

ValueListenableBuilder

ValueListenableBuilder는 Flutter 프레임워크에서 제공하는 위젯 중 하나로, 값이 변경될 때마다 자동으로 UI를 갱신하는 데 사용됩니다. 이 위젯은 값이 변경될 때마다 새로운 위젯 트리를 생성하는 대신 기존의 위젯 트리를 업데이트하여 효율적으로 화면을 갱신합니다.

ValueListenableBuilder는 세 가지 중요한 매개변수를 갖습니다:

  1. valueListenable: 값을 감시하는 ValueListenable입니다. 이 값이 변경될 때마다 ValueListenableBuilder는 자동으로 자식 위젯을 재빌드합니다.
  2. builder: 실제 위젯 트리를 생성하는 데 사용되는 빌더 함수입니다. 이 빌더 함수는 값이 변경될 때마다 호출되며, 현재 값과 빌드 컨텍스트를 매개변수로 받습니다.
  3. child: 선택적 매개변수로, 고정된 자식 위젯을 제공할 수 있습니다. 이 자식 위젯은 값이 변경될 때마다 새로 생성되는 대신 재사용됩니다.

ValueListenableBuilder는 보통 ValueNotifier나 AnimationController 등의 클래스와 함께 사용됩니다. 이러한 클래스들은 값의 변화를 감지할 수 있는 ValueListenable을 구현합니다. 예를 들어, ValueNotifier는 값이 변경될 때마다 리스너에게 알리고, AnimationController는 애니메이션 값의 변화를 감시할 수 있습니다.


 

구현 예제.

  • 동그란 원을 Drag 하여 원하는 위치로 이동하는 기능을 만들어 봅니다.
  • GestureDetector의  onPanUpdate(details) 로 가져온 x,y축 값을 ValueNotifier<Offset>에 실시간 저장합니다.
  • ValueListenableBuilder 를 사용하여 child: 원의  x, y 축 값을 계속 감시하고 원을 나타내는 자식 위젯만 재빌드 시킵니다.
  • 이외 애니메이션 기능
    • Transform.translate의 offset: 속성값에 ValueNifier<Offset>을 넣어주고 값 변화에 따른 이동 애니메이션 구현
    • Drag가 Start 되었을 때 Scale이 1.5배 커지고, End 되었을 때 원래 크기로 돌아오는 애니메이션 구현
    • Drag가 Start 되고 원의 크기 Scale이 다 켜졌다면 푸른색(인디고 색상)으로 변하고, Drag가 End 되었을 때 다시 원래 색상(핑크색)으로 돌아오는 애니메이션 구현

Drag되는 원의 좌표를 감시하는&nbsp; ValueListenableBuilder


예제 소스.

import 'package:flutter/material.dart';

class TestScreen extends StatefulWidget {
  const TestScreen({super.key}); // 생성자

  @override
  State<TestScreen> createState() => _TestScreenState(); // 상태를 생성하는 createState 메서드
}

class _TestScreenState extends State<TestScreen>
    with SingleTickerProviderStateMixin {
  late final AnimationController _animationController = AnimationController(
    vsync: this, // vsync에 현재 상태 객체를 제공하여 애니메이션을 위한 ticker를 제공
    duration: const Duration(milliseconds: 300), // 애니메이션 지속 시간
  );
  late final Animation<double> _scale =
      Tween(begin: 1.0, end: 1.5).animate(_animationController); // 크기 변환 애니메이션
  late final ValueNotifier<Offset> _dxy = ValueNotifier(Offset.zero); // 드래그한 위치를 관리하는 ValueNotifier

  void _onPanStart(DragStartDetails details) {
    _animationController.forward(); // 드래그가 시작되면 애니메이션을 시작
  }

  void _onPanUpdate(DragUpdateDetails details) {
    _dxy.value = Offset( // 드래그 이벤트가 발생할 때마다 드래그한 위치를 업데이트
      _dxy.value.dx + details.delta.dx,
      _dxy.value.dy + details.delta.dy,
    );
  }

  void _onPanEnd(DragEndDetails details) {
    _animationController.reverse(); // 드래그가 끝나면 애니메이션을 역방향으로 재생
  }

  @override
  Widget build(BuildContext context) {
    final size = MediaQuery.of(context).size; // 현재 화면의 크기
    return Scaffold( // 화면의 기본 레이아웃 구조를 제공하는 Scaffold
      body: Center( // 자식 위젯을 화면의 가운데에 배치하는 Center 위젯
        child: ValueListenableBuilder( // ValueNotifier를 청취하고 해당 값이 변경될 때마다 리빌드
          valueListenable: _dxy, // 드래그한 위치를 업데이트하기 위한 ValueNotifier
          builder: (context, value, child) => GestureDetector( // 사용자의 터치 동작을 감지하는 GestureDetector
            onPanStart: _onPanStart, // 드래그가 시작되면 호출되는 콜백
            onPanUpdate: _onPanUpdate, // 드래그 이벤트가 업데이트될 때 호출되는 콜백
            onPanEnd: _onPanEnd, // 드래그가 끝날 때 호출되는 콜백
            child: Transform.translate( // 자식 위젯을 이동시키는 Transform.translate
              offset: _dxy.value, // 드래그한 위치에 따라 자식 위젯을 이동
              child: ScaleTransition( // 자식 위젯에 크기 변환을 적용하는 ScaleTransition
                scale: _scale, // 크기 변환 애니메이션
                child: Container( // 자식 위젯을 장식하는 Container
                  alignment: Alignment.center, // 자식 위젯을 가운데로 정렬
                  height: 100, // 높이
                  width: 100, // 너비
                  decoration: BoxDecoration( // 자식 위젯의 장식
                    color: _animationController.status == AnimationStatus.completed // 애니메이션 상태에 따라 배경색 변경
                        ? Colors.indigo // 애니메이션이 완료된 경우의 배경색
                        : Colors.pink, // 애니메이션이 완료되지 않은 경우의 배경색
                    shape: BoxShape.circle, // 원형 모양으로 설정
                  ),
                  child: Text( // 드래그한 위치를 표시하는 Text 위젯
                      '(${_dxy.value.dx.ceil()} , ${_dxy.value.dy.ceil()})'),
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

소스설명.

GestureDetector

  • onPanUpdate: Drag 되는 동안 호출되며 x, y 축 값을 제공합니다. (DragUpdateDetails)
  • onPanStart: Drag가 시작될 때 호출되며, 위에서 정의한_onPanStart() 메소드의 _animationController.forward()를 통해  애니메이션을 실행 시켜줍니다.(원의 스케일 커짐, 푸른색 색상으로 변함)
  • onPanEnd: Drag가 종료되었을 때 호출되며, 위에서 정의한  _onPanStart() 메소드의 _animationController.reverse()를 통해  애니메이션을 반대로 실행 시켜줍니다. (원래 원의 스케일로 돌아옴, 원래 색상인 핑크색으로 돌아옴)

*onHorizontalDragUpdate: ,onVerticalDragUpdate: 이 아닌 onPan 을 사용한 이유:

  • 각각 x축, y축 값만 제공하고 2개다 사용한다고 해도 메소드 호출이 동시에 이루어지지 않았습니다.
  • 가로로 먼저 Drag한경우에는 onVerticalDragUpdate 이 호출되지않아 y축 값이 변동되지않았으며,
    세로로 먼저 Drag한 경우에는 onHorizontalDragUpdate 이 호출되지않아 x축 값 변동이 이루어지지않았습니다.

 

ValueListenableBuilder 

  • ValueListenableBuilder 로 Container(원)를 감싸주고 ValueListenable 에 감시할 값을 넣어줍니다.
    (감시할 값은 ValueNotifier<Offset>  _dxy 로 Drag된 x, y 축값을 나타냅니다.)
  • ValueListenableBuilder는 ValueNotifier를 감시하고 해당 값이 변경될 때마다 자동으로 리빌드합니다.
    (_dxy ValueNotifier를 사용하여 드래그한 위치를 업데이트합니다.)

 

 

이외 애니메이션 위젯들

  • Transform.translate 위젯은 자식 위젯을 이동시키는 데 사용됩니다. 여기서는 _dxy.value와 _animationController.value를 곱하여 드래그한 위치에 따라 자식 위젯이 이동하도록 설정됩니다.
  • ScaleTransition은 자식 위젯에 크기 변환을 적용합니다. 여기서는 _scale 애니메이션을 사용하여 자식 위젯에 크기 변환을 적용합니다.

 

반응형
Comments