14 분 소요

Resolving conflicts


1

개발자 1과 2가 같은 버전에서 기반하여 브랜치를 생성해서 같은 파일을 수정하고 push하려고 할 때 병합 충돌이 발생한다. 병합 충돌이 어떤 브랜치에서 발생했는지 보기 위해 다음 커맨드를 실행한다. git log --merge (병합된 커밋에 관한 정보만 표시)

commit 79bca730b68e5045b38b96bec35ad374f44fe4e3 (HEAD -> feature2)
Author: Developer 2 
<developer2@demo.com>
Date:   Sat Jan 29 16:55:40 2022 +0000

    chore: add feature 2

commit 678b0648107b7c53e90682f2eb8103c59f3cb0c0
Author: Developer 1 
<developer1@demo.com>
Date:   Sat Jan 29 16:53:40 2022 +0000

    chore: add feature 1

여기서 feature 1과 2 브랜치에서 충돌이 발생했음을 알 수 있다. 다음으로 변경된 코드 중 어느 부분이 충돌을 일으켰는지 보기 위해 git diff 명령어를 사용한다.

git diff

diff --cc Feature.js
index 1b1136f,c3be92f..0000000
--- a/Feature.js
+++ b/Feature.js
@@@ -1,4 -1,4 +1,8 @@@
  let add = (a, b) => {
++<<<<<<< HEAD
 +  if(a + b > 10) { return 'way too much'}
++=======
+   if(a + b > 10){ return 'too much' }
++>>>>>>> d3b3cc0d9b6b084eef3e0afe111adf9fe612898e
    return a + b;
  }
  • index 1b1136f,c3be92f..0000000

    이 부분은 파일의 해시(고유 식별자)를 나타낸다. 변경된 버전(1b1136f)과 변경 후 버전(c3be92f)의 해시를 비교한다.

  • -- a/Feature.js: 변경 전 파일을 나타내며, a/Feature.js는 원본 파일의 경로를 나타낸다.
  • +++ b/Feature.js: 변경 후 파일을 나타내며, b/Feature.js는 변경된 파일의 경로를 나타낸다.
  • <<<<<<< HEAD: 이 부분은 현재 작업 중인 브랜치의 변경 내용을 나타냅니다. HEAD는 현재 작업 중인 브랜치를 가리킨다.
  • ======= 는 HEAD 브랜치와 다른 브랜치의 변경 내용을 구분하는 구분선이다.
  • d3b3cc0d9b6b084eef3e0afe111adf9fe612898e 는 다른 브랜치의 변경 내용을 나타내며, d3b3cc0d9b6b084eef3e0afe111adf9fe612898e는 해당 브랜치의 커밋 해시를 나타낸다.

Mixin


Mixin은 클래스에 추가할 수 있는 재사용 가능한 메서드, 속성 또는 다른 코드 블록들의 집합을 말한다.

코드 예시로는 여러 종류의 동물원을 관리하는 프로그램을 만든다고 가정해보자. 각 동물들은 공통적인 기능을 가지고 있지만, 각자의 특별한 특성도 가지고 있다. 이런 경우에 mixin을 사용하여 코드의 재사용성을 높일 수 있다.

// 기본 동물 클래스
class Animal {
  String name;
  
  Animal(this.name);
  
  void makeSound() {
    print('동물 소리');
  }
}

// 비행 기능을 가진 mixin
mixin Flyable {
  void fly() {
    print('날기');
  }
}

// 수영 기능을 가진 mixin
mixin Swimmable {
  void swim() {
    print('수영하기');
  }
}

// 비둘기 클래스: 동물 클래스를 확장하면서 Flyable mixin을 mixin
class Pigeon extends Animal with Flyable {
  Pigeon(String name) : super(name);
}

// 고래 클래스: 동물 클래스를 확장하면서 Swimmable mixin을 mixin
class Whale extends Animal with Swimmable {
  Whale(String name) : super(name);
}

기본 동물이라는 객체를 설계할 때 위의 Animal이라는 클래스를 보면 name이라는 속성과 makeSound 라는 메서드를 가지도록 했다.

