Denizanasını Compose'da hareket ettirme: ImageVectors'u canlandırma ve AGSL RenderEffects'i uygulama
İnternette ilham veren insanları takip etmeyi ve ne yaptıklarını görmeyi seviyorum - böyle bir kişi Cassie Codes , web için inanılmaz animasyonlar yapıyor. İlham veren örneklerinden biri de bu sevimli animasyonlu Denizanası .
Bunu gördükten ve bir süre kafama taktıktan sonra, kendi kendime bu sevimli küçük yaratığın Compose'da da canlanması gerektiğini düşündüm. Bu blog gönderisi, bunu Jetpack Compose'da nasıl yaptığımı açıklıyor, son kod burada bulunabilir . Buradaki teknikler elbette sadece denizanası için geçerli değil… başka herhangi bir balık da yapacak! Şaka yapıyorum — bu blog gönderisi şunları kapsayacak:
- Özel Görsel Vektörleri
- ImageVector Yollarını veya Gruplarını Canlandırma
- AGSL RenderEffect ile bir Composable'a distorsiyon gürültü efekti uygulama.
SVG'yi analiz etme
Bu denizanasını uygulamak için önce SVG'nin nelerden oluştuğunu görmemiz ve onun farklı kısımlarını kopyalamaya çalışmamız gerekiyor. Bir SVG'nin ne çizdiğini anlamanın en iyi yolu, çeşitli kısımlarını yorumlamak ve svg'nin her bir bölümünün oluşturduğu görsel sonucu görmektir. Bunu yapmak için, ya yukarıda bağlantısı verilen codepen'de değiştirebilir ya da bir SVG'yi indirip bir metin düzenleyicide açabilirsiniz (metin okunabilir bir formattır).
Şimdi bu SVG'ye genel bir bakış atalım:
<!--
Jellyfish SVG, path data removed for brevity
-->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 530.46 563.1">
<defs>
<filter id="turbulence" filterUnits="objectBoundingBox" x="0" y="0" width="100%" height="100%">
<feTurbulence data-filterId="3" baseFrequency="0.02 0.03" result="turbulence" id="feturbulence" type="fractalNoise" numOctaves="1" seed="1"></feTurbulence>
<feDisplacementMap id="displacement" xChannelSelector="R" yChannelSelector="G" in="SourceGraphic" in2="turbulence" scale="13" />
</filter>
</defs>
<g class="jellyfish" filter="url(#turbulence)">
<path class="tentacle"/>
<path class="tentacle"/>
<path class="tentacle" />
<path class="tentacle" />
<path class="tentacle"/>
<path class="tentacle"/>
<path class="tentacle"/>
<path class="tentacle"/>
<path class="tentacle"/>
<path class="face" />
<path class="outerJelly"/>
<path id="freckle" />
<path id="freckle"/>
<path id="freckle-4"/>
</g>
<g id="bubbles" fill="#fff">
<path class="bubble"/>
<path class="bubble"/>
<path class="bubble" />
<path class="bubble"/>
<path class="bubble"/>
<path class="bubble"/>
<path class="bubble" />
</g>
<g class="jellyfish face">
<path class="eye lefteye" fill="#b4bebf" d=""/>
<path class="eye righteye" fill="#b4bebf" d=""/>
<path class="mouth" fill="#d3d3d3" opacity=".72"/>
</g>
</svg>
- SVG'yi oluşturan yollar ve yol grupları:
- Dokunaçlar
- Yüz — damla ve dış jöle
- Gözler — canlı açık ve kapalı
- Kabarcıklar — denizanasının etrafında rastgele hareket eder — boyut ve alfa animasyonları
- M, m : Taşı
- L, l, H, h, V, v : Satırdan satıra
- C, c, S, s : Kübik Bézier eğrisi
- Q, q, T, t: İkinci dereceden Bézier eğrisi
- A, a: Eliptik yay eğrisi
- Z, z — Yolu kapat
- Denizanası yavaşça yukarı ve aşağı hareket etmelidir.
- Denizanası tıklandığında gözler yanıp sönmelidir
- Denizanası gövdesine sallanan/gürültü efekti uygulanmış olmalıdır.
Artık bu SVG'nin nelerden oluştuğunu anladığımıza göre, statik sürümü Compose'da oluşturmaya başlayalım.
Özel ImageVector oluşturma
Compose, SVG'ye benzer şekilde programlı olarak bir vektör oluşturabileceğiniz bir ImageVector konseptine sahiptir. Değiştirmeden işlemek istediğiniz vektörler/SVG'ler için, paintResource(R.drawable.vector_image) kullanarak bir VectorDrawable da yükleyebilirsiniz. Bu, Compose'un oluşturacağı bir ImageVector'a dönüştürmeyi halledecektir.
Şimdi kendinize soruyor olabilirsiniz - neden denizanasını bir SVG olarak bir xml dosyasına aktarıp painterResource(R.drawable.jelly_fish)
?
Bu harika bir soru — ve denizanasını bu şekilde yüklemek, SVG'nin türbülans özelliğini kaldırmak mümkündür ve görüntü, yüklenmiş bir XML ile işlenecektir (buradaki belgelerde açıklandığı gibi ). Ancak, tıklamayla bölümleri canlandırmak ve gövdeye bir gürültü efekti uygulamak gibi yolun ayrı bölümleriyle biraz daha fazlasını yapmak istiyoruz, böylece ImageVector
programatik olarak oluşturacağız.
Bu denizanasını Compose'da işlemek d
için, balığı oluşturan yol verilerini (veya yoldaki " " etiketini) kopyalayabiliriz, örneğin, ilk dokunaç aşağıdaki yol verilerine sahiptir:
M226.31 258.64c.77 8.68 2.71 16.48 1.55 25.15-.78 8.24-5 15.18-7.37 23-3.1 10.84-4.65 22.55 1.17 32.52 4.65 7.37 7.75 11.71 5.81 21.25-2.33 8.67-7.37 16.91-2.71 26 4.26 8.68 7.75 4.34 8.14-3 .39-12.14 0-24.28.77-36 .78-16.91-12-27.75-2.71-44.23 7-12.15 11.24-33 7.76-46.83z
Şimdi muhtemelen düşünüyorsunuz - kafamda çizim yapmam ve tüm pozisyonları ve komutları elle bilmem gerekiyor mu? Hayır, hiç de değil. Figma veya Inkscape gibi çoğu tasarım programında bir vektör oluşturabilir ve bu bilgiyi kendiniz almak için çiziminizin sonucunu bir SVG'ye aktarabilirsiniz. vay!
Compose'da vektörü oluşturmak için: öğesini çağırırız rememberVectorPainter
, bu da bir öğesini oluşturur ve bir çağrıyı ImageVector
, ardından başka bir çağrıyı yaratır ve ilk dokunaç için ilkini bunun içine yerleştiririz . Ayrıca tüm denizanası için arka plan olarak bir a ayarladık.Group
jellyfish
Group
tentacles
Path
RadialGradient
Ve aşağıdakilerin sonucu, radyal gradyan arka planıyla ekrana çizilen küçük bir dokunaçtır!
Bu işlemi SVG'nin tüm öğeleri için tekrarlıyoruz — yolun bitlerini SVG dosyasından alıp rengi ve alfayı çizilecek yola uygulayarak, ayrıca yolları mantıksal olarak dokunaçlar, yüz, yüz olarak gruplandırıyoruz. kabarcıklar vb:
Artık tüm denizanamızı yukarıdakilerle oluşturuyoruz ImageVector
:
ImageVector Yollarını ve Gruplarını Canlandırma
Bu vektörün bazı kısımlarını canlandırmak istiyoruz:
O halde, ImageVector
.
Denizanasını yukarı ve aşağı hareket ettirme
Codepen'e baktığımızda, denizanasının yukarı ve aşağı bir öteleme (y öteleme) ile hareket ettiğini görebiliriz. Bunu kompozisyonda yapmak için sonsuz bir geçiş ve translationY
3000 milin üzerinde animasyon olacak bir a yaratıyoruz, ardından denizanasını içeren grubu ve yüzü bir olacak şekilde ayarlıyoruz translationY
, bu yukarı ve aşağı animasyonu üretecek.
Harika — bölümün bir kısmı ImageVector
artık yukarı ve aşağı hareket ediyor, baloncukların aynı konumda kaldığını fark edeceksiniz.
Göz kırpıyor ️
Codepen'e baktığımızda her bir gözün üzerinde scaleY
ve opacity
animasyonu olduğunu görebiliriz. Group
Bu iki değişkeni oluşturalım ve ölçeği Path
. Ayrıca, bunu daha etkileşimli bir animasyon yapmak için bunları yalnızca denizanasına tıkladığınızda uygulayacağız.
Animasyon durumunu tutacak iki Animasyon Tablosu ve denizanasına tıklandığında çağıracağımız bir askıya alma işlevi yaratıyoruz - bu özellikleri gözleri ölçeklendirmek ve soldurmak için canlandırıyoruz.
Artık tıklandığında sevimli bir yanıp sönen animasyonumuz var ve denizanamız neredeyse tamamlandı!
Bozulma/gürültü efekti uygulama
Böylece canlandırmak istediğimiz şeylerin çoğuna sahibiz - yukarı ve aşağı hareket ve yanıp sönme. Denizanasının vücuduna o titrek etkinin nasıl uygulandığına bakalım, vücut ve dokunaçlar onlara uygulanan ses ile hareket ediyor ve ona hareket hissi veriyor.
SVG'ye ve animasyon koduna baktığımızda, feTurbulence
daha sonra SVG'ye bir feDisplacementMap
.
<filter id="turbulence" filterUnits="objectBoundingBox" x="0" y="0" width="100%" height="100%">
<feTurbulence data-filterId="3" baseFrequency="0.02 0.03" result="turbulence" id="feturbulence" type="fractalNoise" numOctaves="1" seed="1"></feTurbulence>
<feDisplacementMap id="displacement" xChannelSelector="R" yChannelSelector="G" in="SourceGraphic" in2="turbulence" scale="13" />
</filter>
</defs>
<g class="jellyfish" filter="url(#turbulence)">
Bunu başarmak için AGSL gölgelendiricilerini kullanabiliriz, bunun yalnızca Tiramisu ve sonraki sürümlerde (API 33+) desteklendiğini belirtmekte fayda var. Öncelikle yalpalama işlevi görecek bir gölgelendirici oluşturmamız gerekiyor, ilk başta gürültü kullanmayacağız — basitlik yerine sadece bir eşleme işlevi.
Gölgelendiricilerin çalışma şekli, tek tek pikseller üzerinde hareket etmeleridir - bir koordinat ( fragCoord
) alırız ve bu koordinatta işlenecek bir renk sonucu üretmemiz beklenir. Şekillendirilebiliri dönüştürmek için kullanacağımız ilk gölgelendirici aşağıdadır:
Bizim durumumuzda, kullanacağımız girdi, ekranda şu anda işlenmiş piksellerimizdir. uniform shader contents;
Girdi olarak göndereceğimiz değişken aracılığıyla buna erişiyoruz . Girdi koordinatını ( fragCoord
) alıyoruz ve bu koordinata bazı dönüşümler uyguluyoruz - zamanla hareket ettiriyoruz ve genellikle onu hareket ettirmek için üzerinde biraz matematik yapıyoruz.
Bu, yeni bir koordinat üretir, bu nedenle, fragCoord
konumdaki tam rengi döndürmek yerine, giriş pikselini nereden aldığımızı değiştiririz. Örneğin, elimizde olsaydı return contents.eval(fragCoord)
, hiçbir değişiklik üretmezdi - bu bir geçişkenlik olurdu. Artık piksel rengini, birleştirilebilir öğenin farklı bir noktasından alıyoruz - bu, birleştirilebilir öğenin içeriğinde titrek bir bozulma etkisi yaratacaktır.
Bunu şekillendirilebilir dosyamızda kullanmak için, bu gölgelendiriciyi RenderEffect
şekillendirilebilir içeriğin içeriğine a olarak uygulayabiliriz:
as girdisini kullanarak createRuntimeShaderEffect
, kullanırız. WOBBLE_SHADER
Bu, birleştirilebilir içeriğin mevcut içeriğini alır ve " " parametre adıyla gölgelendiriciye girdi olarak sağlar contents
. Daha sonra içindekileri sorgularız WOBBLE_SHADER
. Değişken time
, zamanla yalpalamayı değiştirir (animasyonu oluşturur).
Bunu çalıştırdığımızda, artık bütünün Image
bozulduğunu ve biraz daha sallantılı göründüğünü görebiliriz - tıpkı bir denizanası gibi.
Efektin yüze ve baloncuklara uygulanmasını istemiyorsak, bunları ayrı olarak çıkarabilir ImageVectors
ve oluşturma efektini bu vektörlere uygulamayı atlayabiliriz:
Gürültü Efekti Uygulamak
Yukarıda belirttiğimiz gölgelendirici, şekillendirilebilirin içeriğine bir yer değiştirme uygulamak için bir gürültü işlevi kullanmıyor. Gürültü, daha yapılandırılmış bir rasgele işlevle yer değiştirmeyi uygulamanın bir yoludur. Bu tür gürültülerden biri Perlin gürültüsüdür (gizli olarak kullanılan da budur feTurbulence
), Perlin gürültü işlevini çalıştırmanın sonucunu işlersek şöyle görünür:
Alandaki her koordinat için gürültü değerini kullanırız ve " contents
" gölgelendiricide yeni bir koordinat sorgulamak için kullanırız.
Bir Perlin gürültü işlevi kullanmak için gölgelendiricimizi güncelleyelim ( bu Github deposundan uyarlanmıştır ). Daha sonra bunu, giriş koordinatından çıkış koordinatına (yani bir yer değiştirme haritası) koordinat eşlemesini belirlemek için kullanacağız.
Bu gürültü işlevini uygulayarak çok daha iyi bir sonuç elde ederiz! Denizanası suyun içinde hareket ediyormuş gibi görünür.
Ama bunu neden kullanayım?
Bu noktada merak ediyor olabilirsiniz, bu harika - ama kullanım durumunda çok niş, Rebecca. Elbette - belki de işte her gün animasyonlu bir denizanası yapmıyorsunuzdur (hayal edebiliyoruz değil mi?). Ancak RenderEffects
herhangi bir şekillendirilebilir ağaca uygulanabilir - istediğiniz hemen hemen her şeye efekt uygulamanıza izin verir.
Örneğin, gradyan metninizin veya tüm şekillendirilebilir ekranınızın bir gürültü efektine veya kalbinizin arzu ettiği herhangi bir AGSL efektine sahip olmasını neden istemeyesiniz?
toparla
Bu nedenle, bu blog gönderisinde pek çok ilginç kavramı ele aldık - ImageVectors
SVG'lerden özel oluşturma, bir parçasının animasyon oluşturma ImageVector
ve AGSL gölgelendiricilerini Compose'daki RenderEffects
kullanıcı arabirimimize göre uygulama.
Denizanası'nın tam kodu için — esasın tamamına buradan göz atın . AGSL RenderEffects hakkında daha fazla bilgi için belgelere veya başka bir kullanım örneği için JetLagged Sample'a bakın.
Herhangi bir sorunuz varsa — Mastodon androiddev.social/@riggaroo veya Twitter üzerinden bize ulaşmaktan çekinmeyin .
Bu gönderiyle ilgili değerli geri bildirimleri için Jolanda Verhoef , Nick Butcher , Florina Muntenescu , Romain Guy ve Nader Jawad'a teşekkürler.