Flutter Animation(플러터 애니메이션) - 1 (암시적 애니메이션)
Flutter Animation(플러터 애니메이션) - 1 (암시적 애니메이션)
오랜만에 포스트를 작성하게 되었네요. 이번 포스트에서는 Flutter Animation에 대하여 알아보겠습니다.
목차
암시적 애니메이션(Implicit Animation)
Flutter의 애니메이션 라이브러리를 사용하면 UI에서 위젯에 모션을 추가하고 시각 효과를 만들 수 있습니다.
위 라이브러리에 설정된 하나의 위젯이 애니메이션을 관리합니다.
이러한 위젯을 통칭하여 암시 적 애니메이션 또는 암시 적으로 애니메이션 된 위젯이라고 하며,
ImplicitlyAnimatedWidget 클래스를 구현합니다
✨암시적 애니메이션 위젯들은 자동으로 변경사항을 애니메이션 합니다
특징
⚡ 애니메이션을 사용하면 대상 값을 설정하여 위젯 속성에 애니메이션을 적용할 수 있습니다.
⚡ 대상 값이 변경될 때마다 위젯은 이전 값에서 새 값으로 특성에 애니메이션을 적용합니다.
⚡ 컨트롤러 등으로 애니메이션 효과를 관리하지 않습니다.
애니메이션 과정
값을 통한 애니메이션으로
old value와 new value의 사이를 보간(Interpolation)합니다
암시적 애니메이션 위젯은 이 보간을 처리합니다.
여러 가지 암시적 애니메이션 위젯
⭐ AnimatedAlign(Align)
⭐ AnimatedContainer(Container)
⭐ AnimatedDefaultTextStyle(DefaultTextStyle)
⭐ AnimatedOpacity(Opacity)
⭐ AnimatedPadding(Padding)
⭐ AnimatedPhysicalModel(PhysicalModel)
⭐ AnimatedPositioned(Positioned)
⭐ AnimatedThemeSize
⭐ AnimatedSize
⭐ AnimatedCrossFade
⭐ AnimatedSwitcher
AnimatedAlign
지정된 Align(정렬)이 변경될 때마다 지정된 Duration동안 자동으로 child의 위치를 전환하는 애니메이션 버전의 Align입니다
class AnimatedAlignScreen extends StatefulWidget {
@override
_AnimatedAlignScreenState createState() => _AnimatedAlignScreenState();
}
class _AnimatedAlignScreenState extends State<AnimatedAlignScreen> {
var alignment = Alignment.bottomLeft;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('AnimatedAlign')),
body: Container(
padding: EdgeInsets.all(30.0),
child: Column(
children: <Widget>[
Expanded(
child: AnimatedAlign(
alignment: alignment,
duration: Duration(milliseconds: 100),
child: FlutterLogo(size: 150),
),
),
FlatButton(
color: Colors.blueAccent,
child: Text('alignment change'),
onPressed: () {
setState(() {
alignment = alignment == Alignment.bottomLeft
? Alignment.topRight
: Alignment.bottomLeft;
});
},
)
],
),
),
);
}
}
AnimatedContainer
AnimatedContainer는 일정 기간 동안 점차적으로 값을 변경하는 Container 애니메이션 버전입니다.
📌이전 값과 새로운 값 사이를 자동적으로 애니메이션 합니다. (Curve와 Duration 사용)
📌속성 값이 null인 것은 애니메이션 하지 않습니다.
📌자식은 애니메이션 하지 않습니다.
📌Color, Border, BorderRadii, BackgroundImages, Shadows, Gradients, Shape, Padding, Width, Height, Alignment, Transforms.. 등등을 애니메이션 할 수 있습니다.
class AnimatedContainerScreen extends StatefulWidget {
@override
_AnimatedContainerScreenState createState() =>
_AnimatedContainerScreenState();
}
class _AnimatedContainerScreenState extends State<AnimatedContainerScreen> {
bool selected = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('AnimatedContainer')),
body: GestureDetector(
onTap: () {
setState(() {
selected = !selected;
});
},
child: Center(
child: AnimatedContainer(
width: selected ? 300.0 : 100.0,
height: selected ? 100.0 : 300.0,
alignment:
selected ? Alignment.center : AlignmentDirectional.topCenter,
duration: Duration(milliseconds: 500),
decoration: BoxDecoration(
border: selected
? Border.all(color: Colors.black, width: 3)
: Border.all(color: Colors.red, width: 3),
gradient: new LinearGradient(
begin: FractionalOffset.topCenter,
end: FractionalOffset.bottomCenter,
colors: selected
? [Colors.lightGreen, Colors.redAccent]
: [Colors.orange, Colors.deepOrangeAccent],
stops: [0.0, 1.0],
),
color: selected ? Colors.red : Colors.blue,
),
curve: Curves.fastOutSlowIn,
child: FlutterLogo(size: 75),
),
),
),
);
}
}
AnimatedDefaultTextStyle
AnimatedDefaultTextStyle는 TextSytle이 바뀔 때마다 자동적으로 애니메이션 합니다.
📌 textAlign, softWrap, textOverflow, maxLines 속성은 애니메이션 변경할 때 즉시 적용되지 않습니다
class AnimatedDefaultTextStyleScreen extends StatefulWidget {
@override
_AnimatedDefaultTextStyleScreenState createState() =>
_AnimatedDefaultTextStyleScreenState();
}
class _AnimatedDefaultTextStyleScreenState
extends State<AnimatedDefaultTextStyleScreen> {
bool selected = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('AnimatedDefaultTextStyle')),
body: GestureDetector(
onTap: () {
setState(() {
selected = !selected;
});
},
child: Center(
child: Container(
height: 120,
child: AnimatedDefaultTextStyle(
duration: const Duration(milliseconds: 300),
style: TextStyle(
fontSize: 50.0,
color: selected ? Colors.red : Colors.blueAccent,
fontWeight: selected ? FontWeight.w100 : FontWeight.bold,
),
child: Text('Flutter'),
),
),
),
),
);
}
}
AnimatedOpacity
지정된 opacity가 변경될 때마다 지정된 duration 동안 child의 opacity를 자동으로 애니메이션 합니다.
class AnimatedOpacityScreen extends StatefulWidget {
@override
_AnimatedOpacityScreenState createState() => _AnimatedOpacityScreenState();
}
class _AnimatedOpacityScreenState extends State<AnimatedOpacityScreen> {
bool selected = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('AnimatedOpacity')),
body: GestureDetector(
onTap: () {
setState(() {
selected = !selected;
});
},
child: Center(
child: Container(
height: 120.0,
width: 120.0,
color: Colors.blue[50],
child: AnimatedOpacity(
opacity: selected ? 0.0 : 1.0,
duration: Duration(milliseconds: 500),
child: FlutterLogo(
size: 60,
),
),
),
),
),
);
}
}
AnimatedPadding
패딩이 변경되면 자동으로 애니메이션 합니다. 애니메이션 버전의 Padding입니다.
class AnimatedPaddingScreen extends StatefulWidget {
@override
_AnimatedPaddingScreenState createState() => _AnimatedPaddingScreenState();
}
class _AnimatedPaddingScreenState extends State<AnimatedPaddingScreen> {
bool selected = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('AnimatedPadding')),
body: GestureDetector(
onTap: () {
setState(() {
selected = !selected;
});
},
child: Center(
child: Container(
height: 300.0,
width: 300.0,
child: AnimatedPadding(
padding: selected
? EdgeInsets.only(top: 100, bottom: 100)
: EdgeInsets.only(left: 100, right: 100),
curve: Curves.ease,
duration: Duration(seconds: 1),
child: Container(
color: Colors.blue,
child: Center(child: Text('Flutter')),
),
),
),
),
),
);
}
}
AnimatedPhysicalModel
borderRadius와 elevation이 변경될 때마다 자동으로 애니메이션 됩니다. PhysicalModel의 애니메이션 버전입니다.
📌 shape는 애니메이션 되지 않습니다.
class AnimatedPhysicalModelScreen extends StatefulWidget {
@override
_AnimatedPhysicalModelScreenState createState() =>
_AnimatedPhysicalModelScreenState();
}
class _AnimatedPhysicalModelScreenState
extends State<AnimatedPhysicalModelScreen> {
bool selected = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('AnimatedPhysicalModel')),
body: GestureDetector(
onTap: () {
setState(() {
selected = !selected;
});
},
child: Center(
child: AnimatedPhysicalModel(
duration: const Duration(milliseconds: 500),
curve: Curves.fastOutSlowIn,
elevation: selected ? 0 : 6.0,
shape: BoxShape.rectangle,
shadowColor: Colors.black,
color: Colors.white,
borderRadius: selected
? const BorderRadius.all(Radius.circular(0))
: const BorderRadius.all(Radius.circular(10)),
child: Container(
height: 120.0,
width: 120.0,
color: Colors.blue[50],
child: FlutterLogo(
size: 60,
),
),
),
),
),
);
}
}
AnimatedPositioned
Stack위젯에서 자식의 위치를 제어하며
지정된 위치가 변경될 때마다 지정된 기간 동안 자녀의 위치를 자동으로 애니메이션 합니다.
class AnimatedPositionedScreen extends StatefulWidget {
@override
_AnimatedPositionedScreenState createState() =>
_AnimatedPositionedScreenState();
}
class _AnimatedPositionedScreenState extends State<AnimatedPositionedScreen> {
bool selected = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('AnimatedPositioned')),
body: GestureDetector(
onTap: () {
setState(() {
selected = !selected;
});
},
child: Stack(
children: <Widget>[
AnimatedPositioned(
duration: const Duration(milliseconds: 500),
curve: Curves.fastOutSlowIn,
left: selected ? 10 : 100,
top: selected ? 70 : 100,
right: selected ? 10 : 100,
bottom: selected ? 70 : 100,
child: Container(
color: Colors.blue,
),
),
],
),
),
);
}
}
AnimatedTheme
지정된 Theme이 변경될 때마다 지정된 Duration 동안 색상 등을 자동으로 전환하는 Theme 애니메이션 버전.
class AnimatedThemeScreen extends StatefulWidget {
@override
_AnimatedPositionedDirectionalScreenState createState() =>
_AnimatedPositionedDirectionalScreenState();
}
class _AnimatedPositionedDirectionalScreenState
extends State<AnimatedThemeScreen> with SingleTickerProviderStateMixin {
bool selected = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('AnimatedTheme')),
body: GestureDetector(
onTap: () {
setState(() {
selected = !selected;
});
},
child: AnimatedTheme(
data: selected ? ThemeData.light() : ThemeData.dark(),
duration: const Duration(milliseconds: 500),
child: Center(
child: Card(
child: const Padding(
padding: EdgeInsets.all(16),
child: Text(
'theme',
style: TextStyle(fontSize: 24),
),
),
),
),
),
),
);
}
}
AnimatedSize
주어진 자녀의 크기가 변할 때마다 주어진 기간 동안 자동으로 크기를 전환하는 애니메이션 위젯
import 'package:flutter/material.dart';
class AnimatedSizeScreen extends StatefulWidget {
@override
_AnimatedPositionedDirectionalScreenState createState() =>
_AnimatedPositionedDirectionalScreenState();
}
class _AnimatedPositionedDirectionalScreenState
extends State<AnimatedSizeScreen> with SingleTickerProviderStateMixin {
bool selected = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('AnimatedSize')),
body: GestureDetector(
onTap: () {
setState(() {
selected = !selected;
});
},
child: Container(
height: 300,
child: Center(
child: AnimatedSize(
duration: const Duration(milliseconds: 500),
curve: Curves.fastOutSlowIn,
child: Container(
width: selected ? 300 : 200,
height: selected ? 160 : 200,
color: Colors.blue,
),
vsync: this,
),
),
),
),
);
}
}
AnimatedCrossFade
두 자식 사이에서만 페이드 되지만 크기를 보강하며 뒤집습니다.
class AnimatedCrossFadeScreen extends StatefulWidget {
@override
_AnimatedCrossFadeScreenState createState() =>
_AnimatedCrossFadeScreenState();
}
class _AnimatedCrossFadeScreenState extends State<AnimatedCrossFadeScreen> {
bool selected = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('AnimatedCrossFade')),
body: GestureDetector(
onTap: () {
setState(() {
selected = !selected;
});
},
child: Center(
child: AnimatedCrossFade(
duration: const Duration(milliseconds: 500),
firstChild: const FlutterLogo(
style: FlutterLogoStyle.horizontal, size: 100.0),
secondChild:
const FlutterLogo(style: FlutterLogoStyle.stacked, size: 100.0),
crossFadeState:
selected ? CrossFadeState.showFirst : CrossFadeState.showSecond,
),
)),
);
}
}
AnimatedSwitcher
기본적으로 새 위젯과, AnimatedSwitcher에서 자식으로 설정 한 위젯 간에 크로스 페이드를 수행하는 위젯입니다.
import 'package:flutter/material.dart';
class AnimatedSwitcherScreen extends StatefulWidget {
@override
_AnimatedPositionedDirectionalScreenState createState() =>
_AnimatedPositionedDirectionalScreenState();
}
class _AnimatedPositionedDirectionalScreenState
extends State<AnimatedSwitcherScreen> {
int _count = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('AnimatedSwitcher')),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
AnimatedSwitcher(
duration: const Duration(milliseconds: 500),
transitionBuilder: (Widget child, Animation<double> animation) {
return ScaleTransition(child: child, scale: animation);
},
child: Text(
'$_count',
key: ValueKey<int>(_count),
style: Theme.of(context).textTheme.display1,
),
),
Container(
padding: EdgeInsets.all(30.0),
child: RaisedButton(
child: const Text('Increment'),
onPressed: () {
setState(() {
_count += 1;
});
},
),
),
],
),
);
}
}
샘플 코드
https://github.com/qjatjr1108/Flutter-Animation