그리고 비행 기능을 가진 mixin과 수영 기능을 가진 mixin을 만들고 비둘기 클래스와 고래 클래스를 만들 때 Animal이라는 클래스를 상속(비둘기, 고래 둘 다 동물이므로 동물이 가진 속성을 또 선언하지 않아도 사용할 수 있다)하고 비둘기는 Flyable이라는 mixin을 추가하고 고래는 Swimmable이라는 mixin을 추가해 다음과 같이 사용할 수 있다.

void main() {
  var pigeon = Pigeon('비둘기');
  pigeon.makeSound(); // 동물 소리
  pigeon.fly();       // 날기

  var whale = Whale('고래');
  whale.makeSound();  // 동물 소리
  whale.swim();       // 수영하기
}

비둘기와 고래 인스턴스를 생성했고 Animal 클래스의 method 뿐만 아니라 각 동물의 특성에 맞는 mixin을 사용할 수 있음을 알 수 있다.

Flutter Animation

Essential animation concepts and classes


What’s the point?

  • Animation, a core class in Flutter’s animation library, interpolates the values used to guide an animation.
  • An Animation object knows the current state of an animation (for example, whether it’s started, stopped, or moving forward or in reverse), but doesn’t know anything about what appears onscreen.
  • An AnimationController manages the Animation.
  • CurvedAnimation defines progression as a non-linear curve.
  • Tween interpolates between the range of data as used by the object being animated. For example, a Tween might define an interpolation from red to blue, or from 0 to 255.
  • Use Listeners and StatusListeners to monitor animation state changes.

  • interpolate(보간하다)는 추정의 한 유형(estimation)으로 알려진 이산 데이터의 범위를 기반으로 새로운 데이터 포인트를 구성(찾는) 방법을 말한다.

    애니메이션에서는 애니메이션의 시작지점과 끝지점의 중간 값을 계산하고 시간에 따라 부드럽게 변경함으로써 애니메이션을 만들 수 있다.

    2

  • 애니메이션 객체는 현재 애니메이션 상태는 알고 있지만 화면에 나타나는 내용에 대해서는 아무것도 알지 못한다.
  • CurvedAnimation은 진행을 비선형 곡선으로 정의한다”

    이 문장을 뜯어보면, ‘진행’이란 애니메이션의 진행 상태를 의미하는 것이고 ‘비선형’은 애니메이션의 진행 속도가 일정하지 않게 변하는 것을 의미하며 “곡선”이라는 의미는 애니메이션의 진행 상태와 시간의 관계가 직선이 아니라 곡선 형태로 정의됨을 의미함.

Animation


In Flutter, an Animation object knows nothing about what is onscreen. An Animation is an abstract class that understands its current value and its state (completed or dismissed). One of the more commonly used animation types is Animation<double>. 여기서 Animation 은 현재의 값과 상태(완료 또는 끝)를 이해하는 abstract class이다. 이 때, abstract class란 직접 인스턴스(객체)를 생성할 수 없으며, 다른 클래스들이 공통으로 가져야 하는 특성과 동작을 정의하는 데 사용된다. 즉, Animation 는 위와 같은 특성을 가지고 있으므로 Animation를 상속받는 구체적인 클래스를 만들어서 원하는 애니메이션을 정의하고 사용한다.

An Animation object sequentially generates interpolated numbers between two values over a certain duration. The output of an Animation object might be linear, a curve, a step function, or any other mapping you can devise. Depending on how the Animation object is controlled, it could run in reverse, or even switch directions in the middle.

Animation 객체는 일정한 시간동안 두 값 사이의 중간 값을 순차적으로 생성한다.

Animations can also interpolate types other than double, such as Animation<Color> or Animation<Size>.

애니메이션은 숫자뿐 아니라 색깔과 크기와 같은 다른 유형도 보간할 수 있다.

An Animation object has state. Its current value is always available in the .value member.

애니메이션 객체는 상태를 가지고 있다. 현재 값은 항상 .value 멤버에서 사용할 수 있다.

An Animation object knows nothing about rendering or build() functions.

