[Flutter] Transform Widget

Uno·2024년 1월 15일
2

flutter

목록 보기
12/16

동기

Flutter Animation을 직접 만들고 싶었다. 그래서 자료를 살펴보는데, "Matrix4"를 알아야 했다. 이것은 행렬이다. 이 행렬을 위젯으로 만들면 Transform Widget이라고 한다. 그래서 먼저 TransformWidget을 사용해보면서 감을 잡고자 이 글을 작성한다.

나는 아래와 같은 애니메이션을 직접 만들고 싶었다.

출처: loading_animation_widget package

Transform Widget이란?

"transforms" 은 형태나 사이즈 위치를 변경하는 것을 뜻한다. 그것을 Widget을 감싸서 처리하면 된다.
cf) flutter Docs: Transform class

공식문서에서는 아래와 같이 정의하고 있다.

A widget that applies a transformation before painting its child.
하위 위젯을 그리기 전에, 형태를 변형하도록 해주는 위젯

그리고 생성자를 보면 끝이다. 아래 gif를 보면 단박에 이해가 될 것이다.

  • Transform.flip

출처: Medium: Flutter:flip animation


  • Transform.rotate

(출처: Medium: A Deep Dive Into Transform Widgets in Flutter)



  • Transform.scale

(출처: Medium: A Deep Dive Into Transform Widgets in Flutter)


  • Transform.translate: 이 친구는 그냥 이동이다.


간단 예제: rotate

간단하게 예제를 만들어보자.

import 'package:flutter/material.dart';  
  
void main() {  
  runApp(  
    MaterialApp(  
      title: 'My app', // used by the OS task switcher  
      home: const MyApp(),  
    ),  
  );  
}  
  
class MyApp extends StatefulWidget {  
  const MyApp({super.key});  
  
    
  State<MyApp> createState() => _MyAppState();  
}  
  
class _MyAppState extends State<MyApp> {  
  
    
  void initState() {  
    super.initState();  
  }  
  
    
  Widget build(BuildContext context) {  
    return Scaffold(  
      body: Center(  
        child: Transform.rotate(  
          angle: 1.0,  
          origin: Offset(0.0, 0.0),  
          child: Container(  
            height: 100.0,  
            width: 100.0,  
            color: Colors.indigo,  
          ),  
        ),  
      ),  
    );  
  }  
}

이 코드를 실행해보면, 아래와 같다.

잘 돌아가있지만, 심심하다. 그렇다면 animation을 불어넣어주면 된다.

이렇게 회전시키는 것은 AnimationController와 Aniation 을 사용하여 구현할 수 있다.

제일 먼저, 두 참조변수를 선언한다.

class _MyState extends State<MyApp> {
	// 1. Controller & Aniation 선언
	late AnimationController _controller;
	late Animation<double> _animation;
}

이제 참조변수에 값을 전달한다. 이것은 initState 시점에 한다. 이 때, "SingleTickerProviderStateMixin" 를 조합한다. (mixin 이라 확장이라는 용어 대신 조합)

class _MyState extends State<MyApp> with SingleTickerProviderStateMixin {
	...

	
	void initState() {
		super.initState();
		
		_controller = AnimationController(
			duration: const Duration(seconds: 2),
			vsync: this,
		);
		
		_animation = Tween<double>(begin: 0.0, end: 2 * pi)
			.animate(controller)
			..addListener(() {
				setState(() {});
			})
			..addStatusListener((state) {
				if (status == AnimationStatus.completed) {
					_controller.repeate();
					_controller.forward();
				}
			});
		_controller.forward();
	}
}
  • SingleTickerProviderStateMixin : 애니메이션을 시작하기 위해서는 '티커' 라는 개념이 필요하다. 그 티커가 동작하도록 하게 해주는 mixin이다.
    (간단하게 생각하면, animation 동작에 필요하다.)
  • vsync : vsync에 보면 this를 건내주고 있다. 이로써. Animation을 그릴 때, 프레임에 맞게 그릴 수 있도록 되는 것이다.
  • 이후에 _animation에 할당한 코드를 보면 "Tween" 이라는 객체로 할당하고 있다. 이것은 시작지점과 끝지점만 지정해주고 이것을 animation 하는 위젯에 전달한다. 그러면 마치 총알을 쏘듯 저기에 있는 값들을 입력해준다.
  • Tween이라는 것은 선형보간법을 뜻한다. 어렵게 말해서 선형보간법이지, 간단하게 생각하면 그냥 두점 사이를 직진으로 이어버리는 것이다.
  • 0부터 2pi 까지 선형적으로 input이 전달된다. 2pi인 이유는 2pi Radian = 360도 이다.

