Jetpack Compose ile Animasyon II

Ömer Durmaz
6 min readDec 19, 2022

Merhaba bu yazımıda Jetpack Compose animasyonlarından bahsetmeye devam edeceğiz. Bir önceki yazımızda üst düzey animasyonlardan bahsetmiştik ki o yazıya buradan ulaşabilirsin;

Bu yazımıda ise androidin bize sunduğu animasyon dökümanındaki kalan başlıklara değineceğiz. O zaman başlayalım;

Düşük seviye animasyon apileri

Bir önceki yazıda bahsettiğimiz üst düzey animasyon apileri’ nin hepsi şimdi bahsedeceğimiz düşük seviyeli animasyon apilerinin ya da daha temel animasyon methodlarının üzerine kurulmuştur.

Örnek verecek olursak animate*AsState methodları bir önceki yazıda bahsettiğimiz gibi basit bir değer değişikliğini animasyon olarak dönen method olduğunu biliyoruz. İşte gerçekleşen bu olayAnimatable apisi tarafından desteklenmektedir.

Ayrıca updateTransition aynı anda birden çok animasyonu çalıştırmamıza ve tek bir yerden yönetmemize yarayan method olduğundan bahsetmiştik. rememberInfiniteTransition da benzer bir şekilde birden çok animasyonu aynı anda çalıştırabilir ancak updateTransition dan farkı animasyonlar sonsuz bir şekilde tekrar eder demiştik.

Bu üç animasyon methodlarının, yani üst düzey animasyon apilerinin ortak yanı sadece composable methodlar içinde çalışmalarıdır. Composable methodlar dışında oluşturulamazlar. Ancak düşük seviyeli animasyon apileri composable methodlar dışında da oluşturulabildikleri için daha avantajlıdırlar.

Bütün bu animasyon apilerinin daha anlaşılır olması açısından aşağıdaki şemayı inceleyebilirsin;

Şemada da görüldüğü üzere üst düzey ya da alt seviye animasyon apilerinin hepsi Animation apisini temel alır. Çoğu modern uygulama doğrudan Animation apisi ile etkileşim içinde olmasa da görüldüğü üzere daha üst seviye apiler sayesinde oluşturulan özelleştirmeleri kullanmaktadırlar. Animasyonların özelleştirmeleri hakkında daha fazla bilgi almak istiyorsan yazının devamındaki Animasyonları Özelleştirme başlığına bakabilirsin.

Animatable

Animatable bir değer tutan ve değerin değişmesi durumunu anime eden bir api dir. Bunun için animateTo methodu kullanılır. Yukarıda da bahsettiğimiz gibi animate*AsState methodu bu apiyi temel almaktadır.

Animatable apisinin animateTo methodu dahil bir çok methodu suspend method olarak tanımlanmıştır. Bundan dolayı herhangi bir Animatable methodu kullanmak istediğimizde bir coroutineScope çalıştırmamız gerekmektedir. Aşağıdaki örnekteki gibi LaunchedEffect ile bir animasyonu tetikleyebiliriz.

Yukarıdaki örnekte görüldüğü gibi Animatable remember içinde bir Animatable instance’ i oluşturuyor ve başlangıç değeri tanımlıyoruz. ok değeri true olduğu durumda rengi yeşil, false olduğu durumda ise kırmızı renge anime ediyoruz.

Dipnot: Mevcut instance içinde animasyon henüz tamamlanmamışken yeni bir animasyon çalıştırılmaya kalkışılırsa devam eden animasyon durdurulur ve yeni animasyon başlatılır.

Dipnot: Animatable Float ve Color tiplerini de desteklemektedir. Ancak TwoWayConverter ile diğer veri tiplerinde de animasyon oluşturabiliriz.

Animation

Animation mevcutta bulunan en alt seviye animasyon apisidir. Şu ana kadar gördüğümüz bütün animasyon apileri Animation apisini temel almaktadır. Animation ‘ un TargetBasedAnimation ve DecayAnimation olmak üzere iki alt tipi vardır;

TargetBasedAnimation:

Diğer apiler bir çok alanda ihtiyacımızı karşılar, ancak TargetBasedAnimation ‘ ı doğrudan kullanmak, animasyon oynatma süresini kendiniz kontrol etmenize olanak tanır. Aşağıdaki örnekte, TargetAnimation ‘ın oynatma süresi, withFrameNanos tarafından sağlanan kare süresine göre manuel olarak kontrol edilir.