애니메이션 객체는 렌더링이나 build() 함수에 대해서 전혀 알지 못한다.

AnimationController


AnimationController is a special Animation object that generates a new value whenever the hardware is ready for a new frame. By default, an AnimationController linearly produces the numbers from 0.0 to 1.0 during a given duration. For example, this code creates an Animation object, but does not start it running:

애니메이션을 초단위로 나눠서 생각해보면 불연속적인 화면을 시간의 흐름에 따라 이어가면서 부드럽게 구현한다고 생각하면 된다. 따라서 AnimationController 는 애니메이션이 재생되는 시간 동안 잘게 나눠서 시간마다 생성할 화면이 준비될 때마다 새로운 값을 생성하는 특수한 애니메이션 객체라고 생각하면 된다.

예를 들어서, 아래 코드에서 2초 동안 애니메이션을 진행하는데 2초 동안 0.0에서 1.0까지의 숫자를 선형으로 생성한다.

controller =
    AnimationController(duration: const Duration(seconds: 2), vsync: this);

AnimationController derives from Animation<double>, so it can be used wherever an Animation object is needed. However, the AnimationController has additional methods to control the animation. For example, you start an animation with the .forward() method. The generation of numbers is tied to the screen refresh, so typically 60 numbers are generated per second. After each number is generated, each Animation object calls the attached Listener objects. To create a custom display list for each child, see RepaintBoundary.

여기서 숫자의 생성에 대한 이야기가 무슨 말인지 튕길 수 있는데, 화면 주사율을 생각해보면 된다. 주사율은 1초당 화면이 갱신되는 횟수를 뜻하는데, 60Hz라는 말은 1초 동안 화면을 60단계로 쪼개서 보여줄 수 있다는 의미다. 위에서 AnimationController 는 화면이 갱신될 때마다 숫자를 생성한다고 했다. 따라서 숫자가 생성된다는 것은(0.0 ~ 1.0) 화면이 갱신되면서 애니메이션이 진행되고 있다는 것을 뜻한다.

When creating an AnimationController, you pass it a vsync argument. The presence of vsync prevents offscreen animations from consuming unnecessary resources. You can use your stateful object as the vsync by adding SingleTickerProviderStateMixin to the class definition. You can see an example of this in animate1 on GitHub.

위 코드에서 vsync 키워드가 생소할 텐데, 작은 장난감 자동차 애니메이션을 상상해보자. 내 노트북 스크린은 초당 60번 화면을 갱신하는데, 자동차는 초당 200번 화면을 갱신한다. 즉, 스크린과 자동차의 화면 주사율이 다르기 때문에 스크린의 화면이 갱신되는 동안 화면 밖에서 자동차 애니메이션이 더 많은 갱신을 일으키고 있어 불필요한 리소스를 소비하게 된다.

요약하자면, vsync 는 애니메이션을 화면 주사율에 맞추어 실행하여 화면 밖에서 실행되는 애니메이션의 무분별한 리소스 소비를 방지하는 것이라고 생각할 수 있다.

Tween


AnimationController 객체는 0.0에서 1.0까지의 범위를 가짐. 다른 범위나 다른 데이터 유형이 필요하다면, Tween 객체를 사용하여 애니메이션을 다른 범위나 다른 데이터 유형으로 보간하도록 구성할 수 있음.

tween = Tween<double>(begin: -200, end: 0);

Tween 은 오직 begin 와 end 파라미터를 갖는 상태가 없는 객체임. Tween 의 주요 역할은 입력 범위에 따른 출력 범위로 계산하여 반환하는 것이다.

TweenAnimation 대신 Animatable<T>에서 상속받는다. AnimatableAnimation처럼 반드시 double을 출력할 필요는 없다. 예를 들어, ColorTween은 두 개의 색상 사이의 진행을 지정합니다.

colorTween = ColorTween(begin: Colors.transparent, end: Colors.black54);

Tween 객체는 어떠한 상태도 저장하지 않고 대신 애니메이션의 현재 값(0.0 과 1.0 사이)을 실제 애니메이션 값으로 대응하여 반환하는 transform  함수를 사용하는 evaluate 함수를 제공한다.

