가을기 Workspace

[플러터] 29일차 - 애니메이션, 인트로 본문

개발/개인앱

[플러터] 29일차 - 애니메이션, 인트로

가을기_ 2021. 8. 19. 02:05

https://api.flutter.dev/flutter/animation/Curves-class.html

 

페이지 이동할 때 애니메이션을 적용하기 위해 Hero 위젯을 이용한다. Hero 위젯은 페이지 간 이미지를 자연스럽게 애니메이션으로 연결해준다.

 

환경 준비

flutter:

  # The following line ensures that the Material Icons font is
  # included with your application, so that you can use the icons in
  # the material Icons class.
  uses-material-design: true

  # To add assets to your application, add an assets section, like this:
  assets:
    - images/circle.png
    - images/saturn.png
    - images/sunny.png

 

 

코드

  • people.dart
class People {
  late String name;
  late double height;
  late double weight;
  late double bmi;

  People(this.name, this.height, this.weight) {
    this.bmi = weight / ((height / 100) * (weight / 100));
  }
}
  • main.dart
import 'package:animation_example/introPage.dart';
import 'package:animation_example/people.dart';
import 'package:flutter/material.dart';

import 'SecondPage.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // This is the theme of your application.
        //
        // Try running your application with "flutter run". You'll see the
        // application has a blue toolbar. Then, without quitting the app, try
        // changing the primarySwatch below to Colors.green and then invoke
        // "hot reload" (press "r" in the console where you ran "flutter run",
        // or simply save your changes to "hot reload" in a Flutter IDE).
        // Notice that the counter didn't reset back to zero; the application
        // is not restarted.
        primarySwatch: Colors.blue,
      ),
      home: IntroPage(),
    );
  }
}

class AnimationApp extends StatefulWidget {
  @override
  State<AnimationApp> createState() => _AnimationApp();
}

class _AnimationApp extends State<AnimationApp> {
  List<People> peoples = [
    People('스미스', 180, 92),
    People('메리', 162, 55),
    People('존', 177, 75),
    People('바트', 130, 40),
    People('콘', 194, 140),
    People('디디', 100, 80),
  ];
  Color weightColor = Colors.blue;
  int current = 0;
  double _opacity = 1;

  @override
  void initState() {
    super.initState();
  }

  void _changeWeightColor(double weight) {
    weightColor = weight < 40
        ? Colors.blueAccent
        : weight < 60
        ? Colors.indigo
        : weight < 80
        ? Colors.orange
        : Colors.red;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Row(
          children: [
            Icon(Icons.info),
            Text('Animation Example'),
          ],
          mainAxisAlignment: MainAxisAlignment.start,
        ),
      ),
      body: Container(
        child: Center(
          child: Column(
            children: [
              AnimatedOpacity(
                opacity: _opacity,
                duration: Duration(seconds: 1),
                curve: Curves.easeIn,
                child: SizedBox(
                  child: Row(
                    children: [
                      SizedBox(
                        width: 100,
                        child: Text('이름 ${peoples[current].name}'),
                      ),
                      AnimatedContainer(
                        duration: Duration(milliseconds: 800),
                        curve: Curves.easeIn,
                        color: Colors.amber,
                        child: Text(
                          '키 ${peoples[current].height}',
                          textAlign: TextAlign.center,
                        ),
                        width: 50,
                        height: peoples[current].height,
                      ),
                      AnimatedContainer(
                        duration: Duration(milliseconds: 800),
                        curve: Curves.easeInCubic,
                        color: weightColor,
                        child: Text(
                          '몸무게 ${peoples[current].weight}',
                          textAlign: TextAlign.center,
                        ),
                        width: 50,
                        height: peoples[current].weight,
                      ),
                      AnimatedContainer(
                        duration: Duration(milliseconds: 800),
                        curve: Curves.linear,
                        color: Colors.pinkAccent,
                        child: Text(
                          'bmi ${peoples[current].bmi}',
                          textAlign: TextAlign.center,
                        ),
                        width: 50,
                        height: peoples[current].bmi,
                      ),
                    ],
                    mainAxisAlignment: MainAxisAlignment.spaceAround,
                    crossAxisAlignment: CrossAxisAlignment.end,
                  ),
                  height: 200,
                ),
              ),
              ElevatedButton(
                onPressed: () {
                  setState(() {
                    current = (current + 1) % (peoples.length);
                    _changeWeightColor(peoples[current].weight);
                  });
                },
                child: Text('다음'),
              ),
              ElevatedButton(
                  onPressed: () {
                    setState(() {
                      current =
                          (current + peoples.length - 1) % (peoples.length);
                      _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: [
                      Hero(tag: 'detail', child: Icon(Icons.cake)),
                      Text('이동하기'),
                    ],
                  ),
                ),
              )
            ],
            mainAxisAlignment: MainAxisAlignment.center,
          ),
        ),
      ),
    );
  }
}
  • introPage.dart
import 'dart:async';

import 'package:animation_example/SaturnLoading.dart';
import 'package:animation_example/main.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

class IntroPage extends StatefulWidget {
  @override
  State<IntroPage> createState() => _IntroPage();
}

class _IntroPage extends State<IntroPage> {
  Future<Timer> loadData() async {
    return Timer(Duration(seconds: 5), () async {
      Navigator.of(context)
          .pushReplacement(MaterialPageRoute(builder: (context) => AnimationApp()));
    });
  }

  @override
  void initState() {
    super.initState();
    loadData();
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        child: Center(
          child: Column(
            children: [
              Text('애니메이션 앱'),
              SizedBox(
                height: 20,
              ),
              SaturnLoading(),
            ],
            mainAxisAlignment: MainAxisAlignment.center,
          ),
        ),
      ),
    );
  }
}
  • SaturnLoading.dart
import 'dart:math';

import 'package:flutter/cupertino.dart';

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 {
  late AnimationController _animationController;
  late Animation _animation;

  void start() {
    _animationController.repeat();
  }
  void stop() {
    _animationController.stop(canceled: true);
  }

  @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: [
                Image.asset(
                  'images/circle.png',
                  width: 100,
                  height: 100,
                ),
                Center(
                  child: Image.asset(
                    'images/sunny.png',
                    width: 30,
                    height: 30,
                  ),
                ),
                Padding(
                  padding: EdgeInsets.all(5),
                  child: Transform.rotate(
                    angle: _animation.value,
                    origin: Offset(35, 35),
                    child: Image.asset(
                      'images/saturn.png',
                      width: 20,
                      height: 20,
                    ),
                  ),
                ),
              ],
            ),
          );
        });
  }
}
  • SecondPage.dart
import 'dart:math';

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

class SecondPage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _SecondPage();
}

class _SecondPage extends State<SecondPage>
    with SingleTickerProviderStateMixin {
  late AnimationController _animationController;
  late Animation _rotateAnimation;
  late Animation _scaleAnimation;
  late Animation _transAnimation;

  @override
  void initState() {
    super.initState();
    _animationController =
        AnimationController(vsync: this, duration: Duration(seconds: 5));
    _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 context) {
    return Scaffold(
      appBar: AppBar(title: Text('Animation Example2')),
      body: Container(
        child: Center(
          child: Column(
            children: [
              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,
          ),
        ),
      ),
    );
  }
}
Comments