본문 바로가기

Flutter

플러터 기본기 다지기 - 4 (GestureDetector 위젯)

Flutter에서의 위젯 생명 주기

Flutter에서 위젯의 생명 주기는 중요한 개념이다. 특히, StatelessWidgetStatefulWidget은 동작 방식이 다르기 때문에 각각의 생명 주기를 이해하는 것이 중요하다.

 

State 생명 주기

  1. StatelessWidget과 StatefulWidget은 빌드될 때마다 새로 생성된다.
  2. StatelessWidget은 build 메서드가 호출되면서 한 번만 생성되고 끝난다.
  3. StatefulWidget은 내부적으로 State 객체를 생성하며, 생성된 State 객체는 메모리에 유지되면서 생명 주기를 가진다.
    • 한 번 생성된 State는 재사용되며, 필요할 때만 build 메서드가 다시 호출되어 업데이트된다.

 

StatefulWidget의 생명 주기

  • StatefulWidget의 생명 주기는 다음과 같은 주요 메서드로 동작함
    • createState() : StatefulWidget에서는 createState() 메서드를 통해 State 객체를 생성해야 하며, 전체 생명 주기 중, 한 번만 호출됨
class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}
  • initState() : State 객체가 생성된 후 호출되며, 위젯 상태를 초기화하는 데 보통 사용됨. 전체 생명 주기 중, 한 번만 호출됨
  • build() : initState() 호출 후, 호출되며, 개발자가 setState()를 호출하면, build() 메서드가 재호출됨.
    • 호출 시마다, 변경된 상태를 기반으로 변경된 UI를 표현
  • dispose() : 위젯 트리에서 제거될 때 호출되며, State 객체가 영구적으로 제거되고, 사용 자원이 해제됨

 

코드 확인
import 'package:flutter/material.dart';

void main() {
  runApp(MyWidget());
}

class MyWidget extends StatefulWidget {
  @override
  _MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  int _counter = 0;

  @override
  void initState() {
    super.initState();
    print('initState called');
  }

