alexjlockwood / ShapeShifter

SVG icon animation tool for Android, iOS, and the web
https://shapeshifter.design
Apache License 2.0
3.96k stars 200 forks source link

Export to Bodymovin format #19

Open EmmanuelVinas opened 7 years ago

EmmanuelVinas commented 7 years ago

Bodymovin format is used by https://github.com/airbnb/lottie-android

Also see : https://github.com/romannurik/AndroidIconAnimator/issues/99

alexjlockwood commented 7 years ago

Thanks for filing! Will definitely add this as an option! :)

alexjlockwood commented 7 years ago

For future reference, here is a Twitter thread that has some good information: https://twitter.com/airnanan/status/862137455096594432

marcmo commented 6 years ago

just found out about shapeshifter...so awesome !!! Currently I use AfterEffects + Lottie but AfterEffects are overkill for my purposes. export to bodymovin would truly be a win since you immediately could use shapeshifter with lottie. any plans to add this in the future?

alexjlockwood commented 6 years ago

Before AVD:

<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="heartbreak"
            android:width="56dp"
            android:height="56dp"
            android:viewportWidth="56"
            android:viewportHeight="56">
            <group
                android:name="broken_heart_left_group"
                android:pivotX="28"
                android:pivotY="37.3">
                <path
                    android:name="broken_heart_left"
                    android:pathData="M 28.031 21.054 C 28.02 21.066 28.01 21.078 28 21.09 C 26.91 19.81 25.24 19 23.5 19 C 20.42 19 18 21.42 18 24.5 C 18 28.28 21.4 31.36 26.55 36.03 L 28 37.35 L 28.002 37.348 L 27.781 36.988 L 28.489 36.073 L 27.506 34.764 L 28.782 33.027 L 26.944 31.008 L 29.149 28.725 L 27.117 27.143 L 29.149 25.018 L 26.488 22.977 L 28.031 21.054 L 28.031 21.054 Z"
                    android:fillColor="#000"/>
            </group>
            <group
                android:name="broken_heart_right_group"
                android:pivotX="28"
                android:pivotY="37.3">
                <path
                    android:name="broken_heart_right"
                    android:pathData="M 28.031 21.054 C 28.169 20.895 28.316 20.743 28.471 20.599 L 28.915 20.226 C 29.926 19.457 31.193 19 32.5 19 C 35.58 19 38 21.42 38 24.5 C 38 28.28 34.6 31.36 29.45 36.04 L 28.002 37.348 L 27.781 36.988 L 28.489 36.073 L 27.506 34.764 L 28.782 33.027 L 26.944 31.008 L 29.149 28.725 L 27.117 27.143 L 29.149 25.018 L 26.488 22.977 L 28.031 21.054 L 28.031 21.054 Z"
                    android:fillColor="#000"/>
            </group>
            <path
                android:name="heart_stroke_left"
                android:pathData="M 28.719 38.296 L 25.669 35.552 C 21.621 31.793 18.016 28.891 18.016 24.845 C 18.016 21.588 20.631 19.965 23.634 19.965 C 24.999 19.965 26.799 21.181 28.644 23.13"
                android:strokeColor="#000"
                android:strokeWidth="2"
                android:trimPathEnd="0"/>
            <path
                android:name="heart_stroke_right"
                android:pathData="M 27.231 38.294 L 30.765 35.2 C 34.834 31.235 37.752 29.118 38.004 25.084 C 38.168 22.459 35.773 20.035 33.379 20.035 C 30.432 20.035 29.672 21.047 27.231 23.133"
                android:strokeColor="#000"
                android:strokeWidth="2"
                android:trimPathEnd="0"/>
            <group android:name="filled">
                <clip-path
                    android:name="clip"
                    android:pathData="M 18 37 L 38 37 L 38 37 L 18 37 Z"/>
                <path
                    android:name="fill"
                    android:pathData="M 28 39 L 26.405 37.567 C 20.74 32.471 17 29.109 17 24.995 C 17 21.632 19.657 19 23.05 19 C 24.964 19 26.801 19.883 28 21.272 C 29.199 19.883 31.036 19 32.95 19 C 36.343 19 39 21.632 39 24.995 C 39 29.109 35.26 32.471 29.595 37.567 L 28 39 L 28 39 Z"
                    android:fillColor="#000000"/>
            </group>
        </vector>
    </aapt:attr>
    <target android:name="broken_heart_left_group">
        <aapt:attr name="android:animation">
            <objectAnimator
                android:propertyName="rotation"
                android:duration="400"
                android:valueFrom="0"
                android:valueTo="-20"
                android:valueType="floatType"
                android:interpolator="@android:interpolator/linear_out_slow_in"/>
        </aapt:attr>
    </target>
    <target android:name="broken_heart_right_group">
        <aapt:attr name="android:animation">
            <objectAnimator
                android:propertyName="rotation"
                android:duration="400"
                android:valueFrom="0"
                android:valueTo="20"
                android:valueType="floatType"
                android:interpolator="@android:interpolator/linear_out_slow_in"/>
        </aapt:attr>
    </target>
    <target android:name="broken_heart_left">
        <aapt:attr name="android:animation">
            <objectAnimator
                android:propertyName="fillAlpha"
                android:startOffset="100"
                android:duration="300"
                android:valueFrom="1"
                android:valueTo="0"
                android:valueType="floatType"
                android:interpolator="@android:interpolator/linear_out_slow_in"/>
        </aapt:attr>
    </target>
    <target android:name="broken_heart_right">
        <aapt:attr name="android:animation">
            <objectAnimator
                android:propertyName="fillAlpha"
                android:startOffset="100"
                android:duration="300"
                android:valueFrom="1"
                android:valueTo="0"
                android:valueType="floatType"
                android:interpolator="@android:interpolator/linear_out_slow_in"/>
        </aapt:attr>
    </target>
    <target android:name="heart_stroke_left">
        <aapt:attr name="android:animation">
            <objectAnimator
                android:propertyName="trimPathEnd"
                android:startOffset="500"
                android:duration="400"
                android:valueFrom="0"
                android:valueTo="1"
                android:valueType="floatType"
                android:interpolator="@android:interpolator/fast_out_slow_in"/>
        </aapt:attr>
    </target>
    <target android:name="heart_stroke_right">
        <aapt:attr name="android:animation">
            <objectAnimator
                android:propertyName="trimPathEnd"
                android:startOffset="500"
                android:duration="400"
                android:valueFrom="0"
                android:valueTo="1"
                android:valueType="floatType"
                android:interpolator="@android:interpolator/fast_out_slow_in"/>
        </aapt:attr>
    </target>
    <target android:name="clip">
        <aapt:attr name="android:animation">
            <set>
                <objectAnimator
                    android:propertyName="pathData"
                    android:startOffset="1160"
                    android:duration="120"
                    android:valueFrom="M 18 26 C 18 26 21 28 24 28 C 27 28 29 25 32 25 C 35 25 38 26 38 26 L 38 38 L 18 38 L 18 26 Z"
                    android:valueTo="M 18 18 C 18 18 24 18 24 18 C 24 18 32 18 32 18 C 32 18 38 18 38 18 L 38 38 L 18 38 L 18 18 Z"
                    android:valueType="pathType"
                    android:interpolator="@android:interpolator/fast_out_linear_in"/>
                <objectAnimator
                    android:propertyName="pathData"
                    android:startOffset="1000"
                    android:duration="160"
                    android:valueFrom="M 18 38 C 18 38 24 38 24 38 C 24 38 32 38 32 38 C 32 38 38 38 38 38 L 38 38 L 18 38 L 18 38 Z"
                    android:valueTo="M 18 26 C 18 26 21 28 24 28 C 27 28 29 25 32 25 C 35 25 38 26 38 26 L 38 38 L 18 38 L 18 26 Z"
                    android:valueType="pathType"
                    android:interpolator="@android:interpolator/fast_out_linear_in"/>
            </set>
        </aapt:attr>
    </target>
    <target android:name="heartbreak">
        <aapt:attr name="android:animation">
            <objectAnimator
                android:propertyName="alpha"
                android:startOffset="500"
                android:duration="400"
                android:valueFrom="0.4"
                android:valueTo="1"
                android:valueType="floatType"
                android:interpolator="@android:interpolator/fast_out_slow_in"/>
        </aapt:attr>
    </target>