DecayAnimation:

TargetBasedAnimation ‘ dan farklı olarak DecayAnimation targetValuedeğerinin tanımlanmasını zorunlu kılmaz. Onun yerine InitialVelocity ve InitialValue ile sağlanan ve DecayAnimationSpec tarafından ayarlanan başlangıç koşullarına göre kendisi targetValue değerini hesaplar.

Not: DecayAnimation genellikle bir yavaşlatma ve ardından durdurma animasyonları için kullanılır.

Animasyonları Özelleştirme

Çoğu animasyon apisi özelleştirme parametrelerinin kullanılmasını destekler. Hadi bu özelleştirme parametrelerine yakından bakalım;

AnimationSpec

Çoğu animasyon apisi geliştiricinin animasyonu özelleştirmesine izin verir ve bunun için opsiyonel olananimationSpec parametresini kabul eder.

val alpha: Float by animateFloatAsState(
targetValue = if (enabled) 1f else 0.5f,
// Configure the animation duration and easing.
animationSpec = tween(durationMillis = 300, easing = FastOutSlowInEasing)
)

Farklı türde animasyon oluşturmamıza olanak sağlaması için çok farklı tipde AnimationSpec vardır;

spring:

spring başlangıç ve bitiş değerleri arasında fizik tabanlı bir animasyon oluşturmaya yarar. İki parametre ile çalışır: dampingRatio ve stiffnes . dampingRatio ne kadar zıplanmalı bunu belirler. Varsayılan değeri ise Spring.DampingRatioNoBouncy ‘ dir. Diğer değerleri aşağıda görebilirsin;

stiffnes ise başlangıç ve bitiş değerleri arasında ne kadar hızlı hareket etmeli onu belirler. Varsayılan değeri ise Stiffnes.StiffnessMediumdur.

Bilgilendirme: stiffnes diğer AnimationSpec ‘ ler içinde en akıcı şekilde animasyonu devam eden özelleştirmedir diyebiliriz. Bundan dolayı animate*AsState ve updateTransition gibi bir çok üst düzey animasyon apisinde kullanılmıştır.

twen:

twen de spring gibi başlangıç ve bitiş değerleri arasında animasyon oluşturmaya yararlar. Aralarındaki fark twen kullanırken durationMilis değeri vererek animasyon süresini belirtebilir, delayMilis kullanarak animasyonu geciktirebiliriz. Ayrıca yazının devamında bahsedeceğimiz easing parametresi alarak da animasyon doğrultu hızını belirtebiliriz.

val value by animateFloatAsState(
targetValue = 1f,
animationSpec = tween(
durationMillis = 300,
delayMillis = 50,
easing = LinearOutSlowInEasing
)
)

keyframes:

keyframes farklı kare aralıklarına farklı easing yöntemleri tanımlamamıza yarayan bir methoddur.

val value by animateFloatAsState(
targetValue = 1f,
animationSpec = keyframes {
durationMillis = 375
0.0f at 0 with LinearOutSlowInEasing // for 0-15 ms
0.2f at 15 with FastOutLinearInEasing // for 15-75 ms
0.4f at 75 // ms
0.4f at 225 // ms
}
)

Yukarıdaki örnekte görüldüğü üzere targetValue değerini istediğimiz kadar parçalara bölüyoruz. keyframes methodu tanımladıktan sonra başlangıç değerinden targetValue değerine ne kadar sürede ulaşacağını durationMilis ile belirliyoruz. Ardından bölmek istediğimiz kadar frame aralığı belirliyoruz ve kullanmak istediğimiz easing yöntemlerini tanımlıyoruz.

Dipnot: Animasyonun düzgün çalışması için burada dikkat edilmesi gereken şey bölüğümüz parçalardaki value değerlerinin toplamı targetValue eşit olması gerekmektedir

repeatable:

repeatable twen ve keyframes gibi animasyonları çalıştırabilen ve tekrarlayabilen bir methoddur. iterations parametresi ile kaç defa tekrar edeceğini, animation ile hangi animasyonu çalıştıracağımızı, repeatMode ile ise tekrar türünü belirleyebiliriz;

  • RepeatMode.Restart animasyonu en baştan tekrar eder
  • RepeatMode.Reverse animasyonu bir düz bir ters şekilde tekrar eder
val value by animateFloatAsState(
targetValue = 1f,
animationSpec = repeatable(
iterations = 3,
animation = tween(durationMillis = 300),
repeatMode = RepeatMode.Reverse
)
)