Animation notifications


An Animation object can have Listeners and StatusListeners, defined with addListener() and addStatusListener().

  • Listener is called whenever the value of the animation changes. The most common behavior of a Listener is to call setState() to cause a rebuild.
  • StatusListener is called when an animation begins, ends, moves forward, or moves reverse, as defined by AnimationStatus.

Animation examples

Rendering animations

기본 예제

import 'package:flutter/material.dart';

void main() => runApp(const LogoApp());

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

  @override
  State<LogoApp> createState() => _LogoAppState();
}

class _LogoAppState extends State<LogoApp> {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        margin: const EdgeInsets.symmetric(vertical: 10),
        height: 300,
        width: 300,
        child: const FlutterLogo(),
      ),
    );
  }
}

애니메이션을 추가한 예제

State<LogoApp> createState() => _LogoAppState();            
                                 }            
                    -            class _LogoAppState extends State<LogoApp> {            
                    +            class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {            
                    +              late Animation<double> animation;            
                    +              late AnimationController controller;            
                    +                        
                    +              @override            
                    +              void initState() {            
                    +                super.initState();            
                    +                controller =            
                    +                    AnimationController(duration: const Duration(seconds: 2), vsync: this);            
                    +                animation = Tween<double>(begin: 0, end: 300).animate(controller)            
                    +                  ..addListener(() {            
                    +                    setState(() {            
                    +                      // The state that has changed here is the animation object's value.            
                    +                    });            
                    +                  });            
                    +                controller.forward();            
                    +              }            
                    +                        
                                   @override            
                                   Widget build(BuildContext context) {            
                                     return Center(            
                                       child: Container(            
                                         margin: const EdgeInsets.symmetric(vertical: 10),        
                    +                    height: animation.value,            
                    +                    width: animation.value,            
                                         child: const FlutterLogo(),            
                                       ),            
                                     );            
                                   }            
                    +                        
                    +              @override            
                    +              void dispose() {            
                    +                controller.dispose();            
                    +                super.dispose();            
                    +              }            
                                 }

먼저late 키워드는 변수를 나중에 초기화할 것임을 나타내는 키워드이다. 즉, 변수를 선언할 때는 초기값을 제공하지 않고, 변수가 실제 사용되기 전에 값을 제공할 것임을 표시할 때 사용하는 키워드다.

Dart의 연쇄 표기(..) 는 객체의 메서드를 연속적으로 호출할 때 이를 간결하게 만들어주는 문법이다.

var person = Person()
  ..setName('John')
  ..setAge(30)
  ..setEmail('john@example.com');

addListener() 함수는 애니메이션이 새로운 숫자(화면이 갱신됨, 즉 애니메이션이 진행됨)를 생성할 때마다 setState 함수를 호출하므로 build( )함수가 애니메이션이 끝날 때까지 호출된다.

아래의 build() 에서는Container위젯에서 animation의 값을 받고 있으므로 애니메이션이 진행되는 동안 Container 의 크기가 변경된다.

Simplifying with Animated­Widget


AnimatedWidget 기본 클래스를 사용하면 핵심 위젯 코드를 애니메이션 코드에서 분리할 수 있 AnimatedWidget 은 애니메이션을 보유하는 State 객체를 유지할 필요가 없다.

class AnimatedLogo extends AnimatedWidget {
  const AnimatedLogo({super.key, required Animation<double> animation})
      : super(listenable: animation);

  @override
  Widget build(BuildContext context) {
    final animation = listenable as Animation<double>;
    return Center(
      child: Container(
        margin: const EdgeInsets.symmetric(vertical: 10),
        height: animation.value,
        width: animation.value,
        child: const FlutterLogo(),
      ),
    );
  }
}

즉, 위 코드와 차이를 살펴보면 LogoApp 이라는 핵심 위젯 코드 내에서 addListner 메서드를 사용하여 애니메이션의 상태를 관리했다.(initState( ) 내에서 선언)

하지만 위와 같이 AnimatedWidget 를 상속받아서 위젯을 생성하게 되면 LogoApp이라는 더 큰 단위의 위젯에서 애니메이션 관련 위젯을 manage 하지 않을 수 있다.

import 'package:flutter/material.dart';            
                                 void main() => runApp(const LogoApp());            
                    +            class AnimatedLogo extends AnimatedWidget {            
                    +              const AnimatedLogo({super.key, required Animation<double> animation})            
                    +                  : super(listenable: animation);            
                    +                        
                    +              @override            
                    +              Widget build(BuildContext context) {            
                    +                final animation = listenable as Animation<double>;            
                    +                return Center(            
                    +                  child: Container(            
                    +                    margin: const EdgeInsets.symmetric(vertical: 10),            
                    +                    height: animation.value,            
                    +                    width: animation.value,            
                    +                    child: const FlutterLogo(),            
                    +                  ),            
                    +                );            
                    +              }            
                    +            }            
                    +                        
                                 class LogoApp extends StatefulWidget {            
                                   const LogoApp({super.key});            
                                   @override            
                                   State<LogoApp> createState() => _LogoAppState();            
                                 }            
        @@ -15,32 +33,18 @@    
                                   @override            
                                   void initState() {            
                                     super.initState();            
                                     controller = AnimationController(duration: const Duration(seconds: 2), vsync: this); 
                    +                animation = Tween<double>(begin: 0, end: 300).animate(controller);            
                                     controller.forward();            
                                   }            
                                        
                    +              Widget build(BuildContext context) => AnimatedLogo(animation: animation); 
           
                                   @override            
                                   void dispose() {            
                                     controller.dispose();            
                                     super.dispose();            
                                   }

위와 같이 LogoApp 이라는 위젯에서는 AnimatedLogo 라는 위젯으로 extract 해줌으로서 더 간결하게 해당 위젯 내 코드를 관리할 수 있다.

Refactoring with AnimatedBuilder


  • 다른 위젯의 build 메서드의 일부로 애니메이션을 보여주려면 AnimatedBuilder 를 사용하고 단순히 재사용 가능한 애니메이션을 가진 위젯을 정의하려면 AnimatedWidget 를 사용하는 것이 좋다.
  • 플러터 .API에서 AnimatedBuilders 의 예시로는 다음과 같다.

     BottomSheetExpansionTilePopupMenuProgressIndicatorRefreshIndicatorScaffoldSnackBarTabBarTextField.

One problem with the code in the animate3 example, is that changing the animation required changing the widget that renders the logo. A better solution is to separate responsibilities into different classes:

  • Render the logo
  • Define the Animation object
  • Render the transition

You can accomplish this separation with the help of the AnimatedBuilder class. An AnimatedBuilder is a separate class in the render tree. Like AnimatedWidgetAnimatedBuilder automatically listens to notifications from the Animation object, and marks the widget tree dirty as necessary, so you don’t need to call addListener().

The widget tree for the animate4 example looks like this:

3

Starting from the bottom of the widget tree, the code for rendering the logo is straightforward:

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

  // Leave out the height and width so it fills the animating parent
  @override
  Widget build(BuildContext context) {
    return Container(
      margin: const EdgeInsets.symmetric(vertical: 10),
      child: const FlutterLogo(),
    );
  }
}

