본문 바로가기
App/Flutter

Flutter Animation 만들기 (with Provider)

by Day0404 2024. 4. 2.
728x90
반응형

이번 글은 Flutter를 통해 애니메이션을 그리는 방법에 대해 작성하려고 합니다.

Flutter에서는 애니메이션을 구현하게 도와주는 강력한 도구를 제공합니다. 시각적인 요소들을 부드럽게 이동하거나 변경하고 화면 전환을 자연스럽게 만드는 등 다양한 환경에서 사용됩니다.

 

Flutter 애니메이션의 주요 개념

1. Animation 클래스

Flutter 애니메이션을 활용하고 제어하기 위한 기본 추상 클래스입니다. Animation 클래스는 애니메이션의 현재 값 및 상태를 추적하고, 애니메이션을 조작합니다. 

 

2. AnimationController

Animation 클래스를 생성하고 제어하는 데 사용됩니다. 이 클래스에서 애니메이션의 지속 시간, 시작 및 중지, 방향 등을 제어하는 데 사용됩니다. 일반적으로 애니메이션 컨트롤러는 StatefulWidget의 initState 메서드에서 생성되고, dispose 메서드에서 해제됩니다. 애니메이션의 시작과 종료 지점을 정의하고 애니메이션의 지속 시간을 설정합니다. AnimationController는 Animation 객체를 생성하고 애니메이션을 플레이하거나 중지하는 메서드를 제공합니다.

 

3. Tween

시작 값과 종료 값 사이의 값을 애니메이션화하는데 사용됩니다. Tween은 AnimationContorller의 값을 변경하여 애니메이션을 생성합니다. 일반적으로 Tween은 애니메이션 도중에 변경되는 값의 범위를 정의합니다. Tween은 주로 Animation 객체와 함께 사용되며, Animation 객체에는 Tween이 정의한 범위 내의 값을 보간(interpolate, 주어진 두 가지 값 사이에서 새로운 값을 생성하는 과정)하여 애니메이션을 생성하는 메서드가 있습니다.

 

4. Curves

Curves는 애니메이션의 보간 방법을 지정하는 클래스입니다. 일반적으로 애니메이션이 진행되는 동안 속도를 조절하기 위해 사용되며 다양한 Curves를 사용하여 애니메이션의 속도를 조절하거나 특정한 움직임 패턴을 만들 수 있습니다. Flutte에는 기본적으로 여러 가지 표준 Curves가 내장되어 있으며, 필요에 따라 커스텀 Curves를 만들 수도 있습니다.

 

Flutter 애니메이션 종류

Flutter에서 지원하는 애니메이션 종류는 아래와 같습니다.

 

1. Implicit 애니메이션

Implicit 애니메이션은 변경된 속성에 따라 자동으로 애니메이션을 적용하는 애니메이션입니다. 예를 들어 AnimatedContainer 와 AnimatedOpacity는 부모 위젯에서 속성이 변경될 때 애니메이션을 자동으로 적용합니다.

 

2. Explicit 애니메이션

Explicit 애니메잇녀은 AnimationController와 함께 사용되어 직접적으로 애니메이션을 제어합니다. 직접적인 제어를 통해 복잡한 애니메이션을 만들 수 있습니다.

 

3. Hero 애니메이션

Hero 애니메이션은 두 개의 화면 사이에서 위젯의 전환을 부드럽게 만들어주는 애니메이션입니다. 주로 페이지 전환 시에 사용됩니다.

 

4. Custom 애니메이션

Custom 애니메이션은 사용자가 직접 만든 애니메이션으로, CustomPainter, AnimationController 및 Animation 클래스를 사용하여 생성됩니다.

 

StatefulWidget과 Provider 패턴을 사용한 애니메이션 예제

기본적으로 애니메이션은 StatefulWidget 클래스를 상속하여 만들 수 있습니다. 그러나 이것은 애플리케이션의 규모가 커질수록 상태관리의 어려움이 있기 때문에 저는 이것을 Provider에 적용시켜 봤습니다.

 

Directory 구조

디렉토리 구조는 간단하게 위 와 같이 만들었습니다.

 

../widgets/stateful_animation_widget.dart

import 'package:flutter/material.dart';

class StatefulAnimationWidget extends StatefulWidget {
  const StatefulAnimationWidget({super.key});

  @override
  State<StatefulAnimationWidget> createState() => _StatefulAnimationWidgetState();
}

class _StatefulAnimationWidgetState extends State<StatefulAnimationWidget> with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;

  bool _isExpanded = false;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 500),
    );
    _animation = Tween<double>(begin: 100, end: 200).animate(_controller);
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  void _toggleExpanded() {
    setState(() {
      _isExpanded = !_isExpanded;
    });
    if (_isExpanded) {
      _controller.forward();
    } else {
      _controller.reverse();
    }
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: _toggleExpanded,
      child: AnimatedBuilder(
        animation: _animation,
        builder: (context, child) {
          return Container(
            width: _animation.value,
            height: _animation.value,
            color: _isExpanded ? Colors.red : Colors.green,
            alignment: Alignment.center,
            child: Text(
              _isExpanded ? 'Expanded' : 'Collapsed',
              style: const TextStyle(color: Colors.white),
            ),
          );
        },
      ),
    );
  }
}