</animated-vector>

After AVD exported in bodymovin format:

{
  "assets": [], //List of rasterized assets. Probably wouldn't exist with shapeshifter.
  "fr": 30, // Framerate. I would use 60 as a default.
  "h": 56, // Height of the canvas
  "w": 56, // Width of the canvas
  "ip": 0, // In point of the animation. It means at what frame to start animationg. It would always be 0 with Shapeshifter.
  "op": 37.5, // Out point of the animation in frames. It would be always the duration of the animation in Sh.Sh. 1250 * 30 / 1000
  "nm": "heart", // Name of the animation
  "v": "5.1.16", // Version of the exported ffile
  "layers": [ // This is the list of layers of the composition. Sh.Sh. could use only one and animate all in it in subgroups. Or set one layer per element.
    {
      "ty": 4, // Type of layer. In this case Shape Layer. With Sh.Sh. always 4. It could also be an image layer, text layer, etc.
      "ind": 1, // Layer index. An incremental number per layer would be fine.
      "st": 0, // Start value of the layer. In Sh.Sh. it will probably be 0.
      "ip": 0, // In point of the layer. In Sh.Sh. it will probably be 0.
      "op": 70, // Out point of the layer expressed in frames. With Sh.Sh. it should be [duration of the full animation in milliseconds * framerate / 1000]
      "ks": { // "ks" is an object with all transformations for the layer: Anchor Point(Or pivot), Opacity, Position, Rotation, Scale. With Sh.Sh. it will probably make sense to keep this values as the default values.
        "a": { // Anchor point property
          "k": [0,0,0] // "k" is the property that holds the animation information. It can be an array of numbers or keyframes. For the Anchor Point, default is 0,0,0
        },
        "o": { // Opacity
          "k": 100 // Default values. Ranges from 0 - 100
        },
        "p": { // Position
          "k": [0,0,0] // default 0,0,0
        },
        "r": { // Rotation in degrees
          "k": 0 // default 0
        },
        "s": { // Scale
          "k": [100,100,100] // default 100 (no scaling)
        }
      },
      "shapes": [ // This array contains all shapes and shape properties of the layer. It can hold nested groups as well.
        {
          "ty": "gr", // "ty" specifies the type of element. In this case "gr" it's a nested groupd.
          "it": [ // "it" are the nested elements of a group.
            {
              "ty": "sh", // "sh" type is a shape (shape or path are the same)
              "ks": { // Shape data.
                "k": { // Shape data keyframes or single keyframe. In this case it's a single keyframe since it doesn't interpolate the shape.
                  "c": true, // Is shape closed or open
                  "v": [[28.031, 21.054],[28, 21.09],[23.5, 19],[18, 24.5],[26.55, 36.03],[28, 37.35],[28.002, 37.348],[27.781, 36.988],[28.489, 36.073],[27.506, 34.764],[28.782, 33.027],[26.944, 31.008],[29.149, 28.725],[27.117, 27.143],[29.149, 25.018],[26.488, 22.977],[28.031, 21.054]],
                  "i": [[0, 0],[0, 0.01],[1.74, 0],[0, -3.08],[-5.15, -4.67],[0, 0],[0, 0],[0, 0],[0, 0],[0, 0],[0, 0],[0, 0],[0, 0],[0, 0],[0, 0],[0, 0],[0, 0]],
                  "o": [[0, -0.01],[-1.09, -1.28],[-3.08, 0],[0, 3.78],[0, 0],[0, 0],[0, 0],[0, 0],[0, 0],[0, 0],[0, 0],[0, 0],[0, 0],[0, 0],[0, 0],[0, 0],[0, 0]]
                } // "v" are vertices, "o" outpoints and "i" inpoints. In bodymovin i and o are relative to v. All shapes in bodymovin are bezier curves.
              }
            },
            {
              "ty": "fl", // This is the fill type
              "c": { // Color property
                "k": [0,0,0,1] // Color property value RGB ranging from 0 to 1. A is always 1. 
              },
              "o": { // Fill Opacity. This one is animated.
                "k": [ // Fill Opacity property keyframes.
                  {
                    "e": [0], // End value of segment. "valueTo" in AVD
                    "i": { // interpolation curve inpoint
                      "x": [0.2], // x component of inpoint
                      "y": [1] // y component of inpoint
                    },
                    "o": { // interpolation curve outpoint
                      "x": [0], // x component of outpoint
                      "y": [0] // y component of outpoint
                    },
                    "s": [100], // Starting value of segment. "valueFrom" in AVD
                    "t": 3, // Starting time in frames of the animation in frames. "startOffset". 100 * framerate / 1000
                  },
                  {
                    "t": 12, // End time in frames of the animation in frames. "startOffset + duration". (100+300) * framerate / 1000
                  }
                ]
              },
              "r": 1 // Fill-rule of the fill. 1 is Non-zero, 2 is Even-Odd
            },
            {
              "ty": "tr", // Transform type.
              "a": { // Anchor point (or Pivot)
                "k": [28,37.3]
              },
              "o": { // Opacity
                "k": 100
              },
              "p": { // Position. Needs to compensate the pivot value.
                "k": [28,37.3]
              },
              "r": { // Rotation. This is animated. so the "k" values is an array of segments (or keyframes).
                "k": [
                  {
                    "e": [-20], // End value of the rotation. "valueTo" in AVD
                    "i": { // interpolation curve inpoint
                      "x": [0.2], // x component of inpoint
                      "y": [1] // y component of inpoint
                    },
                    "o": { // interpolation curve outpoint
                      "x": [0], // x component of outpoint
                      "y": [0] // x component of outpoint
                    },
                    "s": [0], // Starting value of segment. "valueFrom" in AVD
                    "t": 3 // Starting time in frames of the animation in frames. "startOffset". 100 * framerate / 1000
                  },
                  {
                    "t": 12 // End time in frames of the animation in frames. "startOffset + duration". (100+300) * framerate / 1000
                  }
                ]
              },
              "s": { // Scale value
                "k": [100,100]
              },
              "sa": { // Skew Angle
                "k": 0
              },
              "sk": { // Skew
                "k": 0
              }
            }
          ]
        }
      ]
    }
  ]
}
divyanshub024 commented 4 years ago

@alexjlockwood Any update on this?