위 그림의 중간 세 블록은 모두 아래에 표시된 GrowTransition내의 build() 메서드에서 생성된다.

GrowTransition 위젯 자체는 애니메이션 상태를 가지지 않고 아래 생성자에서 볼 수 있듯이, child(애니메이션을 그릴 위젯)과 animation 객체를 전달받고 있다.

이를 활용하여 The build() 함수는 익명함수(Anonymous builder) 와 LogoWidget 객체를 파라미터로 가지는 AnimatedBuilder 를 생성하고 반환한다. 이 때, 실제로 화면에 애니메이션을 구현하는 것은 익명함수(Anonymous builder) 가 수행하고 애니메이션을 적용할 객체를 감쌀 Container위젯을 생성한다.

class GrowTransition extends StatelessWidget {
  const GrowTransition(
      {required this.child, required this.animation, super.key});

  final Widget child;
  final Animation<double> animation;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: AnimatedBuilder(
        animation: animation,
        builder: (context, child) {
          return SizedBox(
            height: animation.value,
            width: animation.value,
            child: child,
          );
        },
        child: child,
      ),
    );
  }
}

바깥 참조(outer reference)란 변수가 자신이 정의된 스코프가 아닌 다른 스코프에서 사용되는 것을 의미한다. 즉, 위 코드에서 child 라는 변수가 두 번 지정된 것처럼 보이지만 AnimatedBuilder 내부의 익명 함수(builder)에서 child를 참조하고 있는데 이 child 변수는 익명 함수의 바깥에서 선언된 AnimatedBuilder 의 child 인자로 받은 child를 참조하고 있는 것! 이다.