이후 코드는 dispose 시점에 controller를 메모리 해제하는 것과 Transform.rotate에 돌아갈 각도에다가 _anmation을 전달하면 된다.

전체코드

import 'dart:math';  
  
import 'package:flutter/material.dart';  
  
void main() {  
  runApp(  
    MaterialApp(  
      title: 'My app', // used by the OS task switcher  
      home: const MyApp(),  
    ),  
  );  
}  
  
class MyApp extends StatefulWidget {  
  const MyApp({super.key});  
  
    
  State<MyApp> createState() => _MyAppState();  
}  
  
class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {  
  // 1. Controller와 Animation을 선언한다.  
  late AnimationController _controller;  
  late Animation<double> _animation;  
  
    
  void initState() {  
    super.initState();  
    // 2. 선언한 참조변수들에게 값을 할당한다.  
    // 이 때, with 를 사용하여 mixin을 조합한다.(extends 옆에 추가)  
    _controller = AnimationController(  
      duration: const Duration(seconds: 2),  
      vsync: this, // with를 통해 기능을 조합하면 this를 사용할 수 있다.  
    );  
    _animation = Tween<double>(begin: 0.0, end: pi *2).animate(_controller)  
      ..addListener(() {  
        setState(() {});  
      })  
      ..addStatusListener((status) {  
        if (status == AnimationStatus.completed) {  
          _controller.repeat();  
          // _controller.forward();  
        }  
      });    
    void initState() {  
      super.initState();  
      // 2. 선언한 참조변수들에게 값을 할당한다.  
      // 이 때, with 를 사용하여 mixin을 조합한다.(extends 옆에 추가)  
      _controller = AnimationController(  
        duration: const Duration(seconds: 2),  
        vsync: this, // with를 통해 기능을 조합하면 this를 사용할 수 있다.  
      );  
      _animation = Tween<double>(begin: 0.0, end: pi *2).animate(_controller)  
        ..addListener(() {  
          setState(() {});  
        })  
        ..addStatusListener((status) {  
          if (status == AnimationStatus.completed) {  
            _controller.repeat();  
            _controller.forward();  
          }  
        });  
      _controller.forward();  
    }  
  
    _controller.forward();  
  }  
  
    
  void dispose() {  
    // 3. Controller를 dispose 해준다.  
    _controller.dispose();  
    super.dispose();  
  }  
  
    
  Widget build(BuildContext context) {  
    return Scaffold(  
      body: Center(  
        child: Transform.rotate(  
          angle: _animation.value,  
          origin: Offset(0.0, 0.0),  
          child: Stack(  
            children: [  
              Container(  
                height: 100.0,  
                width: 100.0,  
                color: Colors.indigo,  
              ),  
              CircleAvatar(  
                radius: 10.0,  
                backgroundColor: Colors.red,  
              ),  
            ],  
          ),  
        ),  
      ),  
    );  
  }  
}

추후 사용방법

  • 지금은 한 개의 애니메이션만 사용하고 있어서 큰 의미가 없어 보일 수 있다.
  • 하지만, 여러 개의 Transform을 동시에 적용이 가능하다.

Widget build(BuildContext context) {
  return AnimatedBuilder(
    animation: _controller,
    builder: (context, child) {
      return Transform(
        alignment: Alignment.center,
        transform: Matrix4.identity()
          ..rotateZ(_rotationAnimation.value) // <-
          ..scale(_scaleAnimation.value), // <--
        child: Container(
          width: 200,
          height: 200,
          color: Colors.blue,
          child: FlutterLogo(),
        ),
      );
    },
  );
}
  • 앞으로 진행할 애니메이션의 근간이 되는 위젯이므로 잘 알아두는 것이 필요해 보인다 :)
profile
iOS & Flutter

0개의 댓글