infiniteRepeatable:

infiniteRepeatable adından da anlaşılacağı üzere repeatable gibi ancak sonsuz şekilde animasyonu devam ettirir.

val value by animateFloatAsState(
targetValue = 1f,
animationSpec = infiniteRepeatable(
animation = tween(durationMillis = 300),
repeatMode = RepeatMode.Reverse
)
)

snap:

snap başlangıç değerini hedef değere aniden geçiren bir animasyon yöntemidir. delayMilis parametresi ile ne kadar süre sonra animasyonun gerçekleşeceğini belirleyebiliriz.

val value by animateFloatAsState(
targetValue = 1f,
animationSpec = snap(delayMillis = 50)
)

Easing

easing , twen ya da keyframes gibi süre bazlı animasyonlarda animasyonun sabit bir hızda hareket etmesi yerine, hızlanmasına ya da yavaşlamasına olanak tanır. easing aslında 0 ile 1.0 float değerleri arasında bir kesir değeri alan ve kayan bir noktayı döndüren işlevdir. Bu nokta aşmayı ve altında kalmayı temsil etmek için sınırın dışında olabilir.

val CustomEasing = Easing { fraction -> fraction * fraction }

@Composable
fun EasingUsage() {
val value by animateFloatAsState(
targetValue = 1f,
animationSpec = tween(
durationMillis = 300,
easing = CustomEasing
)
)
}

Jetpack compose işimizi kolaylaştırmak açısından bir çok easing methodunu bizim için oluşturmuş. Sende senin için en uygun easing methodunu buraya tıklayarak seçebilirsin.

AnimationVector

Çoğu Compose animasyon apileri Float , Color , Dp ve diğer basit data tiplerini destekler. Ama bazen farklı bir data tipinde animasyon oluşturmamız gerekebilir. Animasyon süresince herhangi bir animasyon değeri AnimationVector olarak temsil edilir. Değer TwoWayConverter tarafından bir AnimationVectore dönüştürülür. Bu sayede Çekirdek animasyon sistemi değeri düzgün bir şekilde işleyebilir.

Örneğin Int tipi tek bir float değeri tutan AnimationVector1D olarak temsil edilir. Int için TwoWatConverter kullanımı örneğini aşağıda görebilirsin;

val IntToVector: TwoWayConverter<Int, AnimationVector1D> =
TwoWayConverter({ AnimationVector1D(it.toFloat()) }, { it.value.toInt() })

Bir başka örnek Color (Red, Green, Blue ve Alpha) olarak dört değerden oluşan bir kümedir. Yani Color 4 float değeri tutan AnimationVector4De dönüştürülür.

Buradan şunu diyebiliriz ki animasyonlarda kullanılan her veri türü, boyutuna bağlı olarak AnimationVector1D , AnimationVector2D , AnimationVector3D veyaAnimationVector4D tipine dönüştürülürler.

Yine bu durumlar için Animation , temel veri tipleri için Color.VectorConverter , Dp.VectorConverter gibi dönüştürücüleri bize sunmaktadır.

Hadi bir de farklı bir data tipini TwoWayConverter ile animasyon değerine nasıl dönüştürebiliriz aşağıdaki örnekte bakalım;

Yukarıda görüldüğü üzere dp değerlerini float animasyon değerlerine dönüştürerek animasyonumuzu oluşturabiliyoruz.

Kullanıcı Hareketi ve Animasyon

Burada kullanıcı hareketinden kastımız kullanıcının ekranda yaptığı herhangi bir dokunma eylemine göre animasyon oluşturmamızdır. Aşağıda bahsettiğimiz örnekte pointerInput methodu ile kullanıcın dokunmuş olduğu pozisyonu Offset tipinde alabilir. Ardından yukarıda öğrendiğimiz çok basit Animatable yöntemi ile istediğimiz bir viewin offsetini değiştirebiliriz.

Yukarıdaki kodun çıktısı

Basit bir Gesture Animation örneğini anlatmış olduk. SwipeToDismiss gibi daha karmaşık Gesture Animation örnekleri için buraya tıklayabilirsin.

Bu yazımızda düşük seviyeli animasyon apilerinden ve daha karmaşık animasyonların nasıl oluşturulabileceğinden bahsettik. Umarım kendi animasyon geliştirmende yardımcı olur.

Okuduğun için teşekkür ederim, bir sonraki yazıda görüşmek üzere.

Kaynakça:

--

--