Flutter’da Animasyonlar: Implicit Animation Widgets

Mirkan
Flutter İzmir
Published in
5 min readFeb 4, 2020

Animasyonların güzel olduğu kadar işlevsel olduğunu da biliyor muydunuz? Animasyonlar ile kullanıcılarınıza anlamlı geribildirimler verebilirsiniz, onların odağını istediğiniz bir yere yönlendirebilirsiniz, uygulamadaki bir sonraki adım için ipucu verebilirsiniz veya ekranda olan değişiklikleri daha kolay görülmeleri için belirtebilirsiniz. Güzel olduklarından zaten bahsetmiştik, haydi başlayalım.

Implicit Animation Widgets

Flutter’da animasyon kullanmanın en kolay yolu bu widget’lar. Animasyonlar hakkında birşey neredeyse hiçbir şey bilmenize gerek yok.

Flutter’la biraz uğraştıysanız aşağı yukarı herşeyin widget olduğunu fark etmişsinizdir. Opaklığı mı değiştirmek istiyorsunuz, Opacity widget’ı var, Padding mi eklemek istiyorsunuz, Padding widget’ı var.

Implicit Animation Widgets, (Türkçemize Örtülü Animasyon Widget’ları diye çevirebiliriz belki, sadece bir öneri) üstte saydığım ve daha birçok widget’ın başına Animated eklenmiş hali. Mesela AnimatedOpacity, AnimatedPadding, AnimatedContainer, AnimatedPositioned ve benzeri.

Burdaki akış ise şu şekilde, siz Animated bir widget yaratıyorsunuz, sonra bu widget’a verdiğiniz bir özelliği değiştiriyorsunuz, misal color, height veya position, widget tekrar yaratıldığında bir önceki özellikten bir sonraki özelliğe verdiğiniz sürede, verdiğiniz animasyon ile geçiyor.

Dart Pad artık Flutter kodlarını çalıştırabiliyor, aşağıdaki kodun çıktısını görmek ve oynamak için bu yazıya Dart Pad linkleri koyacağım.

AnimatedOpacity

AnimatedOpacity Dart Pad

class AnimatedOpacityExample extends StatefulWidget {
@override
_AnimatedOpacityExampleState createState() => _AnimatedOpacityExampleState();
}

class _AnimatedOpacityExampleState extends State<AnimatedOpacityExample> {
double opacityLevel = 0.0;
@override
Widget build(context) {
return Column(
children: <Widget>[
FlutterLogo(
size: 200,
),
MaterialButton(
child: Text(
"Detayları Göster",
style:
Theme.of(context).textTheme.button.copyWith(color: Colors.blue),
),
onPressed: () {
setState(() {
opacityLevel = 1.0;
});
},
),
AnimatedOpacity(
duration: Duration(seconds: 2),
curve: Curves.fastOutSlowIn,
opacity: opacityLevel,
child: Text(
"Flutter, Google tarafından oluşturulan açık kaynaklı bir UI yazılım geliştirme kitidir. Android, iOS, Windows, Mac, Linux, Google Fuşya ve web uygulamaları geliştirmek için kullanılır.",
style: Theme.of(context).textTheme.body1.copyWith(
fontSize: 16,
color: Colors.white,
),
),
)
],
);
}
}

Örneği incelediyseniz, State class’ımızın içinde opacityLevel adında bir değişken var, bu başlangıçta 0.0 double değerine sahip. Bu değişkeni AnimatedOpacity widget’ının opacity parametresine veriyorum. Detayları Göster yazan butona basıldığında, setState’in içinde bu değişkeni 1.0'a eşitliyorum. Bu işlemi setState içinde yaptığımda framework, yani Flutter, bu değişiklikten etkilenecek widget’ları tekrar yaratıyor. AnimatedOpacity sayesinde bu değişiklik verdiğimiz sürede (Duration)’da oluyor.

Animated diğer widgetlar ile devam etmeden önce Curve kavramına bir açıklık getirelim. Burdaki Curve’ü, animasyonun verdiğimiz süreyi nasıl kullancağı belirten bir eğri olarak düşünebiliriz.

0.0'dan 1.0'a 2 saniyede bir geçiş oldu. Bu geçiş bir anda olmadı ve bu süre zarfında biz 0.0 ve 1.0 arasındaki diğer opaklık değerlerini de gördük, 0.1, 0.2, 0.85 ve benzeri. İşte bu değerler arasında bu sürenin nasıl paylaşılacağını Curve belirtiyor. Her değerde eşit vakit mi geçirecek yoksa ortadaki değerlerde biraz oyalanıp sonra tekrar hızlanacak mı, hızlı başlayıp yavaşlayarak mı bitirecek gibi bir sürü karakteristiği Curve ile verebiliriz. Flutter’da da halihazırda baya bir Curve var, kendiniz de matematik kütüphanesindeki methodları da kullanıp Curve oluşturabilirsiniz.

Curve’lerin karakteristiğini GIF üzerinde görmek isterseniz, dökümanda bunlara yer verilmiş, inceleyebilirsiniz.

AnimatedContainer

Container widget’ında epey bir parametre var, implicit animation widget’larının adeta şaha kalktığı yer burası😄

AnimatedContainer Dart Pad

