ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 애니메이션 활용하기 - (2) 인트로 화면 만들기
    앱 App/플러터 Flutter 2021. 11. 7. 15:37

    애니메이션을 더 세밀하게 조정하는 법 & 페이지 이동 애니메이션을 알아보고

    인트로 화면을 만들어보자!

    애니메이션 활용하기 - (1) 애니메이션 구현하기와 이어짐

     

     

     

    실습1. 페이지 이동 애니메이션 (Hero 위젯)

    https://www.youtube.com/watch?v=Be9UH1kXFDw (Hero 위젯에 관한 설명)

     

    <이동하기>를 누르면 케이크가 커지며 두번째 페이지로 이동했다가,

    왼쪽 상단 뒤로가기를 누르면 케이크가 작아지며 원래 페이지로 이동

     

     

     

    1단계.

    사라지기 버튼 아래에 두번째 페이지로 이동하는 버튼 만들기

    아이콘을 Hero위젯으로 감싸고 Tagdetail로 설정

     

     

    2단계

    Lib 폴더에 secondPage파일 추가하여, 아이콘을 띄우는 페이지 작성

    이때 Hero위젯에서, Tag옵션을 앞과 동일하게 detail로 지정하여 main파일의 Hero와 연결

     

     

     

     

    실습2. 애니메이션 세밀하게 조정하기 (AnimatedBuilder 활용)

    https://www.youtube.com/watch?v=N-RiyZlv8v8 (AnimatedBuilder에 관한 설명)

     

     

     

    <이동하기> 를 눌러 두번째 페이지로 이동한 뒤

    <로테이션 시작하기>를 누르면 케이크가 움직이며 회전

     

     

     

     

    1단계.

    애니메이션 컨트롤러 사용을 위해 SingleTickerProviderStateMixin 클래스 상속하고

    AnimationController 객체 1개와 Animation 객체 3개를 정의

     

     

    2단계.

    각 객체를 initState()에서 초기화

    AnimateionController 생성시 Duration으로 재생시간, vsync로 애니메이션 대상 전달

    (= 현재페이지에서 5초동안 재생하겠다)

     

    나머지는 Tween 클래스로 초기화

    Tween : 시작점인 begin과 끝점인 end 인자만 있으면 되는 stateless 객체로, 사용시 animate함수 호출

     

     

    3단계.

    화면 종료 시 dispose()에서 _animationController!.dispose() 호출하여 애니메이션 종료

    호출하지 않을 시 화면을 그리려는 대상이 없어 오류 발생

     

     

    4단계.

    Build함수로 화면 구현

    앞서 정의한 나머지 3개의 객체를 화면에 표시한다. Translate: 위젯 방향 / Rotate: 회전 / Scale: 크기

    그리고 child 옵션에서 Hero 위젯을 통해 케이크 모양의 아이콘 정의

     

     

     

    실습3. 인트로 화면 만들기

    5초간 토성이 태양 주위를 돌다가 메인화면으로 이동

     

     

    1단계.

    프로젝트 아래에 repo/images라는 새 폴더 생성 후 이미지 파일 추가

    등록한 파일은 Pubspec.yaml 파일에 이미지 파일을 애셋으로 등록 후 Pub get 클릭

     

     

    2단계.

    saturnLoading 파일을 새로 만들고 SaturnLoading 클래스와 _SaturnLoading 클래스 작성

    AnimationController와 Animation 변수 생성하여 initState()에서 초기화

    (AnimationController는 3초간 현재 페이지에서 동작하도록, Animation은 시작과 끝점 정의)

     

     

    3단계.

    build() 작성

    Stack 구조이므로 원, 태양, 토성 순으로 쌓음

    Transform.torate의 origin은 회전의 기준점을 의미하며, 토성 이미지의 가로세로는 각 20픽셀이다

    즉 이미지의 중심은 (10,10)인데 패딩값을 5로 줬으므로 (15,15)가 된다.

    토성 이미지는 태양으로부터 35픽셀 떨어져 회전하도록 설정

     

     

     

    4단계.

     SaturnLoading 클래스와 _SaturnLoading 클래스에

    각각 start(), stop() 함수를 추가 작성하여 애니메이션을 시작하고 멈출 수 있게

     

     

    5단계.

    lib폴더에 intro파일 추가

    IntroPage 클래스를 만들어 앱이 시작될 때 인트로 화면이 5초간 나타나게

    build()의 SaturnLoading()을 통해 애니메이션 불러오기

     

     

     

    6단계.

    방금 만든 파일을 main파일에 임포트하고 home을 IntroPage()로 변경

     

     

     

    끝!

     

    <main.dart>

    import 'package:flutter/material.dart';
    import 'people.dart';
    import 'secondPage.dart';
    import 'intro.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
     @override
      Widget build(BuildContext context) {
       return MaterialApp(
         title: 'Flutter Demo',
         theme: ThemeData(
           primarySwatch: Colors.blue
         ),
         home: IntroPage(),
       );
     }
    }
    
    class AnimationApp extends StatefulWidget {
      @override
      State<StatefulWidget> createState() => _AnimationApp();
    }
    
    class _AnimationApp extends State<AnimationApp> {
      double _opacity = 1;
      List<People> peoples = new List.empty(growable: true);
      Color weightColor = Colors.blue;
      int current = 0;
    
      @override
      void initState() {
        peoples.add(People('스미스', 180, 92));
        peoples.add(People('메리', 162, 55));
        peoples.add(People('존', 177, 75));
        peoples.add(People('바트', 130, 40));
        peoples.add(People('콘', 194, 140));
        peoples.add(People('디디', 100, 80));
        super.initState();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('Animation Example'),
          ),
          body: Container(
            child: Center(
              child: Column(
                children: <Widget>[
                  AnimatedOpacity(
                    opacity: _opacity,
                    duration: Duration(seconds: 1),
                    child: SizedBox(
                      child: Row(
                        children: <Widget>[
                          SizedBox(width: 100, child: Text('이름 : ${peoples[current].name}')),
                          AnimatedContainer(
                            duration: Duration(seconds: 2),
                            curve: Curves.bounceIn,
                            color: Colors.amber,
                            child: Text(
                              '키 ${peoples[current].height}',
                              textAlign: TextAlign.center,
                            ),
                            width: 50,
                            height: peoples[current].height,
                          ),
                          AnimatedContainer(
                            duration: Duration(seconds: 2),
                            curve: Curves.easeInCubic,
                            color: weightColor,
                            child: Text(
                              '몸무게 ${peoples[current].weight}',
                              textAlign: TextAlign.center,
                            ),
                            width: 50,
                            height: peoples[current].weight,
                          ),
                          AnimatedContainer(
                            duration: Duration(seconds: 2),
                            curve: Curves.linear,
                            color: Colors.pinkAccent,
                            child: Text(
                              'bmi ${peoples[current].bmi.toString().substring(0, 2)}',
                              textAlign: TextAlign.center,
                            ),
                            width: 50,
                            height: peoples[current].bmi,
                          )
                        ],
                        mainAxisAlignment: MainAxisAlignment.spaceAround,
                        crossAxisAlignment: CrossAxisAlignment.end,
                      ),
                      height: 200,
                    ),
                  ),
                  ElevatedButton(
                    onPressed: () {
                      setState(() {
                        if (current < peoples.length - 1) {
                          current++;
                        }
                        _changeWeightColor(peoples[current].weight);
                      });
                    },
                    child: Text('다음'),
                  ),
                  ElevatedButton(
                    onPressed: () {
                      setState(() {
                        if (current > 0) {
                          current--;
                        }
                        _changeWeightColor(peoples[current].weight);
                      });
                    },
                    child: Text('이전'),
                  ),
                  ElevatedButton(
                    onPressed: () {
                      setState(() {
                        _opacity == 1 ? _opacity = 0 : _opacity = 1;
                      });
                    },
                    child: Text('사라지기'),
                  ),
                  ElevatedButton(
                    onPressed: () {
                      Navigator.of(context).push(MaterialPageRoute(builder:
                        (context) => SecondPage()));
                    },
                    child: SizedBox(
                      width: 200,
                      child: Row(
                        children: <Widget> [
                          Hero(tag: 'detail', child: Icon(Icons.cake)),
                          Text('이동하기')
                        ],
                      ),),),
                ],
                mainAxisAlignment: MainAxisAlignment.center,
              ),),),
        );
      }
    
      void _changeWeightColor(double weight) {
        if (weight < 40) {
          weightColor = Colors.blueAccent;
        } else if (weight < 60) {
          weightColor = Colors.indigo;
        } else if (weight < 80) {
          weightColor = Colors.orange;
        } else {
          weightColor = Colors.red;
        }
      }
    }

     

    <people.dart>

    class People {
      String name;
      double height;
      double weight;
      double? bmi;
    
      People(this.name, this.height, this.weight) {
        bmi = weight / ((height/ 100) * (height/100));
      }

     

    <secondPage.dart>

    import 'package:flutter/material.dart';
    import 'dart:math';
    
    class SecondPage extends StatefulWidget {
      @override
      State<StatefulWidget> createState() => _SecondPage();
    }
    
    class _SecondPage extends State<SecondPage>
        with SingleTickerProviderStateMixin {
      AnimationController _animationController;
      Animation _rotateAnimation;
      Animation _scaleAnimation;
      Animation _transAnimation;
    
        @override
        void initState() {
          super.initState();
          _animationController =
            AnimationController(duration: Duration(seconds: 5), vsync: this);
          _rotateAnimation =
            Tween<double>(begin: 0, end: pi * 10).animate(_animationController);
          _scaleAnimation =
            Tween<double>(begin: 1, end: 0).animate(_animationController);
          _transAnimation = Tween<Offset>(begin: Offset(0, 0), end: Offset(200, 200))
             .animate(_animationController);
        }
    
        @override
        void dispose() {
          _animationController.dispose();
          super.dispose();
        }
    
        @override
      Widget build(BuildContext contexxt) {
          return Scaffold(
            appBar: AppBar(title: Text('Animation Example2'),),
            body: Container(
              child: Center(
                child: Column(
                  children: <Widget>[
                    AnimatedBuilder(
                      animation: _rotateAnimation,
                      builder: (context, widget) {
                        return Transform.translate(
                          offset: _transAnimation.value,
                          child: Transform.rotate(
                              angle: _rotateAnimation.value,
                              child: Transform.scale(
                                scale: _scaleAnimation.value,
                                child: widget,
                              )),
                        );
                      },
                      child: Hero(
                          tag: 'detail',
                          child: Icon(
                            Icons.cake,
                            size: 300,
                          )),
                    ),
                    ElevatedButton(
                      onPressed: () {
                        _animationController.forward();
                      },
                      child: Text('로테이션 시작하기'),
                    ),
                  ],
                  mainAxisAlignment: MainAxisAlignment.center,
                ),),),
          );
        }
    }

     

     

    <saturnLoading.dart>

    import 'package:flutter/material.dart';
    import 'dart:math';
    
    class SaturnLoading extends StatefulWidget {
      _SaturnLoading _saturnLoading = _SaturnLoading();
    
      void start() {
        _saturnLoading.start();
      }
    
      void stop() {
        _saturnLoading.stop();
      }
    
      @override
      State<StatefulWidget> createState() => _saturnLoading;
    }
    
    class _SaturnLoading extends State<SaturnLoading>
        with SingleTickerProviderStateMixin {
      AnimationController _animationController;
      Animation _animation;
    
      void stop() {
        _animationController.stop(canceled: true);
      }
    
      void start() {
        _animationController.repeat();
      }
    
      @override
      void initState() {
        super.initState();
        _animationController =
            AnimationController(vsync: this, duration: Duration(seconds: 3));
        _animation =
            Tween<double>(begin: 0, end: pi * 2).animate(_animationController);
        _animationController.repeat();
      }
    
      @override
      void dispose() {
        _animationController.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return AnimatedBuilder(
          animation: _animationController,
          builder: (context, child) {
            return SizedBox(
              width: 100,
              height: 100,
              child: Stack( // 스택 구조로 (먼저 넣을수록 뒤에 놓임)
                children: <Widget>[
                  Image.asset(
                    'repo/images/circle.png',
                    width: 100,
                    height: 100,
                  ),
                  Center(
                    child: Image.asset(
                      'repo/images/sunny.png',
                      width: 30,
                      height: 30,
                    ),
                  ),
                  Padding(
                    padding: EdgeInsets.all(5),
                    child: Transform.rotate(
                      angle: _animation.value,
                      origin: Offset(35, 35), // 태양으로부터 35픽셀 떨어져 회전
                      child: Image.asset(
                        'repo/images/saturn.png',
                        width: 20,
                        height: 20,
                      ),),)
                ],),);
          },);
      }
    }

    <intro.dart>

    import 'package:flutter/material.dart';
    import 'package:animation_example/saturnLoading.dart';
    import 'dart:async';
    import 'main.dart';
    
    class IntroPage extends StatefulWidget {
      @override
      State<StatefulWidget> createState() => _IntroPage();
    }
    
    class _IntroPage extends State<IntroPage> {
      @override
      void initState() {
        super.initState();
        loadData();
      }
    
      Future<Timer> loadData() async {
        return Timer(Duration(seconds: 5), onDoneLoading);
      }
    
      onDoneLoading() async {
        Navigator.of(context)
            .pushReplacement(MaterialPageRoute(builder: (context) => AnimationApp()));
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: Container(
            child: Center(
              child: Column(
                children: <Widget>[
                  Text('애니메이션 앱'),
                  SizedBox(
                    height: 20,
                  ),
                  SaturnLoading() // 애니메이션 불러오기
                ],
                mainAxisAlignment: MainAxisAlignment.center,
              ),),),);
      }}

     

     

Designed by Tistory.