최종적으로iniState() 메서드에서 AnimationControllerTween 을 생성하고 이들을 animate() 메서드와 바인딩한다. 실제 애니메이션은 build() 내에서 일어난다.

import 'package:flutter/material.dart';            
                                 void main() => runApp(const LogoApp());            
                    -                   
                    +            class LogoWidget extends StatelessWidget {            
                    +              const LogoWidget({super.key});            
                    +                        
                    +              // Leave out the height and width so it fills the animating parent            
                    +              @override            
                    +              Widget build(BuildContext context) {            
                    +                return Container(            
                    +                  margin: const EdgeInsets.symmetric(vertical: 10),            
                    +                  child: const FlutterLogo(),            
                    +                );            
                    +              }            
                    +            }            
                    +                        
                    +            class GrowTransition extends StatelessWidget {            
                    +              const GrowTransition(            
                    +                  {required this.child, required this.animation, super.key});            
                    +                        
                    +              final Widget child;            
                    +              final Animation<double> animation;            
                                   @override            
                                   Widget build(BuildContext context) {            
                                     return Center(            
                    -                  child: Container(            
                    -                    margin: const EdgeInsets.symmetric(vertical: 10),            
                    -                    height: animation.value,            
                    -                    width: animation.value,            
                    -                    child: const FlutterLogo(),            
                    +                  child: AnimatedBuilder(            
                    +                    animation: animation,            
                    +                    builder: (context, child) {            
                    +                      return SizedBox(            
                    +                        height: animation.value,            
                    +                        width: animation.value,            
                    +                        child: child,            
                    +                      );            
                    +                    },            
                    +                    child: child,            
                                       ),            
                                     );            
                                   }            
                                 }            
                                 class LogoApp extends StatefulWidget {            
                                   const LogoApp({super.key});            
                                   @override            
                                   State<LogoApp> createState() => _LogoAppState();            
        @@ -34,18 +54,23 @@    
                                   @override            
                                   void initState() {            
                                     super.initState();            
                                     controller =            
                                         AnimationController(duration: const Duration(seconds: 2), vsync: this);            
                                     animation = Tween<double>(begin: 0, end: 300).animate(controller);            
                                     controller.forward();            
                                   }            
                                   @override            
                    +              Widget build(BuildContext context) {            
                    +                return GrowTransition(            
                    +                  animation: animation,            
                    +                  child: const LogoWidget(),            
                    +                );            
                    +              }            
                                   @override            
                                   void dispose() {            
                                     controller.dispose();            
                                     super.dispose();            
                                   }            
                                 }

Simultaneous animations


The Curves class defines an array of commonly used curves that you can use with a CurvedAnimation.

Curves 클래스는 CurvedAnimation 과 함께 사용할 수 있는 커브 배열을 정의한다.

이 때 ‘배열’ 이란 동일한 메모리 크기의 요소(값 또는 변수) 컬렉션으로 구성된 데이터 구조를 일컫는 것으로, 각 요소는 적어도 하나의 배열 인덱스나 키로 식별된다.

이때 ‘컬렉션’ 이란 해결 중인 문제에 대해 어느 정도 공유된 의미를 갖는 개수가 변할 수 있는 데이터 항목(0이 될수도 있음)들을 그룹화한 것을 일컫는다.

Each tween manages an aspect of the animation. For example:

controller =
    AnimationController(duration: const Duration(seconds: 2), vsync: this);
sizeAnimation = Tween<double>(begin: 0, end: 300).animate(controller);
opacityAnimation = Tween<double>(begin: 0.1, end: 1).animate(controller);

sizeAnimation.value 로 크기를 opacityAnimation.value 로 투명도를 가져올 수 있지만, AnimatedWidget 위젯의 생성자는 오직 하나의 Animation 객체를 사용한다. 이 문제를 해결하기 위해 예시에는 자체 Tween 객체를 생성하고 명시적으로 값을 계산한다.

트윈 객체를 생성할 때 시작값과 끝값을 직접 지정하여 애니메이션이 어떻게 변할지를 명시적으로 정의함.

AnimatedLogo 를 자체 Tween 객체로 캡슐화하도록 변경하고, build() 메서드에서 부모의 애니메이션 객체에 대한 Tween.evaluate() 를 호출하여 필요한 크기 및 투명도 값을 계산한다.

class AnimatedLogo extends AnimatedWidget {
  const AnimatedLogo({super.key, required Animation<double> animation})
      : super(listenable: animation);

  // Make the Tweens static because they don't change.
  **static final _opacityTween = Tween<double>(begin: 0.1, end: 1);
  static final _sizeTween = Tween<double>(begin: 0, end: 300);**

  @override
  Widget build(BuildContext context) {
    final animation = listenable as Animation<double>;
    return Center(
      **child: Opacity(
        opacity: _opacityTween.evaluate(animation),**
        child: Container(
          margin: const EdgeInsets.symmetric(vertical: 10),
          height: **_sizeTween.evaluate(animation),**
          width: **_sizeTween.evaluate(animation),**
          child: const FlutterLogo(),
        ),
      ),
    );
  }
}

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

  @override
  State<LogoApp> createState() => _LogoAppState();
}