class AnimatedContainerExample extends StatefulWidget {
@override
_AnimatedContainerExampleState createState() =>
_AnimatedContainerExampleState();
}
class _AnimatedContainerExampleState extends State<AnimatedContainerExample> {
Color color;
double borderRadius;
double margin;
@override
void initState() {
super.initState();
color = Colors.deepPurple;
borderRadius = randomBorderRadius();
margin = randomMargin();
}
void change() {
setState(() {
margin = randomMargin();
borderRadius = randomBorderRadius();
color = randomColor();
});
}
@override
Widget build(BuildContext context) {
return Container(
height: 500,
width: 300,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
SizedBox(
width: 128,
height: 128,
child: AnimatedContainer(
curve: Curves.bounceIn,
duration: Duration(
milliseconds: 600,
),
margin: EdgeInsets.all(margin),
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(borderRadius),
),
),
),
FloatingActionButton.extended(
label: Text("Değiştir"),
icon: Icon(Icons.change_history),
onPressed: change,
)
],
),
);
}
}
double randomBorderRadius() {
return Random().nextDouble() * 64;
}
double randomMargin() {
return Random().nextDouble() * 64;
}
Color randomColor() {
return Color.fromARGB(
Random().nextInt(256),
Random().nextInt(256),
Random().nextInt(256),
Random().nextInt(256),
);
}

Color, borderRadius, ve margin’e verdiğim değişkenleri, butona her basıldığında rastgele değerlere eşitliyoruz, bunu da setState içinde yapıyoruz.(change fonksiyonu). Yine Animated widget’ın içinde Curve ve Duration var, karakteristiği ve süreyi vermek için.

AnimatedPositioned

Positioned widget’ını sadece Stack widget’ının içinde kullanabiliyorsunuz, Stack widget’ındaki child widget’ları pozisyonlamak, yerini belirtmek için kullanıyoruz. AnimatedPositioned’da yine aynı şekilde. Bu widget’ın içindeki top, left, right ve bottom gibi parametreler ile, soldan sağdan yukarıdan aşağıdan kaç piksel içeride olacağını belirtiyoruz. Örnek verecek olursak, top parametresine 20 verirseniz, yukarıdan 20 piksel aşağıda olur.

AnimatedPositioned Dart Pad

class AnimatedPositionedExample extends StatefulWidget {
@override
_AnimatedPositionedExampleState createState() =>
_AnimatedPositionedExampleState();
}
class _AnimatedPositionedExampleState extends State<AnimatedPositionedExample> {
double right = 10;
double top = 10;
@override
Widget build(BuildContext context) {
return Container(
height: 500,
width: 500,
child: Stack(
children: <Widget>[
AnimatedPositioned(
curve: Curves.elasticIn,
duration: Duration(seconds: 1),
right: right,
top: top,
child: FlutterLogo(
size: 200,
colors: Colors.red,
),
),
Align(
alignment: Alignment.bottomCenter,
child: RaisedButton.icon(
label: Text("Yer değiştir"),
icon: Icon(Icons.track_changes),
onPressed: () {
setState(() {
right += 50;
top += 50;
});
},
),
)
],
),
);
}
}

Stack içinde AnimatedPositioned widget’ımız var. Başlangıçta 10'a eşit olan top ve right değişkenlerine sahip. Yani altındaki FlutterLogo widget’ını bu Stack widget’ında sağdan ve üstten 10 piksel içeride konumlandırıyor. Butona bastığımızda top ve right’a 50 daha ekliyoruz ve konumu her bastığımızda verdiğimiz sürede ve curve’de değişiyor. Sol aşağı doğru kayan bir FlutterLogo’muz oluyor.

AnimatedDefaultTextStyle

DefaultTextStyle widget’ı altında bulunan Text widget’larına stil sağlıyor. Bunun için style parametresine TextStyle veriyoruz. Örnekte ise _isSelected isimli bir boolean değişkenimiz var, widget’ın style parametresinde elvis operatörü(?:) kullanarak, bu değişken true ise :’dan önceki, değilse :’dan sonraki TextStyle’ı vermiş oluyoruz. Yine bir butonumuz var ve setState ile _isSelected değişkenini true ise false, false ise true yapıyoruz. AnimatedDefaultTextStyle widget’ı bu 2 TextStyle arasında, verdiğimiz Duration ve Curve ile bir geçiş sağlıyor.

AnimatedDefaultTextStyle Dart Pad

class _AnimatedDefaultTextStyleExampleState
extends State<AnimatedDefaultTextStyleExample> {
bool _isSelected = false;
@override
Widget build(BuildContext context) {
return Container(
height: 500,
width: 650,
child: Stack(
children: <Widget>[
Align(
alignment: Alignment.topCenter,
child: AnimatedDefaultTextStyle(
curve: Curves.easeOutExpo,
duration: Duration(milliseconds: 600),
style: _isSelected
? TextStyle(
fontSize: 50,
fontWeight: FontWeight.w900,
color: Colors.deepPurple,
fontStyle: FontStyle.italic)
: TextStyle(
fontSize: 30,
fontWeight: FontWeight.w200,
color: Colors.red,
backgroundColor: Colors.amber,
),
child: Text("AnimatedDefaultTextStyle"),
),
),
Align(
alignment: Alignment.center,
child: FloatingActionButton(
backgroundColor: Colors.amber,
child: Icon(
Icons.arrow_upward,
color: Colors.green,
),
onPressed: () {
setState(() {
_isSelected = !_isSelected;
});
},
),
)
],
),
);
}
}

Implicit Animation Widget örneklerimiz bu kadar, benzer mantıkta çalışan daha çok implicit animation widget’ını dökümanda bulabilirsiniz. Bir sonraki yazımızda animasyonlara derinlemesine dalıp, animasyonları kontrol etmeyi öğreneceğiz.

Sorularınızı Twitter @mirkancal ve LinkedIn üzerinden yazabilirsiniz, sağlıcakla kalın.

Diğer yazılarıma da göz atabilirsiniz.
Flutter’da Temiz Routing
Flutter Web uygulamasını Peanut ile Github Pages’e Dağıtma
Flutter Uygulama Mimarisi: BLoC
Flutter’da SVG Kullanımı
Flutter’da Pusher Kullanımı

--

--