  @override
  Widget build(BuildContext context) {
    print('build called');
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('My Widget'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text('Button tapped $_counter times.'),
              Padding(
                padding: const EdgeInsets.all(8.0),
                child: ElevatedButton(
                  child: Text('Tap me'),
                  onPressed: () {
                    setState(() {
                      _counter++;
                    });
                  },
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }

  @override
  void dispose() {
    print('dispose called'); // 위젯 제거 시, dispose() 호출됨. 이때 메모리상에 할당된 모든 자원 해제해야 함.
    super.dispose();
  }
}

 

 

 

애니매이션 코드 사용해보기
import 'package:flutter/material.dart';
import 'dart:math';

void main() {
  runApp(MyWidget());
}

class MyWidget extends StatefulWidget {
  @override
  _MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> with SingleTickerProviderStateMixin {
  int _counter = 0; // 1. 버튼이 눌린 횟수를 저장하는 변수입니다.
  Color _backgroundColor = Colors.white; // 2. 배경색을 저장하는 변수입니다.
  late AnimationController _controller; // 3. 애니메이션의 진행을 제어하는 컨트롤러입니다.
  late Animation<double> _animation; // 4. 애니메이션의 스케일 값을 저장하는 변수입니다.

  @override
  void initState() {
    super.initState();
    print('initState called'); // initState는 위젯이 처음 생성될 때 한 번 호출됩니다.

    // AnimationController 초기화
    _controller = AnimationController(
      duration: const Duration(milliseconds: 300), // 5. 애니메이션의 지속 시간을 설정합니다.
      vsync: this, // 6. vsync는 화면 새로고침 주기에 동기화하여 애니메이션 성능을 최적화합니다.
    );

    // Tween을 사용하여 애니메이션 범위 정의 (0.8 ~ 1.0)
    _animation = Tween<double>(begin: 0.8, end: 1.0).animate(CurvedAnimation(
      parent: _controller,
      curve: Curves.easeInOut, // 7. 애니메이션이 부드럽게 시작하고 끝나도록 설정합니다.
    ))
      ..addListener(() {
        setState(() {}); // 8. 애니메이션이 진행될 때마다 화면을 업데이트합니다.
      });
  }

  @override
  void dispose() {
    _controller.dispose(); // 9. 메모리 누수를 방지하기 위해 애니메이션 컨트롤러를 해제합니다.
    print('dispose called'); // dispose는 위젯이 제거될 때 한 번 호출됩니다.
    super.dispose();
  }

  // 버튼을 눌렀을 때 호출되는 함수
  void _incrementCounter() {
    setState(() {
      _counter++; // 10. 버튼이 눌릴 때마다 카운터를 증가시킵니다.
      _backgroundColor = _getRandomColor(); // 11. 배경색을 랜덤으로 변경합니다.
    });

    // 12. 애니메이션을 앞으로 진행한 후, 완료되면 원래 상태로 되돌립니다.
    _controller.forward().then((_) {
      _controller.reverse();
    });
  }

  // 랜덤 색상을 생성하는 함수
  Color _getRandomColor() {
    final random = Random();
    return Color.fromARGB(
      255,
      random.nextInt(256), // R (0~255) 랜덤 값
      random.nextInt(256), // G (0~255) 랜덤 값
      random.nextInt(256), // B (0~255) 랜덤 값
    );
  }

  @override
  Widget build(BuildContext context) {
    print('build called'); // build는 상태가 변경될 때마다 호출됩니다.
    return MaterialApp(
      theme: ThemeData(
        useMaterial3: true, // 13. Material 3 스타일을 적용합니다.
        colorSchemeSeed: Colors.blue, // 14. Material 3 컬러 테마의 기본 색상을 지정합니다.
      ),
      home: Scaffold(
        appBar: AppBar(
          title: Text('Animated Counter'),
        ),
        backgroundColor: _backgroundColor, // 15. 배경색을 설정합니다.
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text(
                '버튼을 누른 횟수 : $_counter 번', // 16. 버튼이 눌린 횟수를 텍스트로 표시합니다.
                style: TextStyle(fontSize: 20),
              ),
              SizedBox(height: 20),
              // 17. Transform.scale 위젯으로 버튼의 크기를 애니메이션 효과로 조절합니다.
              Transform.scale(
                scale: _animation.value, // 18. 애니메이션 스케일 값을 적용합니다.
                child: ElevatedButton(
                  style: ElevatedButton.styleFrom(
                    padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
                    backgroundColor: Colors.blueAccent, // 19. 버튼 배경색 설정
                  ),
                  child: Text('눌러 보기', style: TextStyle(fontSize: 18)),
                  onPressed: _incrementCounter, // 20. 버튼을 누르면 _incrementCounter 함수 호출
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

 

코드 설명

  1. AnimationController 초기화: _controller는 애니메이션의 타이밍을 제어하는 객체로, duration을 통해 애니메이션 속도를 조절하고, vsync는 SingleTickerProviderStateMixin을 통해 화면 새로고침 주기에 동기화하여 성능을 최적화한다.
  2. Tween 객체 설정: Tween<double>(begin: 0.8, end: 1.0)은 애니메이션이 시작될 때는 0.8배의 크기에서 시작해, 1.0배 크기로 변하는 애니메이션 효과를 제공한다.
  3. Transform.scale 위젯: scale 속성에 애니메이션 값을 적용해 버튼의 크기가 점점 커졌다가 다시 원래대로 돌아오도록 설정한다.

Flutter에서 애니메이션을 효율적으로 관리하기 위해 vsync와 SingleTickerProviderStateMixin을 사용한다

1. vsync: this의 역할

  • vsync는 "Vertical Synchronization"의 약자로, 애니메이션이 화면의 새로고침 주기와 동기화되도록 도와준다.
  • 목적: 애니메이션이 화면에 보이지 않거나 필요하지 않은 경우, 불필요하게 자원을 사용하지 않도록 애니메이션을 자동으로 멈춰준다.
  • 효과: 화면 주기와 맞춰 애니메이션이 실행되므로 CPU와 GPU의 자원 사용을 줄여 효율적으로 동작한다.

2. SingleTickerProviderStateMixin의 역할

  • Ticker는 Flutter에서 애니메이션을 위한 핵심 요소로, 화면을 여러 번 새로 그려 애니메이션을 부드럽게 보여준다.
  • SingleTickerProviderStateMixin은 Ticker를 한 번만 제공하여, 애니메이션이 필요할 때만 활성화하고 그렇지 않을 때는 비활성화한다.
  • 장점: 애니메이션이 없는 상황에서 Ticker가 불필요하게 작동하는 것을 방지해, 메모리와 자원을 절약한다.

 

왜 SingleTickerProviderStateMixin을 써야 할까요?

애니메이션은 Ticker의 도움으로 화면을 반복해서 새로 그리며 동작합니다. 하지만 필요하지 않은 상황에서도 Ticker가 작동하면 자원을 낭비하게 되죠. SingleTickerProviderStateMixin을 사용하면 필요할 때만 Ticker가 작동하도록 관리할 수 있어, 애니메이션을 효율적으로 실행할 수 있습니다.

 

728x90