기본적으로 StatefulWidget을 상속하여 만든 간단한 토글 애니메이션입니다. 위젯만 가져다 쓸 수 있도록 위젯화 시켜서 만들었고 박스를 클릭하면 커지고 작아지는 토글 박스입니다.

 

../provider/animation_provider.dart

import 'package:flutter/material.dart';

class AnimationProvider with ChangeNotifier {
  late AnimationController _controller;
  late Animation<double> _animation;
  bool _isExpanded = false;

  AnimationController get controller => _controller;
  Animation<double> get animation => _animation;
  bool get isExpanded => _isExpanded;

  AnimationProvider(TickerProvider vsync) {
    _controller = AnimationController(
      duration: const Duration(milliseconds: 500),
      vsync: vsync,
    );
    _animation = Tween<double>(begin: 100, end: 200).animate(_controller);
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
    notifyListeners();
  }

  void toggleExpanded() {
    if (_isExpanded) {
      _controller.reverse();
    } else {
      _controller.forward();
    }
    _isExpanded = !_isExpanded;
    notifyListeners();
  }
}

Provider 패턴을 사용하여 애니메이션을 만들려면 Provider를 정의해야 합니다. StatefulWidget에서는 SingleTickerProviderStateMixin 클래스를 mixin을 통해 가져올 수 있었는데 Provider의 경우 이미 with ChangeNotifier로 mixin을 사용했으므로 두 개 이상의 mixin을 사용할 수 없습니다. 그래서 Provider에서는 TickerProvider 클래스를 인터페이스 해야 하는데 상속과 인터페이스를 같이 사용하는 것은 지양해야 하기 때문에 저는 따로 TickerProvider를 상속받는 TickerProviderImpl 클래스를 만들어 사용했습니다.

 

../provider/ticker_provider_impl.dart

import 'package:flutter/scheduler.dart';

class TickerProviderImpl extends TickerProvider {
  @override
  Ticker createTicker(TickerCallback onTick) {
    return Ticker(onTick);
  }
}

 

main.dart

import 'package:flutter/material.dart';
import 'package:flutter_animation_example/provider/animation_provider.dart';
import 'package:flutter_animation_example/provider/ticker_provider_impl.dart';
import 'package:flutter_animation_example/screens/animation_screen.dart';
import 'package:provider/provider.dart';

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (_) => AnimationProvider(
        TickerProviderImpl(),
      ),
      child: const MaterialApp(
        home: AnimationScreen(),
      ),
    ),
  );
}

 

 

../widgets/provider_animation_widget.dart

import 'package:flutter/material.dart';
import 'package:flutter_animation_example/provider/animation_provider.dart';
import 'package:provider/provider.dart';

class ProviderAnimationWidget extends StatelessWidget {
  const ProviderAnimationWidget({super.key});

  @override
  Widget build(BuildContext context) {
    return Consumer<AnimationProvider>(
      builder: (context, animationProvider, child) {
        return GestureDetector(
          onTap: () {
            animationProvider.toggleExpanded();
          },
          child: AnimatedBuilder(
            animation: animationProvider.controller,
            builder: (context, child) {
              return Container(
                width: animationProvider.animation.value,
                height: animationProvider.animation.value,
                color: animationProvider.isExpanded ? Colors.red : Colors.green,
                alignment: Alignment.center,
                child: Text(
                  animationProvider.isExpanded ? 'Expanded' : 'Collapsed',
                  style: const TextStyle(color: Colors.white),
                ),
              );
            },
          ),
        );
      },
    );
  }
}

 

../screens/animation_screen.dart

import 'package:flutter/material.dart';

import '../widgets/provider_animation_widget.dart';
import '../widgets/stateful_animation_widget.dart';

class AnimationScreen extends StatelessWidget {
  const AnimationScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Animation Example'),
        centerTitle: true,
      ),
      body: const Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ProviderAnimationWidget(),
            Text('Provider Example'),
            SizedBox(height: 20),
            StatefulAnimationWidget(),
            Text('StatefulWidget Example'),
          ],
        ),
      ),
    );
  }
}

 

위 코드를 실행시키면 아래와 같은 화면이 나옵니다.

 

각 각 클릭해 보면 토글버튼을 통해 애니메이션이 잘 작동하는 것을 확인할 수 있습니다

 

간단한 Flutter Animation을 만들어보고 이것을 Provider에 적용시키는 것까지 해봤습니다. 너무 기본적인 애니메이션 기능이라 다음에는 제가 직접 커스텀을 하거나 많이 사용하는 애니메이션을 만들고 디자인 패턴에 적용하여 실제 프로젝트에도 사용할 수 있게 글을 써보도록 하겠습니다.

반응형

댓글