class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
  late Animation<double> animation;
  late AnimationController controller;

  @override
  void initState() {
    super.initState();
    controller =
        AnimationController(duration: const Duration(seconds: 2), vsync: this);
    animation = **CurvedAnimation(parent: controller, curve: Curves.easeIn)**
      ..addStatusListener((status) {
        if (status == AnimationStatus.completed) {
          controller.reverse();
        } else if (status == AnimationStatus.dismissed) {
          controller.forward();
        }
      });
    controller.forward();
  }

  @override
  Widget build(BuildContext context) => AnimatedLogo(animation: animation);

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

캡슐화(encapsulate)란 데이터를 그 데이터를 조작하는 메커니즘 또는 메서드와 함께 묶는 것을 의미하기도 하며, 객체의 구성 요소와 같은 데이터 중 일부에 직접 접근하는 것을 제한하는 것을 의미하기도 함. 아래 코드에서 Tween 객체를 클래스 내에서만 공유 가능하도록 Static final private 변수로 선언함으로써 캡슐화를 했다고 이해할 수 있다.

이 때 외부에서 접근할 수 없다는 것은 해당 변수나 메서드가 동일한 클래스나 라이브러리의 외부에서 직접 사용할 수 없다는 것을 의미한다.

class Example {
  int _privateValue = 42;

  void _privateMethod() {
    print("This is a private method");
  }

  void publicMethod() {
    // 외부에서 호출 가능한 공개 메서드
    print("This is a public method");
  }
}

void main() {
  var obj = Example();

  // 공개 메서드 호출 가능
  obj.publicMethod(); // 출력: This is a public method

  // _privateValue 및 _privateMethod에 직접 접근 불가능
  print(obj._privateValue); // 컴파일 오류
  obj._privateMethod();     // 컴파일 오류
}

static 키워드를 사용하면 클래스의 모든 인스턴스가 공유하는 변수 또는 메서드로 만들 수 있고 이러한 변수 또는 메서드는 객체가 아닌 클래스 자체에 속하며, 클래스 이름을 통해 접근할 수 있다.

태그:

카테고리:

업데이트:

댓글남기기