Flutter’da CustomPainter İle Animasyon Oluşturmak
Merhaba değerli okurlar, bu yazımda sizlere Flutter’da CustomPainter İle Animasyon Oluşturmak konusunu aktarmaya çalışıyor olacağım. Daha önceki animasyonlar yazımda sizlere temel kavramlarını aktarmaya çalıştım. Bu yazımda ise bu temelleri daha fazla geliştirip güçlendireceğiz.
Yazıma geçmeden hemen önce sizlere destekleriniz, beğenileriniz ve paylaşımlarınız için teşekkür ederim. Beğenileriniz, paylaşım ve yorumlarınız beni oldukça motive edip mutlu ediyor. Alanımda Türkçe içerik üretmek ve ortaya çıkarmak en büyük tutkularımdan biri. Bu yolda destekleyen herkese teşekkür ederim.
Şimdiye kadar Flutter’ın bize sunduğu hazır animasyonlu widget’ları (AnimatedContainer) ve temel yapıları (AnimationController) kullanarak harikalar yarattık. Peki ya bu yapılar hayalinizdeki animasyon için yeterli değilse? Ya bir butonu büyütmekten daha fazlasını isteyip, ekranda canlanan bir veri grafiği, nefes alıp veren bir yükleme göstergesi veya tamamen size özgü, eşsiz bir görsel efekt yaratmak isterseniz?
İşte bu noktada Flutter’ın sihirli değneğini elimize alıyoruz: CustomPainter.
CustomPainter, Flutter’daki en güçlü araçlardan biridir. Size boş bir tuval verir ve bu tuval üzerine programatik olarak istediğiniz her şeyi çizme özgürlüğü tanır. Hazır widget’ların kısıtlamalarından kurtulup, uygulamanızın görsel kimliğini en ince detayına kadar şekillendirmenizi sağlar.
Bu bölümde, bu boş tuvali nasıl bir animasyon sahnesine dönüştüreceğimizi, AnimationController’dan gelen gücü piksellere nasıl işleyeceğimizi öğreneceğiz.
Elbette işe yine konunun temellerini öğrenerek başlayacağız.
Temel Kavramlar
CustomPainter ile çalışmaya başlamadan önce atölyemizdeki üç temel aracı tanımalıyız.
- Canvas: Bu, bizim dijital çizim yüzeyimizdir. Üzerine çizgiler çizebileceğimiz, daireler, dörtgenler ve hatta karmaşık yollar (paths) oluşturabileceğimiz alandır. Koordinat sistemi, sol üst köşenin (0, 0) olduğu standart bir yapıdadır.
- Paint: Tuval üzerine ne çizeceğimizi söyler, ama Paint nesnesi nasıl çizeceğimizi belirler. Tıpkı bir ressamın farklı fırçaları ve boyaları olması gibi. Paint ile şunları ayarlayabilirsiniz:
- color: Çizimin rengi.
- style: PaintingStyle.fill (içi dolu) mi, yoksa PaintingStyle.stroke (sadece kenarlık) mı olacağı.
- strokeWidth: Kenarlık kalınlığı.
- shader: Gradyan gibi daha karmaşık dolgular.
3. CustomPainter: Bu, çizim mantığını barındıran asıl sınıftır. Bizden iki önemli metodu uygulamamızı ister:
- paint(Canvas canvas, Size size): Tüm çizim işlemlerinin yapıldığı yer burasıdır. Flutter bize bir canvas (tuval) ve çizim yapabileceğimiz alanın boyutunu (size) verir.
- shouldRepaint(covariant CustomPainter oldDelegate): Bu, performans için hayati bir metottur. Flutter, bu metodu çağırarak “Hey, yeniden çizime gerek var mı?” diye sorar. Eğer true dönerse paint metodu tekrar çalışır. Animasyonlarda, her karede bir değişiklik olduğu için genellikle true döndürmemiz gerekir.
Peki AnimationController’ın ürettiği o soyut 0.0–1.0 değerini, tuval üzerindeki bir çizime nasıl dönüştürürüz? Süreç tam olarak şöyledir:
- Değeri Aktar: AnimationController’dan (veya herhangi bir Animation nesnesinden) gelen anlık değeri, CustomPainter sınıfımıza bir parametre olarak geçiririz.
- Hesapla: paint metodunun içinde, bu animasyon değerini kullanarak çizilecek şekillerin özelliklerini (yarıçap, pozisyon, renk, opaklık vb.) hesaplarız. Örneğin, animasyon değeri 0.5 ise dairenin yarıçapını maksimum yarıçapın %50'si olarak ayarlayabiliriz.
- Çiz: Hesaplanan bu değerlerle canvas üzerine çizim yaparız (canvas.drawCircle, canvas.drawLine vb.).
- Tekrarla: AnimatedBuilder gibi bir yapı, AnimationController her yeni değer ürettiğinde (yani her karede), CustomPainter’ı yeni animasyon değeriyle yeniden oluşturur ve shouldRepaint true döndüğü için paint metodu tekrar çalıştırılır. Bu döngü saniyede 60 kez tekrarlandığında, ortaya akıcı bir animasyon çıkar.
Yine çok fazla teorik kavram ve bilgiye boğulduğunuzu düşünüyorsunuz fakat bu kavramlar ne yapabileceğimizi anlamamız için önemli kavramlar.
Gelin bu kavramları birde örnek üzerinden görelim
Şimdi, yavaşça büyüyüp küçülen ve aynı anda rengi solup canlanan, “nefes alıp veriyormuş” hissi veren bir daire çizelim. Bu, CustomPainter’ın gücünü anlamak için harika bir örnektir.
Önce ressamımızı, yani CustomPainter sınıfımızı oluşturalım:
import 'dart:math';
import 'package:flutter/material.dart';
class PulsingCirclePainter extends CustomPainter {
final Animation<double> animation;
// 1. Animasyon değerini dışarıdan alıyoruz.
PulsingCirclePainter(this.animation) : super(repaint: animation);
@override
void paint(Canvas canvas, Size size) {
// Tuvalin merkez noktasını buluyoruz.
final center = Offset(size.width / 2, size.height / 2);
// 2. Animasyonun mevcut değerini alıyoruz.
// Controller'ımız 0.0 -> 1.0 -> 0.0 şeklinde hareket edecek.
final value = animation.value;
// 3. Değere göre yarıçapı hesaplıyoruz.
// Daire, mevcut alanın %5'i ile %40'ı arasında büyüyüp küçülecek.
final radius = size.width * (0.05 + value * 0.35);
// 4. Değere göre opaklığı hesaplıyoruz.
// Değer 0'a (en küçük hal) yaklaştıkça opaklık 1'e (tam görünür) yaklaşsın.
final opacity = 1.0 - value;
// 5. Boyamızı (fırçamızı) hazırlıyoruz.
final paint = Paint()
..color = Colors.blue.withOpacity(opacity)
..style = PaintingStyle.fill;
// 6. Tuvale daireyi çiziyoruz.
canvas.drawCircle(center, radius, paint);
}
@override
bool shouldRepaint(covariant PulsingCirclePainter oldDelegate) {
// 7. Animasyon değeri değiştiyse yeniden çizim yap.
// Zaten 'super(repaint: animation)' ile bunu hallettik ama manuel yolu budur:
// return oldDelegate.animation.value != animation.value;
return false; // 'super'a güvendiğimiz için false dönebiliriz.
}
}Bu kodları görüp bitir hoca bitir dediğinizi duyar gibiyim ama sabırlı olun
- CustomPainter’ımızın Animation<double> tipinde bir parametre almasını sağlıyoruz. super(repaint: animation) ifadesi, Flutter’a bu animation nesnesini dinlemesini ve o değiştikçe otomatik olarak yeniden çizim yapmasını söyleyen sihirli bir kısayoldur. Bu, shouldRepaint’i manuel yönetme ihtiyacını ortadan kaldırır.
- animation.value ile o anki animasyon değerini (0.0 ile 1.0 arasında) alıyoruz.
- Basit bir matematik formülü ile value’yu bir yarıçap değerine dönüştürüyoruz.
- Benzer şekilde value’yu bir opaklık değerine dönüştürüyoruz. Animasyon ilerledikçe (daire büyüdükçe) opaklığın azalmasını sağlıyoruz.
- Paint nesnemizi oluşturup hesapladığımız opaklık değeriyle rengini ayarlıyoruz.
- canvas.drawCircle() ile merkez noktası, yarıçapı ve boyası belli olan dairemizi çiziyoruz.
- super(repaint: animation) kullandığımız için shouldRepaint’in mantığını bizim yazmamıza gerek kalmaz, false dönebiliriz. Eğer bunu kullanmasaydık, eski ve yeni animasyon değerlerini karşılaştırarak true veya false döndürmemiz gerekirdi.
Şimdi bu ressamı kullanacak olan StatefulWidget’ı oluşturalım.
import 'package:flutter/material.dart';
import 'pulsing_circle_painter.dart'; // Painter'ımızı import ediyoruz.
class CustomPainterAnimationExample extends StatefulWidget {
const CustomPainterAnimationExample({Key? key}) : super(key: key);
@override
State<CustomPainterAnimationExample> createState() =>
_CustomPainterAnimationExampleState();
}
class _CustomPainterAnimationExampleState extends State<CustomPainterAnimationExample>
with SingleTickerProviderStateMixin {
late final AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 2),
)..repeat(reverse: true); // 1. İleri git, sonra geri gel. "Nefes alma" efekti.
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('CustomPainter Animasyon')),
body: Center(
// 2. AnimatedBuilder yerine doğrudan CustomPaint kullanabiliriz,
// çünkü painter'ımız zaten animasyonu dinliyor.
child: CustomPaint(
// 3. Painter'ımızı oluşturup animasyon controller'ını veriyoruz.
painter: PulsingCirclePainter(_controller),
// 4. Çizim alanının boyutunu belirliyoruz.
child: const SizedBox(
width: 200,
height: 200,
),
),
),
);
}
}- AnimationController’ı oluştururken ..repeat(reverse: true) kullanıyoruz. Bu, animasyonun 0.0'dan 1.0'a gitmesini, sonra 1.0'dan 0.0'a geri dönmesini ve bu döngüyü sonsuza dek tekrarlamasını sağlar. Bu, “nefes alma” (pulsating) efekti için mükemmeldir.
- Bu senaryoda AnimatedBuilder’a ihtiyacımız yok, çünkü super(repaint: animation) sayesinde CustomPaint widget’ı zaten animasyonu dinliyor ve kendini güncelliyor. Bu, kodu daha temiz hale getirir.
- CustomPaint widget’ının painter özelliğine, PulsingCirclePainter’ımızın bir örneğini _controller’ı parametre olarak vererek atıyoruz.
- CustomPaint’in boyutu varsayılan olarak sıfırdır. Çizim yapabilmesi için ona bir boyut vermeliyiz. Bunu child olarak bir SizedBox vererek veya CustomPaint’i bir SizedBox ile sarmalayarak yapabiliriz.
Custompainter ile animasyon oluşturmak kafa karaştırıcı görünebilir ancak pratikler yaparak, özellikle yapay zekadan faydalanarak istediğiniz animasyonları çıkarabilirsiniz.
Üzüldüğünüzü biliyorum fakat bir yazımızın daha sonuna geldik. Umarım bu yazı ilgilisi ve meraklısı için faydalı olur.
Sonraki yazılarda görüşmek üzere…
Github: www.github.com/abdullah017
Linkedin: www.linkedin.com/in/abdullahtas
Stackoverflow: https://stackoverflow.com/users/13807726/abdullah-t
#FREEPALESTINA
