アプリ開発備忘録

PlayStationMobile、Android、UWPの開発備忘録

【Android】VectorDrawable AnimatedVectorDrawableを理解する

VectorDrawable、AnimatedVectorDrawableってなんなの。SVGから変換できるけどそれとは何が違うのって思いました。

SVGとVectorDrawable

なので何が違うか確認しましょう。
適当に大きさの違う円形と楕円形のSVGを作ったものとそれをAnimatedVectorDrawableに変換したものです。

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 48 48" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;">
    <circle cx="8.922" cy="10.305" r="5.041" style="fill:rgb(255,48,22);"/>
    <g transform="matrix(1.84649,0,0,1.84649,-17.4162,-40.4344)">
        <circle cx="29.086" cy="27.926" r="5.086" style="fill:rgb(255,48,22);"/>
    </g>
    <g transform="matrix(3.97807,0,0,1.84649,-91.5926,-18.1741)">
        <circle cx="29.086" cy="27.926" r="5.086" style="fill:rgb(255,48,22);"/>
    </g>
</svg>

circleが3つあるのでそれぞれ独立して定義されているのがわかります。

<animated-vector
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:aapt="http://schemas.android.com/aapt">
    <aapt:attr name="android:drawable">
        <vector
            android:name="vector"
            android:width="48dp"
            android:height="48dp"
            android:viewportWidth="48"
            android:viewportHeight="48">
            <path
                android:name="path_1"
                android:pathData="M 8.922 5.264 C 7.858 5.264 6.82 5.601 5.959 6.227 C 5.098 6.852 4.457 7.735 4.128 8.747 C 3.799 9.76 3.799 10.85 4.128 11.863 C 4.457 12.875 5.098 13.758 5.959 14.383 C 6.82 15.009 7.858 15.346 8.922 15.346 C 10.258 15.346 11.542 14.815 12.487 13.87 C 13.432 12.925 13.963 11.641 13.963 10.305 C 13.963 8.969 13.432 7.685 12.487 6.74 C 11.542 5.795 10.258 5.264 8.922 5.264 Z M 36.291 1.739 C 33.801 1.739 31.411 2.73 29.65 4.49 C 27.89 6.251 26.9 8.641 26.9 11.131 C 26.9 13.62 27.89 16.011 29.65 17.771 C 31.411 19.532 33.801 20.522 36.291 20.522 C 38.781 20.522 41.171 19.532 42.931 17.771 C 44.692 16.011 45.682 13.62 45.682 11.131 C 45.682 8.641 44.692 6.251 42.931 4.49 C 41.171 2.73 38.781 1.739 36.291 1.739 Z M 24.114 24 C 18.75 24 13.6 24.99 9.807 26.75 C 6.014 28.511 3.881 30.901 3.881 33.391 C 3.881 35.881 6.014 38.271 9.807 40.032 C 13.6 41.792 18.75 42.782 24.114 42.782 C 28.386 42.782 32.55 42.154 36.006 40.989 C 39.462 39.823 42.036 38.179 43.356 36.293 C 44.676 34.407 44.676 32.375 43.356 30.489 C 42.036 28.603 39.462 26.959 36.006 25.793 C 32.55 24.628 28.386 24 24.114 24 Z"
                android:fillColor="#ff3016"
                android:strokeWidth="1"/>
        </vector>
    </aapt:attr>
</animated-vector>

こちらはデータ量を見るにpathDataが本体みたいですが一個になってしまっています。
正方形でも長方形でもMから始まってZで終わります。唯一違うのは間のアルファベットです。

VectorDrawableとは

じゃあこのpathDataはどのようなフォーマットなのでしょうか。
ドキュメントを見る前に少し推測してみます。端いっぱいの円形です。

