Faire bouger les méduses dans Compose : animer des vecteurs d'image et appliquer les effets de rendu AGSL
J'adore suivre des personnes inspirantes sur Internet et voir ce qu'elles font - l'une de ces personnes est Cassie Codes , elle crée des animations incroyables pour le Web. L'un de ses exemples inspirants est cette jolie méduse animée .
Après avoir vu cela et être obsédé par cela pendant un moment, je n'arrêtais pas de penser que cette jolie petite créature devait également prendre vie dans Compose. Donc, ce billet de blog décrit comment j'ai procédé pour créer cela dans Jetpack Compose, le code final peut être trouvé ici . Les techniques ici ne sont pas seulement pertinentes pour les méduses bien sûr… n'importe quel autre poisson fera l'affaire aussi ! Je plaisante - ce billet de blog couvrira :
- Vecteurs d'images personnalisés
- Animation de chemins ou de groupes ImageVector
- Application d'un effet de bruit de distorsion sur un Composable avec AGSL RenderEffect.
Analyser le SVG
Pour implémenter cette méduse, nous devons d'abord voir de quoi est composé le SVG et essayer d'en reproduire les différentes parties. La meilleure façon de comprendre ce qu'un SVG dessine est de commenter différentes parties de celui-ci et de voir le résultat visuel de ce que chaque section du svg rend. Pour ce faire, vous pouvez soit le modifier dans le codepen lié ci-dessus, soit télécharger et ouvrir un SVG dans un éditeur de texte (c'est un format lisible par texte).
Jetons donc un coup d'œil à ce SVG :
<!--
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>
- Chemins et groupes de chemins qui composent le SVG :
- Tentacules
- Visage - blob et gelée externe
- Yeux - l'animé ouvert et fermé
- Bulles - animées au hasard autour de la méduse - la taille et l'alpha s'animent
- M, m : Déplacer vers
- L, l, H, h, V, v : Ligne à
- C, c, S, s : courbe de Bézier cubique à
- Q, q, T, t : courbe de Bézier quadratique à
- A, a : courbe en arc elliptique à
- Z, z — Fermer le chemin
- La méduse doit monter et descendre lentement
- Les yeux doivent clignoter au clic de la méduse
- Le corps de la méduse doit avoir un effet oscillant/bruit qui lui est appliqué.
Maintenant que nous comprenons de quoi est composé ce SVG, passons au rendu de la version statique dans Compose.
Création d'un ImageVector personnalisé
Compose a un concept de ImageVector , où vous pouvez créer un vecteur par programmation - similaire à SVG. Pour les vecteurs/SVG que vous souhaitez simplement restituer sans les modifier, vous pouvez également charger un VectorDrawable à l'aide de painterResource(R.drawable.vector_image). Cela se chargera de le convertir en un ImageVector que Compose rendra.
Maintenant, vous vous demandez peut-être - pourquoi ne pas simplement importer la méduse en tant que SVG dans un fichier xml et la charger à l'aide de painterResource(R.drawable.jelly_fish)
?
C'est une excellente question - et il est possible de charger la méduse de cette manière, en supprimant l'aspect de turbulence du SVG et l'image sera rendue avec un XML chargé (comme expliqué dans la documentation ici ). Mais nous voulons faire un peu plus avec les parties individuelles du chemin, comme animer des parties au clic et appliquer un effet de bruit au corps, nous allons donc développer notre ImageVector
programmation.
Afin de restituer cette méduse dans Compose, nous pouvons copier les données de chemin (ou la d
balise " " sur le chemin) qui composent le poisson, par exemple, le premier tentacule a les données de chemin suivantes :
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
Maintenant, vous pensez probablement - dois-je dessiner dans ma tête et connaître toutes les positions et commandes à la main ? Non pas du tout. Vous pouvez créer un vecteur dans la plupart des programmes de conception, tels que Figma ou Inkscape, et exporter le résultat de votre dessin vers un fichier SVG pour obtenir ces informations par vous-même. Ouf!
Pour créer le vecteur dans Compose : on appelle rememberVectorPainter
, ce qui crée un ImageVector
, et on crée un Group
appelé jellyfish
, puis un autre Group
appelé tentacles
et on place le premier à l' Path
intérieur pour le premier tentacule. Nous avons également défini un RadialGradient
comme arrière-plan pour l'ensemble de la méduse.
Et le résultat de ce qui suit est un petit tentacule dessiné à l'écran avec un fond dégradé radial !
Nous répétons ce processus pour tous les éléments du SVG - en prenant les bits du chemin du fichier SVG et en appliquant la couleur et l'alpha au chemin qui sera dessiné, nous regroupons également logiquement les chemins dans les tentacules, le visage, le bulles etc :
Nous avons maintenant tout notre rendu de méduses avec ce qui précède ImageVector
:
Animation de chemins et de groupes ImageVector
Nous voulons animer des parties de ce vecteur :
Voyons donc comment nous pouvons animer des parties individuelles du fichier ImageVector
.
Déplacer les méduses de haut en bas
En regardant le codepen, on peut voir que la méduse se déplace avec une translation de haut en bas (translation en y). Pour ce faire dans composer, nous créons une transition infinie et un translationY
qui sera animé sur 3000 millis, nous définissons ensuite le groupe contenant la méduse, et le visage pour avoir un translationY
, cela produira l'animation de haut en bas.
Génial - une partie du ImageVector
s'anime maintenant de haut en bas, vous remarquerez que les bulles restent dans la même position.
Yeux clignotants ️
En regardant le codepen, on peut voir qu'il y a une animation scaleY
et opacity
sur chacun des yeux. Créons ces deux variables et appliquons l'échelle au Group
et l'alpha sur le Path
. Nous les appliquerons également uniquement au clic de la méduse, pour en faire une animation plus interactive.
Nous créons deux Animatables qui contiendront l'état de l'animation et une fonction de suspension que nous appellerons en cliquant sur la méduse - nous animons ces propriétés pour redimensionner et estomper les yeux.
Nous avons maintenant une jolie animation clignotante au clic - et notre méduse est presque terminée !
Application d'un effet de distorsion/bruit
Nous avons donc la plupart des choses que nous voulons animer - le mouvement de haut en bas et le clignotement. Regardons comment le corps de la méduse a cet effet oscillant qui lui est appliqué, le corps et les tentacules se déplacent avec un bruit qui leur est appliqué pour lui donner une impression de mouvement.
En regardant le SVG et le code d'animation, nous pouvons voir qu'il utilise feTurbulence
pour générer du bruit qui est ensuite appliqué au SVG en tant que fichier 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)">
Nous pouvons utiliser les shaders AGSL pour y parvenir, il convient de noter que cela n'est pris en charge que sur Tiramisu et plus (API 33+). Nous devons d'abord créer un shader qui agira comme une oscillation, nous n'utiliserons pas de bruit au début - juste une fonction de mappage à la place pour plus de simplicité.
La façon dont les shaders fonctionnent est qu'ils agissent sur des pixels individuels - nous obtenons une coordonnée ( fragCoord
) et nous sommes censés produire un résultat de couleur qui sera rendu à cette coordonnée. Vous trouverez ci-dessous le shader initial que nous utiliserons pour transformer le composable :
Dans notre cas, l'entrée que nous utiliserons est nos pixels actuellement rendus à l'écran. Nous y avons accès via la uniform shader contents;
variable que nous enverrons en entrée. Nous prenons la coordonnée d'entrée ( fragCoord
), et nous appliquons quelques transformations sur cette coordonnée - en la déplaçant avec le temps et en effectuant généralement des calculs dessus pour la déplacer.
Cela produit une nouvelle coordonnée, donc au lieu de renvoyer la couleur exacte à la fragCoord
position, nous décalons d'où nous obtenons le pixel d'entrée. Par exemple, si nous avions return contents.eval(fragCoord)
, cela ne produirait aucun changement — ce serait un pass-through. Nous obtenons maintenant la couleur des pixels à partir d'un point différent du composable - ce qui créera un effet de distorsion bancale sur le contenu du composable.
Pour l'utiliser sur notre composable, nous pouvons appliquer ce shader RenderEffect
au contenu du composable :
Nous utilisons createRuntimeShaderEffect
, en passant en WOBBLE_SHADER
entrée. Cela prend le contenu actuel du composable et le fournit comme entrée dans le shader, avec le nom de paramètre " contents
". Nous interrogeons ensuite le contenu à l'intérieur du fichier WOBBLE_SHADER
. La time
variable modifie l'oscillation au fil du temps (création de l'animation).
En exécutant cela, nous pouvons voir que l'ensemble Image
est maintenant déformé et semble un peu plus bancal - tout comme une méduse.
Si nous voulions que l'effet ne s'applique pas au visage et aux bulles, nous pouvons les extraire dans des fichiers séparés ImageVectors
et ignorer l'application de l'effet de rendu à ces vecteurs :
Application d'un effet de bruit
Le shader que nous avons spécifié ci-dessus n'utilise pas de fonction de bruit pour appliquer un déplacement au contenu du composable. Le bruit est un moyen d'appliquer un déplacement, avec une fonction aléatoire plus structurée. Un de ces types de bruit est le bruit Perlin (qui est feTurbulence
utilisé sous le capot), voici à quoi cela ressemblerait si nous rendions le résultat de l'exécution de la fonction de bruit Perlin :
Nous utilisons la valeur de bruit pour chaque coordonnée dans l'espace et l'utilisons pour interroger une nouvelle coordonnée dans le contents
shader « ».
Mettons à jour notre shader pour utiliser une fonction de bruit Perlin (adapté de ce dépôt Github ). Nous l'utiliserons ensuite pour déterminer le mappage des coordonnées de la coordonnée d'entrée à la coordonnée de sortie (c'est-à-dire une carte de déplacement).
En appliquant cette fonction de bruit, nous obtenons un bien meilleur résultat ! La méduse semble se déplacer dans l'eau.
Mais pourquoi l'utiliserais-je ?
À ce stade, vous vous demandez peut-être, c'est cool - mais très niche dans son cas d'utilisation, Rebecca. Bien sûr, peut-être que vous ne fabriquez pas une méduse animée tous les jours au travail (nous pouvons rêver, n'est-ce pas ?). Mais RenderEffects
peut être appliqué à n'importe quel arbre composable - vous permettant d'appliquer des effets à à peu près tout ce que vous voulez.
Par exemple, pourquoi ne voudriez-vous pas que votre texte dégradé ou votre écran composable entier ait un effet de bruit ou tout autre effet AGSL que votre cœur désire ?
Concluez
Nous avons donc couvert de nombreux concepts intéressants dans cet article de blog - créer des fichiers personnalisés ImageVectors
à partir de SVG, animer des parties d'un ImageVector
et appliquer des shaders AGSL RenderEffects
à notre interface utilisateur dans Compose.
Pour le code complet de la méduse - consultez l' essentiel ici . Pour plus d'informations sur AGSL RenderEffects - consultez la documentation ou l' exemple JetLagged pour un autre exemple d'utilisation.
Si vous avez des questions, n'hésitez pas à nous contacter sur Mastodon androiddev.social/@riggaroo ou Twitter .
Merci à Jolanda Verhoef , Nick Butcher , Florina Muntenescu , Romain Guy , Nader Jawad pour les précieux commentaires sur ce post.