SVG

4d2070e800356da57315d427aa2343f4?s=47 François Blavoet
January 23, 2017
66

 SVG

Slides d'un talk interne deezer sur les SVG

4d2070e800356da57315d427aa2343f4?s=128

François Blavoet

January 23, 2017
Tweet

Transcript

  1. De <SVG> à <VectorDrawable>

  2. Le vectoriel, c'est très utile, mais ça a quelques pièges

    qu'il faut connaître Déjà, c'est quoi un SVG? -> Scalable Vector Graphics
  3. SVG ? Dans la pratique, cette note de musique, c'est

    un fichier xml : <svg fill="#2233FF" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"> <path d="M0 0h24v24H0z"/> <path d="M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z"/> </svg>
  4. <svg fill="#2233FF" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"> <path

    d="M0 0h24v24H0z"/> <path d="M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z"/> </svg> → viewBox : taille de ma zone de dessin. Ici je dessine dans un carré de 24 de côté → path : liste d'instructions, qui tiennent dans la viewBox, si je dessine en 24 24 je suis en bas à droite de mon image. et 50 50 n'aurait aucun sens ici puisqu'en dehors de la viewbox
  5. <svg fill="#2233FF" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"> <path

    d="M0 0h24v24H0z"/> <path d="M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z"/> </svg> → fill : couleur de remplissage → height / width : la taille de l'image que je veux générer, en pixels. peut être totalement différent de la viewBox
  6. Path Liste d'instructions : M : move L : line

    V : vertical line A : arc (curve) C : cubic bezier curve
  7. Path La spec svg définit beaucoup d'instructions. À chaque fois

    c'est une lettre suivie d'une série de coordonnées. c'est ça que contient un path comme celui-ci : <path d="M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z"/>
  8. Path <path d="M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4

    4-1.79 4-4V7h4V3h-6z"/> certaines instructions sont écrites en majuscules, d'autres en minuscules : lettre majuscule : coordonnées absolues lettre minuscule : coordonnées relatives : on part du point d'arrivée de l'insctruction précédente.
  9. Il y a d'autres choses dans la spec, par exemple

    le linecap : <svg width="160" height="140" xmlns="http://www.w3.org/2000/svg" version="1.1"> <line x1="40" x2="120" y1="20" y2="20" stroke="black" stroke-width="20" stroke-linecap="butt"/> <line x1="40" x2="120" y1="60" y2="60" stroke="black" stroke-width="20" stroke-linecap="square"/> <line x1="40" x2="120" y1="100" y2="100" stroke="black" stroke-width="20" stroke-linecap="round"/> </svg>
  10. Très souvent lors de l'export Sketch rajoute un stroke-width, stroke-color,

    etc dans le svg pour chaque path. Une fois importé dans Android Studio vous pouvez généralement le virer, c'est un bug de leur exporteur.
  11. Et ainsi de suite, la spec SVG, c'est 800 pages

    ! Nous on travaille sur Android. On n'a pas besoin de tout ça.
  12. Si je prends un svg : <svg fill="#2233FF" height="24" viewBox="0

    0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"> <path d="M0 0h24v24H0z"/> <path d="M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z"/> </svg> et l'importe dans Android Studio (drawable/new vector drawable)
  13. j'obtiens ceci : <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportHeight="24.0" android:viewportWidth="24.0"> <path

    android:fillColor="#2233FF" android:pathData="M12,3v10.55c-0.59,-0.34 -1.27,-0.55 -2,-0.55 -2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4V7h4V3h-6z" /> </vector>
  14. j'ai les mêmes composants qu'un svg standard, sauf qu'ici c'est

    un drawable android. Quand j'inflate cette resource android, j'obtiens un VectorDrawable.
  15. c'est un drawable android standard, je peux utiliser des @

    : <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="@dimension/icon_width" android:height="24dp" android:viewportHeight="24.0" android:viewportWidth="24.0"> <path android:fillColor="@color/icon" android:pathData="@string/path_icon" /> </vector>
  16. Problème : le VectorDrawable ne supporte pas toute la spec

    SVG
  17. problème le plus commun : -> even-odd Rêgle qui permet

    de coder des zones pleines et vides suivant qu'elles coupent un nombre de tracés pair ou impair
  18. Sketch ne donne pas les mains aux designers là dessus.

    C'est un outil qui permet de faire du vectoriel, mais qui n'est pas pensé pour le format svg. Résultat: L'export de svg dans sketch peut vous générer des svgs non gérés par android, et les designers n'ont pas souvent la main dessus
  19. exemple de svg avec la rêgle even-odd <svg width="24" height="32"

    viewBox="0 0 24 32" xmlns="http://www.w3.org/2000/svg"> <title> F5F3925F-A5DA-443C-94DA-B08D695ADAAF </title> <g fill="none" fill-rule="evenodd"> <path d="M0 .993C0 .443.453 0 .997 0h6.006C7.553 0 8 .452 8 .993v30.014 c0 .55-.453.993-.997.993H.997C.447 32 0 31.548 0 31.007V.993zm16 0c0-.55.453-.993.997-.993h6.006 c.55 0 .997.452.997.993v30.014c0 .55-.453.993-.997.993h-6.006c-.55 0-.997-.452-.997-.993V.993z" fill="#000"/> <path d="M-4 0h32v32H-4z"/> </g> </svg>
  20. solution : cette webapp permet de nettoyer la plupart des

    svgs pour retirer ces rêgles de fill problématiques. http://a-student.github.io/SvgToVectorDrawableConverter.Web/ Il faut juste faire attention de bien cocher Specify --fix-fill-type
  21. et pour Android < 21 ? Déjà, il faut rajouter

    une ligne dans la config gradle defaultConfig { applicationId 'deezer.android.app' minSdkVersion minSdk targetSdkVersion targetSdk multiDexEnabled true vectorDrawables.useSupportLibrary = true }
  22. vectorDrawables.useSupportLibrary = true dit à la support lib de ne

    pas générer de pngs pour nos vecteurs, on va se débrouiller pour afficher des VectorDrawables, même <21
  23. Deuxième opération, dans BaseActivity : // setup the support lib

    so we can use vector Resources static { AppCompatDelegate.setCompatVectorFromResourcesEnabled(true); } demande à la support lib de hacker l'inflation de drawables quand c'est possible, pour qu'elle prenne en charge les selectors contenant des svgs (on verra ensuite pourquoi c'est très utile)
  24. Utilisation : <android.support.v7.widget.AppCompatImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" app:srcCompat="@drawable/ic_camera" /> Pour une

    imageView, j'ai juste à utiliser srcCompat, qui va appeler une méthode de AppCompatImageView qui gère les vectorDrawable, même en API 16
  25. Problème : <TextView android:id="@+id/mr_control_subtitle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/camera" drawable:left="@drawable/ic_camera" /> drawable:left

    -> appelé dans le constructeur de TextView, pas possible de créer une classe fille qui corrige le comportement du constructeur
  26. C'est l'heure du hack crado ! <?xml version="1.0" encoding="utf-8"?> <selector

    xmlns:android="http://schemas.android.com/apk/res/android"> <item android:drawable="@drawable/ic_family_48" /> </selector>
  27. c'est à ça que sert setCompatVectorFromResourcesEnabled Le hack de la

    support lib permet d'inflater des vectorDrawableCompat mais pas directement, uniquement s'ils sont wrappés dans un selector d'où ce selector qui ne contient qu'un seul élément. A utiliser à chaque fois qu'on a unCompoundDrawable` (TextView, CheckBox, ..)
  28. Deuxième problème : context.getDrawable(id); ContextCompat.getDrawable(context, vectorId); Context et même ContextCompat

    ne gèrent pas les vectorDrawables Pour les vecteurs, utilisez plutôt : AppCompatDrawableManager.getDrawable(context, vectorId);
  29. Presque sauvés ! sauf ... Glide.with(ctx) .load(url) .placeholder(R.drawable.placeholder) .into(view); dans

    Glide/GenericRequest : private Drawable getPlaceholderDrawable() { if (placeholderDrawable == null && placeholderResourceId > 0) { placeholderDrawable = context.getResources().getDrawable(placeholderResourceId); } return placeholderDrawable; }
  30. Les placeholders/error/fallbacks de Glide n'utilisent pas la support lib !

    On peut éventuellement passer directement un drawable créé via AppCompatDrawableManager à Glide
  31. RemoteView Les remoteViews ne font pas non plus appel à

    la support lib
  32. Comment faire ? Pour ces cas ci, il faut souvent

    passer par des drawables raster classiques pour les vieilles versions dans chaque bucket. Et le bucket anydpi-v21 permet de les overrider tous à partir de lollipop
  33. Et les animations ?

  34. <animated-vector xmlns:android="http://schemas.android.com/apk/res/android" android:drawable="@drawable/vectordrawable" > <target android:name="rotationGroup" android:animation="@anim/rotation" /> <target android:name="v"

    android:animation="@anim/path_morph" /> </animated-vector>
  35. <objectAnimator android:duration="6000" android:propertyName="rotation" android:valueFrom="0" android:valueTo="360" />

  36. <set xmlns:android="http://schemas.android.com/apk/res/android"> <objectAnimator android:duration="3000" android:propertyName="pathData" android:valueFrom="M300,70 l 0,-70 70,70 0,0

    -70,70z" android:valueTo="M300,70 l 0,-70 70,0 0,140 -70,0 z" android:valueType="pathType"/> </set>
  37. valueFrom et valueTo doivent être compatibles : contenir les mêmes

    instructions dans le même ordre avec juste des coordonnées différentes. Pour les animations, la principale limitation c'est qu'il manque encore des outils graphiques pour modifier les paths de manière efficace.
  38. Comme solution un peu plus efficace que d'écrire tous les

    paths à la main, il y a entre autres https://romannurik.github.io/AndroidIconAnimator/ mais toujours en cours de dev