<animated-vector
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:aapt="http://schemas.android.com/aapt">
    <aapt:attr name="android:drawable">
        <vector
            android:name="vector"
            android:width="10dp"
            android:height="10dp"
            android:viewportWidth="10"
            android:viewportHeight="10">
            <path
                android:name="path"
                android:pathData="M 5 0 C 3.944 -0.001 2.914 0.333 2.06 0.954 C 1.205 1.574 0.568 2.45 0.242 3.455 C -0.085 4.459 -0.085 5.542 0.242 6.546 C 0.568 7.551 1.205 8.426 2.06 9.047 C 2.914 9.668 3.944 10.002 5 10.001 C 6.057 10.002 7.087 9.668 7.941 9.047 C 8.796 8.426 9.433 7.551 9.759 6.546 C 10.086 5.542 10.086 4.459 9.759 3.455 C 9.433 2.45 8.796 1.574 7.941 0.954 C 7.087 0.333 6.057 -0.001 5 0 Z"
                android:fillColor="#ff3016"
                android:strokeWidth="1"/>
        </vector>
    </aapt:attr>
</animated-vector>
  • M 5 0
    widthとheightが10で円形で「5,0」始点ですね。 アルファベット+数値のセットみたいですね
  • C 3.944 -0.001 2.914 0.333 2.06 0.954
    よくわかりません。
    要素を表すアルファベット1文字+そのアルファベットに対応した値の個数みたいですね。

次に正方形のデータです。

<animated-vector
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:aapt="http://schemas.android.com/aapt">
    <aapt:attr name="android:drawable">
        <vector
            android:name="vector"
            android:width="10dp"
            android:height="10dp"
            android:viewportWidth="10"
            android:viewportHeight="10">
            <path
                android:name="path_1"
                android:pathData="M -0.002 0 L 9.999 0 L 9.999 10 L -0.002 10 Z"
                android:fillColor="#ff3016"
                android:strokeWidth="1"/>
        </vector>
    </aapt:attr>
</animated-vector>
  • M -0.002 0 L 9.999 0 L 9.999 10 L -0.002 10 Z
    若干ぶれてるみたいなので見やすいように手動で補正します。↓
  • M 0 0 L 10 0 L 10 10 L 0 10 Z
    点と点を繋いでいくように描画するみたいですね。

流石にもう分からないのでドキュメントを読みます。

android:pathData Defines path data using exactly same format as "d" attribute in the SVG's path data. This is defined in the viewport space.
https://developer.android.com/reference/android/support/graphics/drawable/VectorDrawableCompat

svgの "d" attribute と一緒と書いてあります。

D Property

The \ value specifies a shape using a path data string. The contents of the \ value must match the svg-path EBNF grammar defined below, and errors within the string are handled according to the rules in the Path Data Error Handling section. If the path data string contains no valid commands, then the behavior is the same as the none value.
https://svgwg.org/svg2-draft/paths.html#DProperty

EBNFの文法がリンクされています。
https://svgwg.org/svg2-draft/paths.html#PathDataBNF

エラーハンドリングの方法も書かれています。
https://svgwg.org/svg2-draft/paths.html#PathDataErrorHandling

アニメーション

アニメーションの事も記述されています。

The \ value specifies a shape using a path data string. The contents of the \ value must match the svg-path EBNF grammar defined below, and errors within the string are handled according to the rules in the Path Data Error Handling section. If the path data string contains no valid commands, then the behavior is the same as the none value.

2つのパスの構造が一緒じゃないとスムーズにアニメーションできない。一緒じゃないなら離散アニメーションで補完しろ。的なことが書いてあります。

離散アニメーション
https://drafts.csswg.org/web-animations/#discrete-animation-type-section

コマンド一覧

大文字は絶対座標で小文字は相対座標です。

  • 絶対移動(M m)
  • 終了(Z z)
    最初の点に接続して終了します
  • 線(L l H h V v)
  • 三次元ベジエ(C c S s)
  • 二次元ベジエ(Q q T t)
  • 楕円形コマンド(A a)

おわる

これ以上はSVGの説明になるので省きますが大体の雰囲気は掴めたと思います。
なんでCircleがCの三次元ベジエで表されていたのかはこれはおそらくアニメーションがしやすいようにでしょうね。円から直線に変わる時にスムーズにアニメーションするには構造が同じでないといけないので。