방명록
- [Flutter] "Do it! 플러터 앱 프로그래밍" - 애니메이션 활용하기 | 애니메이션 구현, 인트로 화면, 스크롤 시 역동적인 앱바2022년 02월 18일 23시 36분 40초에 업로드 된 글입니다.작성자: DandyNow728x90반응형
"조준수. (2021). Do it! 플러터 앱 프로그래밍. 이지스퍼블리싱", 11장을 실습하였다. AnimatedContainer 위젯을 이용해서 애니메이션을 구현한다. 그래프 애니메이션, 애니메이션이 적용된 인트로 화면, 스크롤 시 역동적인 앱바를 만들어 보았다.
애니메이션 구현하기
애니메이션은 수학적인 계산과 상상이 필요한 부분이다. 계산이나 작동 시간이 조금만 이상해도 어울리지 않는 느낌이 들기 때문이다. 또한 앱 동작이 느려질 수 있으므로 필요한 순간에만 사용하는 것이 좋다. 다음 주소에 접속하면 플러터 API의 Curves 클래스가 제공하는 다양한 애니메이션 모양을 확인할 수 있다.
https://api.flutter.dev/flutter/animation/Curves-class.html
그래프 애니메이션
// main.dart import 'package:flutter/material.dart'; import 'people.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: AnimationApp(), ); } } class AnimationApp extends StatefulWidget { @override State<StatefulWidget> createState() => _AnimationApp(); } class _AnimationApp extends State<AnimationApp> { List<People> peoples = new List.empty(growable: true); int current = 0; @override void initState() { peoples.add(People('이일남', 180, 92)); peoples.add(People('이이남', 162, 55)); peoples.add(People('이삼남', 177, 75)); peoples.add(People('이사남', 159, 48)); peoples.add(People('이오남', 194, 110)); peoples.add(People('이육남', 140, 60)); super.initState(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Animation Example'), ), body: Container( child: Center( child: Column( children: <Widget>[ // SizedBox 안에는 Row 이용해 텍스트 위젯 애니메이션을 구현하는 AnimatedContainer를 만든다. 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: Colors.blue, 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++; } }); }, child: Text('다음'), ), ElevatedButton( onPressed: () { setState(() { if (current > 0) { current--; } }); }, child: Text('이전'), ) ], mainAxisAlignment: MainAxisAlignment.center, ), ), ), ); } }
// 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)); } }
색상 변경 애니메이션
몸무게에 색상 변경 애니메이션을 적용하였다.
import 'package:flutter/material.dart'; import 'people.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: AnimationApp(), ); } } class AnimationApp extends StatefulWidget { @override State<StatefulWidget> createState() => _AnimationApp(); } class _AnimationApp extends State<AnimationApp> { 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('이이남', 170, 75)); peoples.add(People('이삼남', 165, 59)); peoples.add(People('이사남', 145, 39)); peoples.add(People('이오남', 194, 110)); peoples.add(People('이육남', 155, 60)); super.initState(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Animation Example'), ), body: Container( child: Center( child: Column( children: <Widget>[ // SizedBox 안에는 Row 이용해 텍스트 위젯 애니메이션을 구현하는 AnimatedContainer를 만든다. 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('이전'), ) ], 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; } } }
불투명도 애니메이션
AnimatedOpacity 위젯을 이용해 서서히 사라지는 효과를 구현할 수 있다.
import 'package:flutter/material.dart'; import 'people.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: AnimationApp(), ); } } 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('이이남', 170, 75)); peoples.add(People('이삼남', 165, 59)); peoples.add(People('이사남', 145, 39)); peoples.add(People('이오남', 194, 110)); peoples.add(People('이육남', 155, 60)); 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), // SizedBox 안에는 Row 이용해 텍스트 위젯 애니메이션을 구현하는 AnimatedContainer를 만든다. 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('이전'), ), // AnimatedOpacity 불투명도를 이용한 사라지기 버튼 ElevatedButton( onPressed: () { setState(() { _opacity == 1 ? _opacity = 0 : _opacity = 1; }); }, child: 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; } } }
나만의 인트로 화면 만들기
페이지 이동 애니메이션 / 애니메이션 세밀하게 조정
페이지 이동 애니메이션은 Hero 위젯을 이용한다.
// main.dart import 'package:flutter/material.dart'; import 'people.dart'; import 'secondPage.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: AnimationApp(), ); } } 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('이이남', 170, 75)); peoples.add(People('이삼남', 165, 59)); peoples.add(People('이사남', 145, 39)); peoples.add(People('이오남', 194, 110)); peoples.add(People('이육남', 155, 60)); 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), // SizedBox 안에는 Row 이용해 텍스트 위젯 애니메이션을 구현하는 AnimatedContainer를 만든다. 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; } } }
// 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); // Tween을 사용해 애니메이션 구성 _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: <Widget>[ // AnimatedBuilder는 애니메이션을 정의한 대로 화면에 그려준다. 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, ), ), ), ); } }
나만의 인트로 화면 만들기
인트로 화면에 필요한 이미지를 repo/images 폴더에 넣고 pubspec.yaml을 수정한다.
// pubspec.yaml (...생략...) flutter: uses-material-design: true assets: - repo/images/
// main.dart import 'package:flutter/material.dart'; import 'package:flutter_animation_example/intro.dart'; import 'people.dart'; import 'secondPage.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('이이남', 170, 75)); peoples.add(People('이삼남', 165, 59)); peoples.add(People('이사남', 145, 39)); peoples.add(People('이오남', 194, 110)); peoples.add(People('이육남', 155, 60)); 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), // SizedBox 안에는 Row 이용해 텍스트 위젯 애니메이션을 구현하는 AnimatedContainer를 만든다. 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; } } }
// intro.dart import 'package:flutter/material.dart'; import '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, ), ), ), ); } }
// saturnLoading.dart import 'package:flutter/material.dart'; import 'dart:math'; class SaturnLoading extends StatefulWidget { _SaturnLoading _saturnLoading = _SaturnLoading(); @override State<StatefulWidget> createState() => _SaturnLoading(); void start() { _saturnLoading.start(); } void stop() { _saturnLoading.stop(); } } class _SaturnLoading extends State<SaturnLoading> with SingleTickerProviderStateMixin { AnimationController? _animationController; Animation? _animation; @override void initState() { super.initState(); _animationController = AnimationController( vsync: this, duration: Duration(seconds: 3)); // 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), // 회전의 기준점 지정하기 child: Image.asset( 'repo/images/saturn.png', width: 20, height: 20, ), ), ) ], ), ); }, ); } // 애니메이션을 시작하고 멈출 수 있게 한다. void stop() { _animationController!.stop(canceled: true); } void start() { _animationController!.repeat(); } }
스크롤 시 역동적인 앱바 만들기
화면 스크롤할 때 앱바의 크기를 변경하는 애니메이션을 구현한다.
// main.dart import 'package:flutter/material.dart'; import 'package:flutter_animation_example/intro.dart'; import 'people.dart'; import 'secondPage.dart'; import 'sliverPage.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('이이남', 170, 75)); peoples.add(People('이삼남', 165, 59)); peoples.add(People('이사남', 145, 39)); peoples.add(People('이오남', 194, 110)); peoples.add(People('이육남', 155, 60)); 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), // SizedBox 안에는 Row 이용해 텍스트 위젯 애니메이션을 구현하는 AnimatedContainer를 만든다. 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('이동하기') ], ), ), ), // SliverPage 이동 버튼 ElevatedButton( onPressed: () { Navigator.of(context).push( MaterialPageRoute(builder: (context) => SliverPage())); }, child: 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; } } }
// silverPage.dart import 'package:flutter/material.dart'; import 'dart:math' as math; class SliverPage extends StatefulWidget { @override State<StatefulWidget> createState() => _SliverPage(); } class _SliverPage extends State<SliverPage> { @override Widget build(BuildContext context) { return Scaffold( // CustomScrollView는 슬리버를 사용해 사용자 정의 스크롤 효과를 만드는 위젯 body: CustomScrollView( // 슬리버가 들어가는 위젯을 이용할 때는 컨테이너나 텍스트 등 기본적인 위젯을 바로 사용할 수 없고 slivers 인자로 위젯을 묶어 주어야 한다. slivers: <Widget>[ // SliverAppBar는 CustomScrollView 위젯 안에서만 사용할 수 있다. SliverAppBar( expandedHeight: 150.0, // 앱바의 최대 높이 설정 //유연하게 조절되는 공간 flexibleSpace: FlexibleSpaceBar( title: Text('Sliver Example'), background: Image.asset('repo/images/sunny.png'), ), backgroundColor: Colors.deepOrangeAccent, pinned: true, ), SliverPersistentHeader( delegate: _HeaderDelegate( minHeight: 50, maxHeight: 150, child: Container( color: Colors.blue, child: Center( child: Column( children: <Widget>[ Text( 'list 숫자', style: TextStyle(fontSize: 30), ), ], mainAxisAlignment: MainAxisAlignment.center, ), ), )), pinned: true, // 앱바가 사라지지 않고 최소 크기로 고정되게 한다. ), SliverList( // delegate: SliverChildListDelegate([ // customCard('1'), // customCard('2'), // customCard('3'), // customCard('4'), // ])), // 리스트를 빌더 형태로 생성 delegate: SliverChildBuilderDelegate((context, index) { return Container( child: customCard('list count : $index'), ); }, childCount: 10)), SliverPersistentHeader( delegate: _HeaderDelegate( minHeight: 50, maxHeight: 150, child: Container( color: Colors.blue, child: Center( child: Column( children: <Widget>[ Text( '그리드 숫자', style: TextStyle(fontSize: 30), ), ], mainAxisAlignment: MainAxisAlignment.center, ), ), )), pinned: true, ), SliverGrid( // delegate: SliverChildListDelegate([ // customCard('1'), // customCard('2'), // customCard('3'), // customCard('4'), // ]), // 리스트를 빌더 형태로 생성 delegate: SliverChildBuilderDelegate((context, index) { return Container( child: customCard('list count : $index'), ); }, childCount: 10), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2)), ], ), ); } // 스크롤되는 위젯 넣기 Widget customCard(String text) { return Card( child: Container( height: 120, child: Center( child: Text( text, style: TextStyle(fontSize: 40), )), ), ); } } // 4개의 함수를 재정의 함 class _HeaderDelegate extends SliverPersistentHeaderDelegate { final double minHeight; final double maxHeight; final Widget child; _HeaderDelegate({ required this.minHeight, required this.maxHeight, required this.child, }); // 머리말을 만들 때 사용할 위젯을 배치 @override Widget build( BuildContext context, double shrinkOffset, bool overlapsContent) { return SizedBox.expand(child: child); } // 해당 위젯의 최대 높이 설정 @override double get maxExtent => math.max(maxHeight, minHeight); // 해당 위젯의 최소 높이 설정 @override double get minExtent => minHeight; // 위젯을 계속 그릴 것인지 정함. 만약 maxHeight나 minHeight, child가 달라진다면 true를 반환해 계속 다시 그릴수 있게 설정한다. @override bool shouldRebuild(_HeaderDelegate oldDelegate) { return maxHeight != oldDelegate.maxHeight || minHeight != oldDelegate.minHeight || child != oldDelegate.child; } }
728x90반응형'언어·프레임워크 > Flutter' 카테고리의 다른 글
다음글이 없습니다.이전글이 없습니다.댓글