Moved AnimatableValue parsing to Parser classes
diff --git a/lottie/src/main/java/com/airbnb/lottie/animation/keyframe/PathKeyframe.java b/lottie/src/main/java/com/airbnb/lottie/animation/keyframe/PathKeyframe.java
index 375143c..ac161dc 100644
--- a/lottie/src/main/java/com/airbnb/lottie/animation/keyframe/PathKeyframe.java
+++ b/lottie/src/main/java/com/airbnb/lottie/animation/keyframe/PathKeyframe.java
@@ -8,8 +8,8 @@
 
 import com.airbnb.lottie.LottieComposition;
 import com.airbnb.lottie.animation.Keyframe;
-import com.airbnb.lottie.model.animatable.AnimatableValue;
 import com.airbnb.lottie.parser.KeyframeParser;
+import com.airbnb.lottie.parser.ValueParser;
 import com.airbnb.lottie.utils.Utils;
 
 import java.io.IOException;
@@ -36,10 +36,10 @@
     }
 
     public static PathKeyframe newInstance(JsonReader reader, LottieComposition composition,
-        AnimatableValue.Factory<PointF> valueFactory) throws IOException {
+        ValueParser<PointF> valueParser) throws IOException {
       boolean animated = reader.peek() == JsonToken.BEGIN_OBJECT;
       Keyframe<PointF> keyframe = KeyframeParser.parse(
-          reader, composition, Utils.dpScale(), valueFactory, animated);
+          reader, composition, Utils.dpScale(), valueParser, animated);
 
       return new PathKeyframe(composition, keyframe);
     }
diff --git a/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableColorValue.java b/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableColorValue.java
index 0ef8568..a6302e5 100644
--- a/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableColorValue.java
+++ b/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableColorValue.java
@@ -1,33 +1,17 @@
 package com.airbnb.lottie.model.animatable;
 
-import android.util.JsonReader;
-
-import com.airbnb.lottie.LottieComposition;
 import com.airbnb.lottie.animation.Keyframe;
 import com.airbnb.lottie.animation.keyframe.BaseKeyframeAnimation;
 import com.airbnb.lottie.animation.keyframe.ColorKeyframeAnimation;
-import com.airbnb.lottie.model.ColorFactory;
 
-import java.io.IOException;
 import java.util.List;
 
 public class AnimatableColorValue extends BaseAnimatableValue<Integer, Integer> {
-  private AnimatableColorValue(List<Keyframe<Integer>> keyframes) {
+  public AnimatableColorValue(List<Keyframe<Integer>> keyframes) {
     super(keyframes);
   }
 
   @Override public BaseKeyframeAnimation<Integer, Integer> createAnimation() {
     return new ColorKeyframeAnimation(keyframes);
   }
-
-  public static final class Factory {
-    private Factory() {
-    }
-
-    public static AnimatableColorValue newInstance(
-        JsonReader reader, LottieComposition composition) throws IOException {
-      return new AnimatableColorValue(
-          AnimatableValueParser.newInstance(reader, 1f, composition, ColorFactory.INSTANCE));
-    }
-  }
 }
diff --git a/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableFloatValue.java b/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableFloatValue.java
index b887665..0d372dd 100644
--- a/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableFloatValue.java
+++ b/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableFloatValue.java
@@ -1,20 +1,14 @@
 package com.airbnb.lottie.model.animatable;
 
-import android.util.JsonReader;
-
-import com.airbnb.lottie.LottieComposition;
 import com.airbnb.lottie.animation.Keyframe;
 import com.airbnb.lottie.animation.keyframe.BaseKeyframeAnimation;
 import com.airbnb.lottie.animation.keyframe.FloatKeyframeAnimation;
-import com.airbnb.lottie.utils.JsonUtils;
-import com.airbnb.lottie.utils.Utils;
 
-import java.io.IOException;
 import java.util.List;
 
 public class AnimatableFloatValue extends BaseAnimatableValue<Float, Float> {
 
-  private AnimatableFloatValue() {
+  AnimatableFloatValue() {
     this(0f);
   }
 
@@ -22,43 +16,11 @@
     super(value);
   }
 
-  private AnimatableFloatValue(List<Keyframe<Float>> keyframes) {
+  public AnimatableFloatValue(List<Keyframe<Float>> keyframes) {
     super(keyframes);
   }
 
   @Override public BaseKeyframeAnimation<Float, Float> createAnimation() {
     return new FloatKeyframeAnimation(keyframes);
   }
-
-  private static class ValueFactory implements AnimatableValue.Factory<Float> {
-    static final ValueFactory INSTANCE = new ValueFactory();
-
-    private ValueFactory() {
-    }
-
-    @Override public Float valueFromObject(JsonReader reader, float scale) throws IOException {
-      return JsonUtils.valueFromObject(reader) * scale;
-    }
-  }
-
-  public static final class Factory {
-    private Factory() {
-    }
-
-    static AnimatableFloatValue newInstance() {
-      return new AnimatableFloatValue();
-    }
-
-    public static AnimatableFloatValue newInstance(JsonReader reader, LottieComposition composition)
-        throws IOException {
-      return newInstance(reader, composition, true);
-    }
-
-    public static AnimatableFloatValue newInstance(
-        JsonReader reader, LottieComposition composition, boolean isDp) throws IOException {
-      float scale = isDp ? Utils.dpScale() : 1f;
-      return new AnimatableFloatValue(
-          AnimatableValueParser.newInstance(reader, scale, composition, ValueFactory.INSTANCE));
-    }
-  }
 }
diff --git a/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableGradientColorValue.java b/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableGradientColorValue.java
index 91d8558..66cc706 100644
--- a/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableGradientColorValue.java
+++ b/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableGradientColorValue.java
@@ -1,26 +1,15 @@
 package com.airbnb.lottie.model.animatable;
 
-import android.graphics.Color;
-import android.support.annotation.IntRange;
-import android.util.JsonReader;
-import android.util.JsonToken;
-import android.util.Log;
-
-import com.airbnb.lottie.L;
-import com.airbnb.lottie.LottieComposition;
 import com.airbnb.lottie.animation.Keyframe;
 import com.airbnb.lottie.animation.keyframe.BaseKeyframeAnimation;
 import com.airbnb.lottie.animation.keyframe.GradientColorKeyframeAnimation;
 import com.airbnb.lottie.model.content.GradientColor;
-import com.airbnb.lottie.utils.MiscUtils;
 
-import java.io.IOException;
-import java.util.ArrayList;
 import java.util.List;
 
 public class AnimatableGradientColorValue extends BaseAnimatableValue<GradientColor,
     GradientColor> {
-  private AnimatableGradientColorValue(
+  public AnimatableGradientColorValue(
       List<Keyframe<GradientColor>> keyframes) {
     super(keyframes);
   }
@@ -28,154 +17,4 @@
   @Override public BaseKeyframeAnimation<GradientColor, GradientColor> createAnimation() {
     return new GradientColorKeyframeAnimation(keyframes);
   }
-
-  public static final class Factory {
-    private Factory() {
-    }
-
-    public static AnimatableGradientColorValue newInstance(
-        JsonReader reader, LottieComposition composition, int points) throws IOException {
-      return new AnimatableGradientColorValue(
-          AnimatableValueParser.newInstance(reader, 1, composition, new ValueFactory(points))
-      );
-    }
-  }
-
-  private static class ValueFactory implements AnimatableValue.Factory<GradientColor> {
-    /** The number of colors if it exists in the json or -1 if it doesn't (legacy bodymovin) */
-    private int colorPoints;
-
-    private ValueFactory(int colorPoints) {
-      this.colorPoints = colorPoints;
-    }
-
-    /**
-     * Both the color stops and opacity stops are in the same array.
-     * There are {@link #colorPoints} colors sequentially as:
-     * [
-     *     ...,
-     *     position,
-     *     red,
-     *     green,
-     *     blue,
-     *     ...
-     * ]
-     *
-     * The remainder of the array is the opacity stops sequentially as:
-     * [
-     *     ...,
-     *     position,
-     *     opacity,
-     *     ...
-     * ]
-     */
-    @Override public GradientColor valueFromObject(JsonReader reader, float scale)
-        throws IOException {
-      List<Float> array = new ArrayList<>();
-      // The array was started by Keyframe because it thought that this may be an array of keyframes
-      // but peek returned a number so it considered it a static array of numbers.
-      boolean isArray = reader.peek() == JsonToken.BEGIN_ARRAY;
-      if (isArray) {
-        reader.beginArray();
-      }
-      while (reader.hasNext()) {
-        array.add((float) reader.nextDouble());
-      }
-      if(isArray) {
-        reader.endArray();
-      }
-      if (colorPoints == -1) {
-        colorPoints = array.size() / 4;
-      }
-
-      float[] positions = new float[colorPoints];
-      int[] colors = new int[colorPoints];
-
-      int r = 0;
-      int g = 0;
-      if (array.size() != colorPoints * 4) {
-        Log.w(L.TAG, "Unexpected gradient length: " + array.size() +
-            ". Expected " + (colorPoints * 4) + ". This may affect the appearance of the gradient. " +
-            "Make sure to save your After Effects file before exporting an animation with " +
-            "gradients.");
-      }
-      for (int i = 0; i < colorPoints * 4; i++) {
-        int colorIndex = i / 4;
-        double value = array.get(i);
-        switch (i % 4) {
-          case 0:
-            // position
-            positions[colorIndex] = (float) value;
-            break;
-          case 1:
-            r = (int) (value * 255);
-            break;
-          case 2:
-            g = (int) (value * 255);
-            break;
-          case 3:
-            int b = (int) (value * 255);
-            colors[colorIndex] = Color.argb(255, r, g, b);
-            break;
-        }
-      }
-
-      GradientColor gradientColor = new GradientColor(positions, colors);
-      addOpacityStopsToGradientIfNeeded(gradientColor, array);
-      return gradientColor;
-    }
-
-    /**
-     * This cheats a little bit.
-     * Opacity stops can be at arbitrary intervals independent of color stops.
-     * This uses the existing color stops and modifies the opacity at each existing color stop
-     * based on what the opacity would be.
-     *
-     * This should be a good approximation is nearly all cases. However, if there are many more
-     * opacity stops than color stops, information will be lost.
-     */
-    private void addOpacityStopsToGradientIfNeeded(GradientColor gradientColor, List<Float> array) {
-      int startIndex = colorPoints * 4;
-      if (array.size() <= startIndex) {
-        return;
-      }
-
-      int opacityStops = (array.size() - startIndex) / 2;
-      double[] positions = new double[opacityStops];
-      double[] opacities = new double[opacityStops];
-
-      for (int i = startIndex, j = 0; i < array.size(); i++) {
-        if (i % 2 == 0) {
-          positions[j] = array.get(i);
-        } else {
-          opacities[j] = array.get(i);
-          j++;
-        }
-      }
-
-      for (int i = 0; i < gradientColor.getSize(); i++) {
-        int color = gradientColor.getColors()[i];
-        color = Color.argb(
-            getOpacityAtPosition(gradientColor.getPositions()[i], positions, opacities),
-            Color.red(color),
-            Color.green(color),
-            Color.blue(color)
-        );
-        gradientColor.getColors()[i] = color;
-      }
-    }
-
-    @IntRange(from=0, to=255)
-    private int getOpacityAtPosition(double position, double[] positions, double[] opacities) {
-      for (int i = 1; i < positions.length; i++) {
-        double lastPosition = positions[i - 1];
-        double thisPosition = positions[i];
-        if (positions[i] >= position) {
-          double progress = (position - lastPosition) / (thisPosition - lastPosition);
-          return (int) (255 * MiscUtils.lerp(opacities[i - 1], opacities[i], progress));
-        }
-      }
-      return (int) (255 * opacities[opacities.length - 1]);
-    }
-  }
 }
diff --git a/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableIntegerValue.java b/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableIntegerValue.java
index 642d3a3..3d0311a 100644
--- a/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableIntegerValue.java
+++ b/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableIntegerValue.java
@@ -1,19 +1,14 @@
 package com.airbnb.lottie.model.animatable;
 
-import android.util.JsonReader;
-
-import com.airbnb.lottie.LottieComposition;
 import com.airbnb.lottie.animation.Keyframe;
 import com.airbnb.lottie.animation.keyframe.BaseKeyframeAnimation;
 import com.airbnb.lottie.animation.keyframe.IntegerKeyframeAnimation;
-import com.airbnb.lottie.utils.JsonUtils;
 
-import java.io.IOException;
 import java.util.List;
 
 public class AnimatableIntegerValue extends BaseAnimatableValue<Integer, Integer> {
 
-  private AnimatableIntegerValue() {
+  public AnimatableIntegerValue() {
     this(100);
   }
 
@@ -21,38 +16,11 @@
     super(value);
   }
 
-  AnimatableIntegerValue(List<Keyframe<Integer>> keyframes) {
+  public AnimatableIntegerValue(List<Keyframe<Integer>> keyframes) {
     super(keyframes);
   }
 
   @Override public BaseKeyframeAnimation<Integer, Integer> createAnimation() {
     return new IntegerKeyframeAnimation(keyframes);
   }
-
-  public static final class Factory {
-    private Factory() {
-    }
-
-    static AnimatableIntegerValue newInstance() {
-      return new AnimatableIntegerValue();
-    }
-
-    public static AnimatableIntegerValue newInstance(
-        JsonReader reader, LottieComposition composition) throws IOException {
-      return new AnimatableIntegerValue(
-          AnimatableValueParser.newInstance(reader, 1, composition, ValueFactory.INSTANCE)
-      );
-    }
-  }
-
-  private static class ValueFactory implements AnimatableValue.Factory<Integer> {
-    private static final ValueFactory INSTANCE = new ValueFactory();
-
-    private ValueFactory() {
-    }
-
-    @Override public Integer valueFromObject(JsonReader reader, float scale) throws IOException {
-      return Math.round(JsonUtils.valueFromObject(reader) * scale);
-    }
-  }
 }
diff --git a/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatablePathValue.java b/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatablePathValue.java
index 458b7c2..945f3e3 100644
--- a/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatablePathValue.java
+++ b/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatablePathValue.java
@@ -10,7 +10,9 @@
 import com.airbnb.lottie.animation.keyframe.PathKeyframe;
 import com.airbnb.lottie.animation.keyframe.PathKeyframeAnimation;
 import com.airbnb.lottie.animation.keyframe.PointKeyframeAnimation;
+import com.airbnb.lottie.parser.AnimatableValueParser;
 import com.airbnb.lottie.parser.KeyframesParser;
+import com.airbnb.lottie.parser.PathParser;
 import com.airbnb.lottie.utils.JsonUtils;
 import com.airbnb.lottie.utils.Utils;
 
@@ -39,7 +41,7 @@
             hasExpressions = true;
             reader.skipValue();
           } else {
-            xAnimation = AnimatableFloatValue.Factory.newInstance(reader, composition);
+            xAnimation = AnimatableValueParser.parseFloat(reader, composition);
           }
           break;
         case "y":
@@ -47,7 +49,7 @@
             hasExpressions = true;
             reader.skipValue();
           } else {
-            yAnimation = AnimatableFloatValue.Factory.newInstance(reader, composition);
+            yAnimation = AnimatableValueParser.parseFloat(reader, composition);
           }
           break;
         default:
@@ -80,7 +82,7 @@
       reader.beginArray();
       while (reader.hasNext()) {
         PathKeyframe keyframe =
-            PathKeyframe.Factory.newInstance(reader, composition, ValueFactory.INSTANCE);
+            PathKeyframe.Factory.newInstance(reader, composition, PathParser.INSTANCE);
         keyframes.add(keyframe);
       }
       reader.endArray();
@@ -97,15 +99,4 @@
     }
     return new PathKeyframeAnimation(keyframes);
   }
-
-  private static class ValueFactory implements AnimatableValue.Factory<PointF> {
-    private static final Factory<PointF> INSTANCE = new ValueFactory();
-
-    private ValueFactory() {
-    }
-
-    @Override public PointF valueFromObject(JsonReader reader, float scale) throws IOException {
-      return JsonUtils.jsonToPoint(reader, scale);
-    }
-  }
 }
diff --git a/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatablePointValue.java b/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatablePointValue.java
index e01be61..4f48692 100644
--- a/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatablePointValue.java
+++ b/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatablePointValue.java
@@ -1,36 +1,19 @@
 package com.airbnb.lottie.model.animatable;
 
 import android.graphics.PointF;
-import android.util.JsonReader;
 
-import com.airbnb.lottie.LottieComposition;
 import com.airbnb.lottie.animation.Keyframe;
 import com.airbnb.lottie.animation.keyframe.BaseKeyframeAnimation;
 import com.airbnb.lottie.animation.keyframe.PointKeyframeAnimation;
-import com.airbnb.lottie.model.PointFFactory;
-import com.airbnb.lottie.utils.Utils;
 
-import java.io.IOException;
 import java.util.List;
 
 public class AnimatablePointValue extends BaseAnimatableValue<PointF, PointF> {
-  private AnimatablePointValue(List<Keyframe<PointF>> keyframes) {
+  public AnimatablePointValue(List<Keyframe<PointF>> keyframes) {
     super(keyframes);
   }
 
   @Override public BaseKeyframeAnimation<PointF, PointF> createAnimation() {
     return new PointKeyframeAnimation(keyframes);
   }
-
-  public static final class Factory {
-    private Factory() {
-    }
-
-    public static AnimatablePointValue newInstance(
-        JsonReader reader, LottieComposition composition) throws IOException {
-      return new AnimatablePointValue(AnimatableValueParser
-              .newInstance(reader, Utils.dpScale(), composition, PointFFactory.INSTANCE)
-      );
-    }
-  }
 }
diff --git a/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableScaleValue.java b/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableScaleValue.java
index 322ad2c..af2c0b3 100644
--- a/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableScaleValue.java
+++ b/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableScaleValue.java
@@ -1,19 +1,15 @@
 package com.airbnb.lottie.model.animatable;
 
-import android.util.JsonReader;
-
-import com.airbnb.lottie.LottieComposition;
 import com.airbnb.lottie.animation.Keyframe;
 import com.airbnb.lottie.animation.keyframe.BaseKeyframeAnimation;
 import com.airbnb.lottie.animation.keyframe.ScaleKeyframeAnimation;
 import com.airbnb.lottie.value.ScaleXY;
 
-import java.io.IOException;
 import java.util.List;
 
 public class AnimatableScaleValue extends BaseAnimatableValue<ScaleXY, ScaleXY> {
 
-  private AnimatableScaleValue() {
+  AnimatableScaleValue() {
     this(new ScaleXY(1f, 1f));
   }
 
@@ -21,27 +17,11 @@
     super(value);
   }
 
-  AnimatableScaleValue(List<Keyframe<ScaleXY>> keyframes) {
+  public AnimatableScaleValue(List<Keyframe<ScaleXY>> keyframes) {
     super(keyframes);
   }
 
   @Override public BaseKeyframeAnimation<ScaleXY, ScaleXY> createAnimation() {
     return new ScaleKeyframeAnimation(keyframes);
   }
-
-  static final class Factory {
-    private Factory() {
-    }
-
-    static AnimatableScaleValue newInstance(
-        JsonReader reader, LottieComposition composition) throws IOException {
-      return new AnimatableScaleValue(
-          AnimatableValueParser.newInstance(reader, 1, composition, ScaleXY.Factory.INSTANCE)
-      );
-    }
-
-    static AnimatableScaleValue newInstance() {
-      return new AnimatableScaleValue();
-    }
-  }
 }
diff --git a/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableShapeValue.java b/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableShapeValue.java
index 37e5cf4..d5c3236 100644
--- a/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableShapeValue.java
+++ b/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableShapeValue.java
@@ -1,37 +1,21 @@
 package com.airbnb.lottie.model.animatable;
 
 import android.graphics.Path;
-import android.util.JsonReader;
 
-import com.airbnb.lottie.LottieComposition;
 import com.airbnb.lottie.animation.Keyframe;
 import com.airbnb.lottie.animation.keyframe.BaseKeyframeAnimation;
 import com.airbnb.lottie.animation.keyframe.ShapeKeyframeAnimation;
 import com.airbnb.lottie.model.content.ShapeData;
-import com.airbnb.lottie.utils.Utils;
 
-import java.io.IOException;
 import java.util.List;
 
 public class AnimatableShapeValue extends BaseAnimatableValue<ShapeData, Path> {
 
-  private AnimatableShapeValue(List<Keyframe<ShapeData>> keyframes) {
+  public AnimatableShapeValue(List<Keyframe<ShapeData>> keyframes) {
     super(keyframes);
   }
 
   @Override public BaseKeyframeAnimation<ShapeData, Path> createAnimation() {
     return new ShapeKeyframeAnimation(keyframes);
   }
-
-  public static final class Factory {
-    private Factory() {
-    }
-
-    public static AnimatableShapeValue newInstance(
-        JsonReader reader, LottieComposition composition) throws IOException {
-      return new AnimatableShapeValue(AnimatableValueParser
-              .newInstance(reader, Utils.dpScale(), composition, ShapeData.Factory.INSTANCE)
-      );
-    }
-  }
 }
diff --git a/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableSplitDimensionPathValue.java b/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableSplitDimensionPathValue.java
index 442ac03..3fbf5a6 100644
--- a/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableSplitDimensionPathValue.java
+++ b/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableSplitDimensionPathValue.java
@@ -20,5 +20,4 @@
     return new SplitDimensionPathKeyframeAnimation(
         animatableXDimension.createAnimation(), animatableYDimension.createAnimation());
   }
-
 }
diff --git a/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableTextFrame.java b/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableTextFrame.java
index 8edb499..054e6a3 100644
--- a/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableTextFrame.java
+++ b/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableTextFrame.java
@@ -1,48 +1,18 @@
 package com.airbnb.lottie.model.animatable;
 
-import android.util.JsonReader;
-
-import com.airbnb.lottie.LottieComposition;
 import com.airbnb.lottie.animation.Keyframe;
 import com.airbnb.lottie.animation.keyframe.TextKeyframeAnimation;
 import com.airbnb.lottie.model.DocumentData;
-import com.airbnb.lottie.parser.DocumentDataParser;
 
-import java.io.IOException;
 import java.util.List;
 
 public class AnimatableTextFrame extends BaseAnimatableValue<DocumentData, DocumentData> {
 
-  AnimatableTextFrame(List<Keyframe<DocumentData>> keyframes) {
+  public AnimatableTextFrame(List<Keyframe<DocumentData>> keyframes) {
     super(keyframes);
   }
 
   @Override public TextKeyframeAnimation createAnimation() {
     return new TextKeyframeAnimation(keyframes);
   }
-
-  public static final class Factory {
-    private Factory() {
-    }
-
-    public static AnimatableTextFrame newInstance(
-        JsonReader reader, LottieComposition composition) throws IOException {
-      return new AnimatableTextFrame(
-          AnimatableValueParser
-              .newInstance(reader, 1, composition, AnimatableTextFrame.ValueFactory.INSTANCE));
-    }
-  }
-
-  private static class ValueFactory implements AnimatableValue.Factory<DocumentData> {
-    private static final AnimatableTextFrame.ValueFactory INSTANCE =
-        new AnimatableTextFrame.ValueFactory();
-
-    private ValueFactory() {
-    }
-
-    @Override
-    public DocumentData valueFromObject(JsonReader reader, float scale) throws IOException {
-      return DocumentDataParser.parse(reader);
-    }
-  }
 }
diff --git a/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableTextProperties.java b/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableTextProperties.java
index 6ffa4a8..6e3a8a5 100644
--- a/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableTextProperties.java
+++ b/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableTextProperties.java
@@ -4,6 +4,7 @@
 import android.util.JsonReader;
 
 import com.airbnb.lottie.LottieComposition;
+import com.airbnb.lottie.parser.AnimatableValueParser;
 
 import java.io.IOException;
 
@@ -61,16 +62,16 @@
       while (reader.hasNext()) {
         switch (reader.nextName()) {
           case "fc":
-            color = AnimatableColorValue.Factory.newInstance(reader, composition);
+            color = AnimatableValueParser.parseColor(reader, composition);
             break;
           case "sc":
-            stroke = AnimatableColorValue.Factory.newInstance(reader, composition);
+            stroke = AnimatableValueParser.parseColor(reader, composition);
             break;
           case "sw":
-            strokeWidth = AnimatableFloatValue.Factory.newInstance(reader, composition);
+            strokeWidth = AnimatableValueParser.parseFloat(reader, composition);
             break;
           case "t":
-            tracking = AnimatableFloatValue.Factory.newInstance(reader, composition);
+            tracking = AnimatableValueParser.parseFloat(reader, composition);
             break;
           default:
             reader.skipValue();
diff --git a/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableTransform.java b/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableTransform.java
index 5aa5aac..c448934 100644
--- a/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableTransform.java
+++ b/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableTransform.java
@@ -14,6 +14,7 @@
 import com.airbnb.lottie.animation.keyframe.TransformKeyframeAnimation;
 import com.airbnb.lottie.model.content.ContentModel;
 import com.airbnb.lottie.model.layer.BaseLayer;
+import com.airbnb.lottie.parser.AnimatableValueParser;
 import com.airbnb.lottie.value.ScaleXY;
 
 import java.io.IOException;
@@ -85,11 +86,11 @@
     public static AnimatableTransform newInstance() {
       AnimatablePathValue anchorPoint = new AnimatablePathValue();
       AnimatableValue<PointF, PointF> position = new AnimatablePathValue();
-      AnimatableScaleValue scale = AnimatableScaleValue.Factory.newInstance();
-      AnimatableFloatValue rotation = AnimatableFloatValue.Factory.newInstance();
-      AnimatableIntegerValue opacity = AnimatableIntegerValue.Factory.newInstance();
-      AnimatableFloatValue startOpacity = AnimatableFloatValue.Factory.newInstance();
-      AnimatableFloatValue endOpacity = AnimatableFloatValue.Factory.newInstance();
+      AnimatableScaleValue scale = new AnimatableScaleValue();
+      AnimatableFloatValue rotation = new AnimatableFloatValue();
+      AnimatableIntegerValue opacity = new AnimatableIntegerValue();
+      AnimatableFloatValue startOpacity = new AnimatableFloatValue();
+      AnimatableFloatValue endOpacity = new AnimatableFloatValue();
       return new AnimatableTransform(anchorPoint, position, scale, rotation, opacity, startOpacity,
           endOpacity);
     }
@@ -126,23 +127,21 @@
                 AnimatablePathValue.createAnimatablePathOrSplitDimensionPath(reader, composition);
             break;
           case "s":
-            scale = AnimatableScaleValue.Factory.newInstance(reader, composition);
+            scale = AnimatableValueParser.parseScale(reader, composition);
             break;
           case "rz":
             composition.addWarning("Lottie doesn't support 3D layers.");
           case "r":
-            rotation = AnimatableFloatValue.Factory.newInstance(reader, composition, false);
+            rotation = AnimatableValueParser.parseFloat(reader, composition, false);
             break;
           case "o":
-            opacity = AnimatableIntegerValue.Factory.newInstance(reader, composition);
+            opacity = AnimatableValueParser.parseInteger(reader, composition);
             break;
           case "so":
-            startOpacity =
-                AnimatableFloatValue.Factory.newInstance(reader, composition, false);
+            startOpacity = AnimatableValueParser.parseFloat(reader, composition, false);
             break;
           case "eo":
-            endOpacity =
-                AnimatableFloatValue.Factory.newInstance(reader, composition, false);
+            endOpacity = AnimatableValueParser.parseFloat(reader, composition, false);
             break;
           default:
             reader.skipValue();
diff --git a/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableValue.java b/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableValue.java
index 4da1429..b8fb1cb 100644
--- a/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableValue.java
+++ b/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableValue.java
@@ -1,15 +1,7 @@
 package com.airbnb.lottie.model.animatable;
 
-import android.util.JsonReader;
-
 import com.airbnb.lottie.animation.keyframe.BaseKeyframeAnimation;
 
-import java.io.IOException;
-
 public interface AnimatableValue<K, A> {
   BaseKeyframeAnimation<K, A> createAnimation();
-
-  interface Factory<V> {
-    V valueFromObject(JsonReader reader, float scale) throws IOException;
-  }
 }
diff --git a/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableValueParser.java b/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableValueParser.java
deleted file mode 100644
index 45cfaa9..0000000
--- a/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableValueParser.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.airbnb.lottie.model.animatable;
-
-import android.support.annotation.Nullable;
-import android.util.JsonReader;
-
-import com.airbnb.lottie.LottieComposition;
-import com.airbnb.lottie.animation.Keyframe;
-import com.airbnb.lottie.parser.KeyframesParser;
-
-import java.io.IOException;
-import java.util.List;
-
-class AnimatableValueParser<T> {
-  private final JsonReader reader;
-  private final float scale;
-  private final LottieComposition composition;
-  private final AnimatableValue.Factory<T> valueFactory;
-
-  private AnimatableValueParser(JsonReader reader, float scale, LottieComposition
-      composition, AnimatableValue.Factory<T> valueFactory) {
-    this.reader = reader;
-    this.scale = scale;
-    this.composition = composition;
-    this.valueFactory = valueFactory;
-  }
-
-  /**
-   * Will return null if the animation can't be played such as if it has expressions.
-   */
-  @Nullable static <T> List<Keyframe<T>> newInstance(JsonReader reader, float scale,
-      LottieComposition composition, AnimatableValue.Factory<T> valueFactory) throws IOException {
-    AnimatableValueParser<T> parser =
-        new AnimatableValueParser<>(reader, scale, composition, valueFactory);
-    return parser.parseKeyframes();
-  }
-
-  /**
-   * Will return null if the animation can't be played such as if it has expressions.
-   */
-  private List<Keyframe<T>> parseKeyframes() throws IOException {
-    return KeyframesParser.parse(reader, composition, scale, valueFactory);
-  }
-}
diff --git a/lottie/src/main/java/com/airbnb/lottie/model/content/Mask.java b/lottie/src/main/java/com/airbnb/lottie/model/content/Mask.java
index 051afe2..849b286 100644
--- a/lottie/src/main/java/com/airbnb/lottie/model/content/Mask.java
+++ b/lottie/src/main/java/com/airbnb/lottie/model/content/Mask.java
@@ -7,6 +7,7 @@
 import com.airbnb.lottie.LottieComposition;
 import com.airbnb.lottie.model.animatable.AnimatableIntegerValue;
 import com.airbnb.lottie.model.animatable.AnimatableShapeValue;
+import com.airbnb.lottie.parser.AnimatableValueParser;
 
 import java.io.IOException;
 
@@ -58,10 +59,10 @@
             }
             break;
           case "pt":
-            maskPath = AnimatableShapeValue.Factory.newInstance(reader, composition);
+            maskPath = AnimatableValueParser.parseShapeData(reader, composition);
             break;
           case "o":
-            opacity = AnimatableIntegerValue.Factory.newInstance(reader, composition);
+            opacity = AnimatableValueParser.parseInteger(reader, composition);
             break;
           default:
             reader.skipValue();
diff --git a/lottie/src/main/java/com/airbnb/lottie/model/content/ShapeData.java b/lottie/src/main/java/com/airbnb/lottie/model/content/ShapeData.java
index f09404b..4c9b0b2 100644
--- a/lottie/src/main/java/com/airbnb/lottie/model/content/ShapeData.java
+++ b/lottie/src/main/java/com/airbnb/lottie/model/content/ShapeData.java
@@ -2,17 +2,11 @@
 
 import android.graphics.PointF;
 import android.support.annotation.FloatRange;
-import android.util.JsonReader;
-import android.util.JsonToken;
 
 import com.airbnb.lottie.model.CubicCurveData;
-import com.airbnb.lottie.model.animatable.AnimatableValue;
-import com.airbnb.lottie.utils.JsonUtils;
 import com.airbnb.lottie.utils.MiscUtils;
 
-import java.io.IOException;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 
 public class ShapeData {
@@ -20,7 +14,7 @@
   private PointF initialPoint;
   private boolean closed;
 
-  private ShapeData(PointF initialPoint, boolean closed, List<CubicCurveData> curves) {
+  public ShapeData(PointF initialPoint, boolean closed, List<CubicCurveData> curves) {
     this.initialPoint = initialPoint;
     this.closed = closed;
     this.curves.addAll(curves);
@@ -102,84 +96,4 @@
         "closed=" + closed +
         '}';
   }
-
-  public static class Factory implements AnimatableValue.Factory<ShapeData> {
-    public static final ShapeData.Factory INSTANCE = new Factory();
-
-    private Factory() {
-    }
-
-    @Override public ShapeData valueFromObject(JsonReader reader, float scale) throws IOException {
-      // Sometimes the points data is in a array of length 1. Sometimes the data is at the top
-      // level.
-      if (reader.peek() == JsonToken.BEGIN_ARRAY) {
-        reader.beginArray();
-      }
-
-      boolean closed = false;
-      List<PointF> pointsArray = null;
-      List<PointF> inTangents = null;
-      List<PointF> outTangents = null;
-      reader.beginObject();
-
-      while (reader.hasNext()) {
-        switch (reader.nextName()) {
-          case "c":
-            closed = reader.nextBoolean();
-            break;
-          case "v":
-            pointsArray =  JsonUtils.jsonToPoints(reader, scale);
-            break;
-          case "i":
-            inTangents =  JsonUtils.jsonToPoints(reader, scale);
-            break;
-          case "o":
-            outTangents =  JsonUtils.jsonToPoints(reader, scale);
-            break;
-        }
-      }
-
-      reader.endObject();
-
-      if (reader.peek() == JsonToken.END_ARRAY) {
-        reader.endArray();
-      }
-
-      if (pointsArray == null || inTangents == null || outTangents == null) {
-        throw new IllegalArgumentException("Shape data was missing information.");
-      }
-
-      if (pointsArray.isEmpty()) {
-        return new ShapeData(new PointF(), false, Collections.<CubicCurveData>emptyList());
-      }
-
-      int length = pointsArray.size();
-      PointF vertex = pointsArray.get(0);
-      PointF initialPoint = vertex;
-      List<CubicCurveData> curves = new ArrayList<>(length);
-
-      for (int i = 1; i < length; i++) {
-        vertex = pointsArray.get(i);
-        PointF previousVertex = pointsArray.get(i - 1);
-        PointF cp1 = outTangents.get(i - 1);
-        PointF cp2 = inTangents.get(i);
-        PointF shapeCp1 = MiscUtils.addPoints(previousVertex, cp1);
-        PointF shapeCp2 = MiscUtils.addPoints(vertex, cp2);
-        curves.add(new CubicCurveData(shapeCp1, shapeCp2, vertex));
-      }
-
-      if (closed) {
-        vertex = pointsArray.get(0);
-        PointF previousVertex = pointsArray.get(length - 1);
-        PointF cp1 = outTangents.get(length - 1);
-        PointF cp2 = inTangents.get(0);
-
-        PointF shapeCp1 = MiscUtils.addPoints(previousVertex, cp1);
-        PointF shapeCp2 = MiscUtils.addPoints(vertex, cp2);
-
-        curves.add(new CubicCurveData(shapeCp1, shapeCp2, vertex));
-      }
-      return new ShapeData(initialPoint, closed, curves);
-    }
-  }
 }
diff --git a/lottie/src/main/java/com/airbnb/lottie/parser/AnimatableValueParser.java b/lottie/src/main/java/com/airbnb/lottie/parser/AnimatableValueParser.java
new file mode 100644
index 0000000..231788e
--- /dev/null
+++ b/lottie/src/main/java/com/airbnb/lottie/parser/AnimatableValueParser.java
@@ -0,0 +1,89 @@
+package com.airbnb.lottie.parser;
+
+import android.support.annotation.Nullable;
+import android.util.JsonReader;
+
+import com.airbnb.lottie.LottieComposition;
+import com.airbnb.lottie.animation.Keyframe;
+import com.airbnb.lottie.model.animatable.AnimatableColorValue;
+import com.airbnb.lottie.model.animatable.AnimatableFloatValue;
+import com.airbnb.lottie.model.animatable.AnimatableGradientColorValue;
+import com.airbnb.lottie.model.animatable.AnimatableIntegerValue;
+import com.airbnb.lottie.model.animatable.AnimatablePointValue;
+import com.airbnb.lottie.model.animatable.AnimatableScaleValue;
+import com.airbnb.lottie.model.animatable.AnimatableShapeValue;
+import com.airbnb.lottie.model.animatable.AnimatableTextFrame;
+import com.airbnb.lottie.utils.Utils;
+
+import java.io.IOException;
+import java.util.List;
+
+public class AnimatableValueParser {
+  private AnimatableValueParser() {
+  }
+
+  public static AnimatableFloatValue parseFloat(
+      JsonReader reader, LottieComposition composition) throws IOException {
+    return parseFloat(reader, composition, true);
+  }
+
+  public static AnimatableFloatValue parseFloat(
+      JsonReader reader, LottieComposition composition, boolean isDp) throws IOException {
+    return new AnimatableFloatValue(
+        parse(reader, isDp ? Utils.dpScale() : 1f, composition, FloatParser.INSTANCE));
+  }
+
+  public static AnimatableIntegerValue parseInteger(
+      JsonReader reader, LottieComposition composition) throws IOException {
+    return new AnimatableIntegerValue(parse(reader, composition, IntegerParser.INSTANCE));
+  }
+
+  public static AnimatablePointValue parsePoint(
+      JsonReader reader, LottieComposition composition) throws IOException {
+    return new AnimatablePointValue(
+        parse(reader, Utils.dpScale(), composition, PointFParser.INSTANCE));
+  }
+
+  public static AnimatableScaleValue parseScale(
+      JsonReader reader, LottieComposition composition) throws IOException {
+    return new AnimatableScaleValue(parse(reader, composition, ScaleXYParser.INSTANCE));
+  }
+
+  public static AnimatableShapeValue parseShapeData(
+      JsonReader reader, LottieComposition composition) throws IOException {
+    return new AnimatableShapeValue(
+        parse(reader, Utils.dpScale(), composition, ShapeDataParser.INSTANCE));
+  }
+
+  public static AnimatableTextFrame parseDocumentData(
+      JsonReader reader, LottieComposition composition) throws IOException {
+    return new AnimatableTextFrame(parse(reader, composition, DocumentDataParser.INSTANCE));
+  }
+
+  public static AnimatableColorValue parseColor(
+      JsonReader reader, LottieComposition composition) throws IOException {
+    return new AnimatableColorValue(parse(reader, composition, ColorParser.INSTANCE));
+  }
+
+  public static AnimatableGradientColorValue parseGradientColor(
+      JsonReader reader, LottieComposition composition, int points) throws IOException {
+    return new AnimatableGradientColorValue(
+        parse(reader, composition, new GradientColorParser(points)));
+  }
+
+  /**
+   * Will return null if the animation can't be played such as if it has expressions.
+   */
+  @Nullable private static <T> List<Keyframe<T>> parse(JsonReader reader,
+      LottieComposition composition, ValueParser<T> valueParser) throws IOException {
+    return KeyframesParser.parse(reader, composition, 1, valueParser);
+  }
+
+  /**
+   * Will return null if the animation can't be played such as if it has expressions.
+   */
+  @Nullable private static <T> List<Keyframe<T>> parse(JsonReader reader, float scale,
+      LottieComposition composition, ValueParser<T> valueParser) throws IOException {
+    return KeyframesParser.parse(reader, composition, scale, valueParser);
+  }
+}
diff --git a/lottie/src/main/java/com/airbnb/lottie/parser/CircleShapeParser.java b/lottie/src/main/java/com/airbnb/lottie/parser/CircleShapeParser.java
index 8165086..8a56358 100644
--- a/lottie/src/main/java/com/airbnb/lottie/parser/CircleShapeParser.java
+++ b/lottie/src/main/java/com/airbnb/lottie/parser/CircleShapeParser.java
@@ -32,7 +32,7 @@
               .createAnimatablePathOrSplitDimensionPath(reader, composition);
           break;
         case "s":
-          size = AnimatablePointValue.Factory.newInstance(reader, composition);
+          size = AnimatableValueParser.parsePoint(reader, composition);
           break;
         case "d":
           // "d" is 2 for normal and 3 for reversed.
diff --git a/lottie/src/main/java/com/airbnb/lottie/model/ColorFactory.java b/lottie/src/main/java/com/airbnb/lottie/parser/ColorParser.java
similarity index 65%
rename from lottie/src/main/java/com/airbnb/lottie/model/ColorFactory.java
rename to lottie/src/main/java/com/airbnb/lottie/parser/ColorParser.java
index 2cd6b80..c3193e3 100644
--- a/lottie/src/main/java/com/airbnb/lottie/model/ColorFactory.java
+++ b/lottie/src/main/java/com/airbnb/lottie/parser/ColorParser.java
@@ -1,17 +1,17 @@
-package com.airbnb.lottie.model;
+package com.airbnb.lottie.parser;
 
 import android.graphics.Color;
 import android.util.JsonReader;
 import android.util.JsonToken;
 
-import com.airbnb.lottie.model.animatable.AnimatableValue;
-
 import java.io.IOException;
 
-public class ColorFactory implements AnimatableValue.Factory<Integer> {
-  public static final ColorFactory INSTANCE = new ColorFactory();
+public class ColorParser implements ValueParser<Integer> {
+  public static final ColorParser INSTANCE = new ColorParser();
 
-  @Override public Integer valueFromObject(JsonReader reader, float scale) throws IOException {
+  private ColorParser() {}
+
+  @Override public Integer parse(JsonReader reader, float scale) throws IOException {
     boolean isArray = reader.peek() == JsonToken.BEGIN_ARRAY;
     if (isArray) {
       reader.beginArray();
diff --git a/lottie/src/main/java/com/airbnb/lottie/parser/DocumentDataParser.java b/lottie/src/main/java/com/airbnb/lottie/parser/DocumentDataParser.java
index 37b51c8..1321023 100644
--- a/lottie/src/main/java/com/airbnb/lottie/parser/DocumentDataParser.java
+++ b/lottie/src/main/java/com/airbnb/lottie/parser/DocumentDataParser.java
@@ -7,11 +7,16 @@
 
 import java.io.IOException;
 
-public class DocumentDataParser {
+public class DocumentDataParser implements ValueParser<DocumentData> {
+  public static DocumentDataParser INSTANCE = new DocumentDataParser();
 
   private DocumentDataParser() {}
 
   public static DocumentData parse(JsonReader reader) throws IOException {
+    return INSTANCE.parse(reader, 1f);
+  }
+
+  @Override public DocumentData parse(JsonReader reader, float scale) throws IOException {
     String text = null;
     String fontName = null;
     double size = 0;
diff --git a/lottie/src/main/java/com/airbnb/lottie/parser/FloatParser.java b/lottie/src/main/java/com/airbnb/lottie/parser/FloatParser.java
new file mode 100644
index 0000000..4525928
--- /dev/null
+++ b/lottie/src/main/java/com/airbnb/lottie/parser/FloatParser.java
@@ -0,0 +1,17 @@
+package com.airbnb.lottie.parser;
+
+import android.util.JsonReader;
+
+import com.airbnb.lottie.utils.JsonUtils;
+
+import java.io.IOException;
+
+public class FloatParser implements ValueParser<Float> {
+  public static final FloatParser INSTANCE = new FloatParser();
+
+  private FloatParser() {}
+
+  @Override public Float parse(JsonReader reader, float scale) throws IOException {
+    return JsonUtils.valueFromObject(reader) * scale;
+  }
+}
\ No newline at end of file
diff --git a/lottie/src/main/java/com/airbnb/lottie/parser/GradientColorParser.java b/lottie/src/main/java/com/airbnb/lottie/parser/GradientColorParser.java
new file mode 100644
index 0000000..bf380e5
--- /dev/null
+++ b/lottie/src/main/java/com/airbnb/lottie/parser/GradientColorParser.java
@@ -0,0 +1,153 @@
+package com.airbnb.lottie.parser;
+
+import android.graphics.Color;
+import android.support.annotation.IntRange;
+import android.util.JsonReader;
+import android.util.JsonToken;
+import android.util.Log;
+
+import com.airbnb.lottie.L;
+import com.airbnb.lottie.model.content.GradientColor;
+import com.airbnb.lottie.utils.MiscUtils;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class GradientColorParser implements com.airbnb.lottie.parser.ValueParser<GradientColor> {
+  /** The number of colors if it exists in the json or -1 if it doesn't (legacy bodymovin) */
+  private int colorPoints;
+
+  public GradientColorParser(int colorPoints) {
+    this.colorPoints = colorPoints;
+  }
+
+  /**
+   * Both the color stops and opacity stops are in the same array.
+   * There are {@link #colorPoints} colors sequentially as:
+   * [
+   *     ...,
+   *     position,
+   *     red,
+   *     green,
+   *     blue,
+   *     ...
+   * ]
+   *
+   * The remainder of the array is the opacity stops sequentially as:
+   * [
+   *     ...,
+   *     position,
+   *     opacity,
+   *     ...
+   * ]
+   */
+  @Override public GradientColor parse(JsonReader reader, float scale)
+      throws IOException {
+    List<Float> array = new ArrayList<>();
+    // The array was started by Keyframe because it thought that this may be an array of keyframes
+    // but peek returned a number so it considered it a static array of numbers.
+    boolean isArray = reader.peek() == JsonToken.BEGIN_ARRAY;
+    if (isArray) {
+      reader.beginArray();
+    }
+    while (reader.hasNext()) {
+      array.add((float) reader.nextDouble());
+    }
+    if(isArray) {
+      reader.endArray();
+    }
+    if (colorPoints == -1) {
+      colorPoints = array.size() / 4;
+    }
+
+    float[] positions = new float[colorPoints];
+    int[] colors = new int[colorPoints];
+
+    int r = 0;
+    int g = 0;
+    if (array.size() != colorPoints * 4) {
+      Log.w(L.TAG, "Unexpected gradient length: " + array.size() +
+          ". Expected " + (colorPoints * 4) + ". This may affect the appearance of the gradient. " +
+          "Make sure to save your After Effects file before exporting an animation with " +
+          "gradients.");
+    }
+    for (int i = 0; i < colorPoints * 4; i++) {
+      int colorIndex = i / 4;
+      double value = array.get(i);
+      switch (i % 4) {
+        case 0:
+          // position
+          positions[colorIndex] = (float) value;
+          break;
+        case 1:
+          r = (int) (value * 255);
+          break;
+        case 2:
+          g = (int) (value * 255);
+          break;
+        case 3:
+          int b = (int) (value * 255);
+          colors[colorIndex] = Color.argb(255, r, g, b);
+          break;
+      }
+    }
+
+    GradientColor gradientColor = new GradientColor(positions, colors);
+    addOpacityStopsToGradientIfNeeded(gradientColor, array);
+    return gradientColor;
+  }
+
+  /**
+   * This cheats a little bit.
+   * Opacity stops can be at arbitrary intervals independent of color stops.
+   * This uses the existing color stops and modifies the opacity at each existing color stop
+   * based on what the opacity would be.
+   *
+   * This should be a good approximation is nearly all cases. However, if there are many more
+   * opacity stops than color stops, information will be lost.
+   */
+  private void addOpacityStopsToGradientIfNeeded(GradientColor gradientColor, List<Float> array) {
+    int startIndex = colorPoints * 4;
+    if (array.size() <= startIndex) {
+      return;
+    }
+
+    int opacityStops = (array.size() - startIndex) / 2;
+    double[] positions = new double[opacityStops];
+    double[] opacities = new double[opacityStops];
+
+    for (int i = startIndex, j = 0; i < array.size(); i++) {
+      if (i % 2 == 0) {
+        positions[j] = array.get(i);
+      } else {
+        opacities[j] = array.get(i);
+        j++;
+      }
+    }
+
+    for (int i = 0; i < gradientColor.getSize(); i++) {
+      int color = gradientColor.getColors()[i];
+      color = Color.argb(
+          getOpacityAtPosition(gradientColor.getPositions()[i], positions, opacities),
+          Color.red(color),
+          Color.green(color),
+          Color.blue(color)
+      );
+      gradientColor.getColors()[i] = color;
+    }
+  }
+
+  @IntRange(from=0, to=255)
+  private int getOpacityAtPosition(double position, double[] positions, double[] opacities) {
+    for (int i = 1; i < positions.length; i++) {
+      double lastPosition = positions[i - 1];
+      double thisPosition = positions[i];
+      if (positions[i] >= position) {
+        double progress = (position - lastPosition) / (thisPosition - lastPosition);
+        return (int) (255 * MiscUtils.lerp(opacities[i - 1], opacities[i], progress));
+      }
+    }
+    return (int) (255 * opacities[opacities.length - 1]);
+  }
+}
\ No newline at end of file
diff --git a/lottie/src/main/java/com/airbnb/lottie/parser/GradientFillParser.java b/lottie/src/main/java/com/airbnb/lottie/parser/GradientFillParser.java
index 3392632..1fa5a32 100644
--- a/lottie/src/main/java/com/airbnb/lottie/parser/GradientFillParser.java
+++ b/lottie/src/main/java/com/airbnb/lottie/parser/GradientFillParser.java
@@ -40,8 +40,7 @@
                 points = reader.nextInt();
                 break;
               case "k":
-                color = AnimatableGradientColorValue.Factory
-                    .newInstance(reader, composition, points);
+                color = AnimatableValueParser.parseGradientColor(reader, composition, points);
                 break;
               default:
                 reader.skipValue();
@@ -50,16 +49,16 @@
           reader.endObject();
           break;
         case "o":
-          opacity = AnimatableIntegerValue.Factory.newInstance(reader, composition);
+          opacity = AnimatableValueParser.parseInteger(reader, composition);
           break;
         case "t":
           gradientType = reader.nextInt() == 1 ? GradientType.Linear : GradientType.Radial;
           break;
         case "s":
-          startPoint = AnimatablePointValue.Factory.newInstance(reader, composition);
+          startPoint = AnimatableValueParser.parsePoint(reader, composition);
           break;
         case "e":
-          endPoint = AnimatablePointValue.Factory.newInstance(reader, composition);
+          endPoint = AnimatableValueParser.parsePoint(reader, composition);
           break;
         case "r":
           fillType = reader.nextInt() == 1 ? Path.FillType.WINDING : Path.FillType.EVEN_ODD;
diff --git a/lottie/src/main/java/com/airbnb/lottie/parser/GradientStrokeParser.java b/lottie/src/main/java/com/airbnb/lottie/parser/GradientStrokeParser.java
index f612a4c..b182624 100644
--- a/lottie/src/main/java/com/airbnb/lottie/parser/GradientStrokeParser.java
+++ b/lottie/src/main/java/com/airbnb/lottie/parser/GradientStrokeParser.java
@@ -49,8 +49,7 @@
                 points = reader.nextInt();
                 break;
               case "k":
-                color = AnimatableGradientColorValue.Factory
-                    .newInstance(reader, composition, points);
+                color = AnimatableValueParser.parseGradientColor(reader, composition, points);
                 break;
               default:
                 reader.skipValue();
@@ -59,19 +58,19 @@
           reader.endObject();
           break;
         case "o":
-          opacity = AnimatableIntegerValue.Factory.newInstance(reader, composition);
+          opacity = AnimatableValueParser.parseInteger(reader, composition);
           break;
         case "t":
           gradientType = reader.nextInt() == 1 ? GradientType.Linear : GradientType.Radial;
           break;
         case "s":
-          startPoint = AnimatablePointValue.Factory.newInstance(reader, composition);
+          startPoint = AnimatableValueParser.parsePoint(reader, composition);
           break;
         case "e":
-          endPoint = AnimatablePointValue.Factory.newInstance(reader, composition);
+          endPoint = AnimatableValueParser.parsePoint(reader, composition);
           break;
         case "w":
-          width = AnimatableFloatValue.Factory.newInstance(reader, composition);
+          width = AnimatableValueParser.parseFloat(reader, composition);
           break;
         case "lc":
           capType = ShapeStroke.LineCapType.values()[reader.nextInt() - 1];
@@ -91,7 +90,7 @@
                   n = reader.nextString();
                   break;
                 case "v":
-                  val =  AnimatableFloatValue.Factory.newInstance(reader, composition);
+                  val = AnimatableValueParser.parseFloat(reader, composition);
                   break;
                 default:
                   reader.skipValue();
diff --git a/lottie/src/main/java/com/airbnb/lottie/parser/IntegerParser.java b/lottie/src/main/java/com/airbnb/lottie/parser/IntegerParser.java
new file mode 100644
index 0000000..6f30557
--- /dev/null
+++ b/lottie/src/main/java/com/airbnb/lottie/parser/IntegerParser.java
@@ -0,0 +1,17 @@
+package com.airbnb.lottie.parser;
+
+import android.util.JsonReader;
+
+import com.airbnb.lottie.utils.JsonUtils;
+
+import java.io.IOException;
+
+public class IntegerParser implements ValueParser<Integer> {
+  public static final IntegerParser INSTANCE = new IntegerParser();
+
+  private IntegerParser() {}
+
+  @Override public Integer parse(JsonReader reader, float scale) throws IOException {
+    return Math.round(JsonUtils.valueFromObject(reader) * scale);
+  }
+}
diff --git a/lottie/src/main/java/com/airbnb/lottie/parser/KeyframeParser.java b/lottie/src/main/java/com/airbnb/lottie/parser/KeyframeParser.java
index 10f8b01..0dfcba9 100644
--- a/lottie/src/main/java/com/airbnb/lottie/parser/KeyframeParser.java
+++ b/lottie/src/main/java/com/airbnb/lottie/parser/KeyframeParser.java
@@ -10,7 +10,6 @@
 
 import com.airbnb.lottie.LottieComposition;
 import com.airbnb.lottie.animation.Keyframe;
-import com.airbnb.lottie.model.animatable.AnimatableValue;
 import com.airbnb.lottie.utils.JsonUtils;
 import com.airbnb.lottie.utils.MiscUtils;
 import com.airbnb.lottie.utils.Utils;
@@ -54,12 +53,12 @@
   }
 
   public static <T> Keyframe<T> parse(JsonReader reader, LottieComposition composition,
-      float scale, AnimatableValue.Factory<T> valueFactory, boolean animated) throws IOException {
+      float scale, ValueParser<T> valueParser, boolean animated) throws IOException {
 
     if (animated) {
-      return parseKeyframe(composition, reader, scale, valueFactory);
+      return parseKeyframe(composition, reader, scale, valueParser);
     } else {
-      return parseStaticValue(reader, scale, valueFactory);
+      return parseStaticValue(reader, scale, valueParser);
     }
   }
 
@@ -68,7 +67,7 @@
    * a non animated value.
    */
   private static <T> Keyframe<T> parseKeyframe(LottieComposition composition, JsonReader reader,
-      float scale, AnimatableValue.Factory<T> valueFactory) throws IOException {
+      float scale, ValueParser<T> valueParser) throws IOException {
     PointF cp1 = null;
     PointF cp2 = null;
     float startFrame = 0;
@@ -88,10 +87,10 @@
           startFrame = (float) reader.nextDouble();
           break;
         case "s":
-          startValue = valueFactory.valueFromObject(reader, scale);
+          startValue = valueParser.parse(reader, scale);
           break;
         case "e":
-          endValue = valueFactory.valueFromObject(reader, scale);
+          endValue = valueParser.parse(reader, scale);
           break;
         case "o":
           cp1 = JsonUtils.jsonToPoint(reader, scale);
@@ -153,8 +152,8 @@
   }
 
   private static <T> Keyframe<T> parseStaticValue(JsonReader reader,
-      float scale, AnimatableValue.Factory<T> valueFactory) throws IOException {
-    T value = valueFactory.valueFromObject(reader, scale);
+      float scale, ValueParser<T> valueParser) throws IOException {
+    T value = valueParser.parse(reader, scale);
     return new Keyframe<>(value);
   }
 }
diff --git a/lottie/src/main/java/com/airbnb/lottie/parser/KeyframesParser.java b/lottie/src/main/java/com/airbnb/lottie/parser/KeyframesParser.java
index 0197f23..3282ced 100644
--- a/lottie/src/main/java/com/airbnb/lottie/parser/KeyframesParser.java
+++ b/lottie/src/main/java/com/airbnb/lottie/parser/KeyframesParser.java
@@ -5,7 +5,6 @@
 
 import com.airbnb.lottie.LottieComposition;
 import com.airbnb.lottie.animation.Keyframe;
-import com.airbnb.lottie.model.animatable.AnimatableValue;
 
 import java.io.IOException;
 import java.util.ArrayList;
@@ -16,7 +15,7 @@
   private KeyframesParser() {}
 
   public static <T> List<Keyframe<T>> parse(JsonReader reader,
-      LottieComposition composition, float scale, AnimatableValue.Factory<T> valueFactory)
+      LottieComposition composition, float scale, ValueParser<T> valueParser)
       throws IOException {
     List<Keyframe<T>> keyframes = new ArrayList<>();
 
@@ -35,15 +34,15 @@
             if (reader.peek() == JsonToken.NUMBER) {
               // For properties in which the static value is an array of numbers.
               keyframes.add(
-                  KeyframeParser.parse(reader, composition, scale, valueFactory, false));
+                  KeyframeParser.parse(reader, composition, scale, valueParser, false));
             } else {
               while (reader.hasNext()) {
-                keyframes.add(KeyframeParser.parse(reader, composition, scale, valueFactory, true));
+                keyframes.add(KeyframeParser.parse(reader, composition, scale, valueParser, true));
               }
             }
             reader.endArray();
           } else {
-            keyframes.add(KeyframeParser.parse(reader, composition, scale, valueFactory, false));
+            keyframes.add(KeyframeParser.parse(reader, composition, scale, valueParser, false));
           }
           break;
         default:
diff --git a/lottie/src/main/java/com/airbnb/lottie/parser/LayerParser.java b/lottie/src/main/java/com/airbnb/lottie/parser/LayerParser.java
index 367c3aa..ca4f770 100644
--- a/lottie/src/main/java/com/airbnb/lottie/parser/LayerParser.java
+++ b/lottie/src/main/java/com/airbnb/lottie/parser/LayerParser.java
@@ -120,7 +120,7 @@
           while (reader.hasNext()) {
             switch (reader.nextName()) {
               case "d":
-                text = AnimatableTextFrame.Factory.newInstance(reader, composition);
+                text = AnimatableValueParser.parseDocumentData(reader, composition);
                 break;
               case "a":
                 reader.beginArray();
@@ -179,7 +179,7 @@
           outFrame = (float) reader.nextDouble();
           break;
         case "tm":
-          timeRemapping = AnimatableFloatValue.Factory.newInstance(reader, composition, false);
+          timeRemapping = AnimatableValueParser.parseFloat(reader, composition, false);
           break;
         case "cl":
           cl = reader.nextString();
diff --git a/lottie/src/main/java/com/airbnb/lottie/parser/PathParser.java b/lottie/src/main/java/com/airbnb/lottie/parser/PathParser.java
new file mode 100644
index 0000000..3ac33a2
--- /dev/null
+++ b/lottie/src/main/java/com/airbnb/lottie/parser/PathParser.java
@@ -0,0 +1,18 @@
+package com.airbnb.lottie.parser;
+
+import android.graphics.PointF;
+import android.util.JsonReader;
+
+import com.airbnb.lottie.utils.JsonUtils;
+
+import java.io.IOException;
+
+public class PathParser implements ValueParser<PointF> {
+  public static final PathParser INSTANCE = new PathParser();
+
+  private PathParser() {}
+
+  @Override public PointF parse(JsonReader reader, float scale) throws IOException {
+    return JsonUtils.jsonToPoint(reader, scale);
+  }
+}
diff --git a/lottie/src/main/java/com/airbnb/lottie/model/PointFFactory.java b/lottie/src/main/java/com/airbnb/lottie/parser/PointFParser.java
similarity index 72%
rename from lottie/src/main/java/com/airbnb/lottie/model/PointFFactory.java
rename to lottie/src/main/java/com/airbnb/lottie/parser/PointFParser.java
index 85f63c0..7bd19c4 100644
--- a/lottie/src/main/java/com/airbnb/lottie/model/PointFFactory.java
+++ b/lottie/src/main/java/com/airbnb/lottie/parser/PointFParser.java
@@ -1,21 +1,19 @@
-package com.airbnb.lottie.model;
+package com.airbnb.lottie.parser;
 
 import android.graphics.PointF;
 import android.util.JsonReader;
 import android.util.JsonToken;
 
-import com.airbnb.lottie.model.animatable.AnimatableValue;
 import com.airbnb.lottie.utils.JsonUtils;
 
 import java.io.IOException;
 
-public class PointFFactory implements AnimatableValue.Factory<PointF> {
-  public static final PointFFactory INSTANCE = new PointFFactory();
+public class PointFParser implements ValueParser<PointF> {
+  public static final PointFParser INSTANCE = new PointFParser();
 
-  private PointFFactory() {
-  }
+  private PointFParser() {}
 
-  @Override public PointF valueFromObject(JsonReader reader, float scale) throws IOException {
+  @Override public PointF parse(JsonReader reader, float scale) throws IOException {
     JsonToken token = reader.peek();
     if (token == JsonToken.BEGIN_ARRAY) {
       return JsonUtils.jsonToPoint(reader, scale);
diff --git a/lottie/src/main/java/com/airbnb/lottie/parser/PolystarShapeParser.java b/lottie/src/main/java/com/airbnb/lottie/parser/PolystarShapeParser.java
index 2855346..376a8a9 100644
--- a/lottie/src/main/java/com/airbnb/lottie/parser/PolystarShapeParser.java
+++ b/lottie/src/main/java/com/airbnb/lottie/parser/PolystarShapeParser.java
@@ -36,26 +36,26 @@
           type = PolystarShape.Type.forValue(reader.nextInt());
           break;
         case "pt":
-          points = AnimatableFloatValue.Factory.newInstance(reader, composition, false);
+          points = AnimatableValueParser.parseFloat(reader, composition, false);
           break;
         case "p":
           position = AnimatablePathValue
               .createAnimatablePathOrSplitDimensionPath(reader, composition);
           break;
         case "r":
-          rotation = AnimatableFloatValue.Factory.newInstance(reader, composition, false);
+          rotation = AnimatableValueParser.parseFloat(reader, composition, false);
           break;
         case "or":
-          outerRadius = AnimatableFloatValue.Factory.newInstance(reader, composition);
+          outerRadius = AnimatableValueParser.parseFloat(reader, composition);
           break;
         case "os":
-          outerRoundedness = AnimatableFloatValue.Factory.newInstance(reader, composition, false);
+          outerRoundedness = AnimatableValueParser.parseFloat(reader, composition, false);
           break;
         case "ir":
-          innerRadius = AnimatableFloatValue.Factory.newInstance(reader, composition);
+          innerRadius = AnimatableValueParser.parseFloat(reader, composition);
           break;
         case "is":
-          innerRoundedness = AnimatableFloatValue.Factory.newInstance(reader, composition, false);
+          innerRoundedness = AnimatableValueParser.parseFloat(reader, composition, false);
           break;
         default:
           reader.skipValue();
diff --git a/lottie/src/main/java/com/airbnb/lottie/parser/RectangleShapeParser.java b/lottie/src/main/java/com/airbnb/lottie/parser/RectangleShapeParser.java
index e077e06..487d3c5 100644
--- a/lottie/src/main/java/com/airbnb/lottie/parser/RectangleShapeParser.java
+++ b/lottie/src/main/java/com/airbnb/lottie/parser/RectangleShapeParser.java
@@ -33,10 +33,10 @@
               AnimatablePathValue.createAnimatablePathOrSplitDimensionPath(reader, composition);
           break;
         case "s":
-          size = AnimatablePointValue.Factory.newInstance(reader, composition);
+          size = AnimatableValueParser.parsePoint(reader, composition);
           break;
         case "r":
-          roundedness = AnimatableFloatValue.Factory.newInstance(reader, composition);
+          roundedness = AnimatableValueParser.parseFloat(reader, composition);
           break;
         default:
           reader.skipValue();
diff --git a/lottie/src/main/java/com/airbnb/lottie/parser/RepeaterParser.java b/lottie/src/main/java/com/airbnb/lottie/parser/RepeaterParser.java
index 7664812..68b9ca8 100644
--- a/lottie/src/main/java/com/airbnb/lottie/parser/RepeaterParser.java
+++ b/lottie/src/main/java/com/airbnb/lottie/parser/RepeaterParser.java
@@ -26,10 +26,10 @@
           name = reader.nextString();
           break;
         case "c":
-          copies = AnimatableFloatValue.Factory.newInstance(reader, composition, false);
+          copies = AnimatableValueParser.parseFloat(reader, composition, false);
           break;
         case "o":
-          offset = AnimatableFloatValue.Factory.newInstance(reader, composition, false);
+          offset = AnimatableValueParser.parseFloat(reader, composition, false);
           break;
         case "tr":
           transform = AnimatableTransform.Factory.newInstance(reader, composition);
diff --git a/lottie/src/main/java/com/airbnb/lottie/parser/ScaleXYParser.java b/lottie/src/main/java/com/airbnb/lottie/parser/ScaleXYParser.java
new file mode 100644
index 0000000..b0bdd9d
--- /dev/null
+++ b/lottie/src/main/java/com/airbnb/lottie/parser/ScaleXYParser.java
@@ -0,0 +1,31 @@
+package com.airbnb.lottie.parser;
+
+import android.util.JsonReader;
+import android.util.JsonToken;
+
+import com.airbnb.lottie.value.ScaleXY;
+
+import java.io.IOException;
+
+public class ScaleXYParser implements ValueParser<ScaleXY> {
+  public static final ScaleXYParser INSTANCE = new ScaleXYParser();
+
+  private ScaleXYParser() {
+  }
+
+  @Override public ScaleXY parse(JsonReader reader, float scale) throws IOException {
+    boolean isArray = reader.peek() == JsonToken.BEGIN_ARRAY;
+    if (isArray) {
+      reader.beginArray();
+    }
+    float sx = (float) reader.nextDouble();
+    float sy = (float) reader.nextDouble();
+    while (reader.hasNext()) {
+      reader.skipValue();
+    }
+    if (isArray) {
+      reader.endArray();
+    }
+    return new ScaleXY(sx / 100f * scale, sy / 100f * scale);
+  }
+}
diff --git a/lottie/src/main/java/com/airbnb/lottie/parser/ShapeDataParser.java b/lottie/src/main/java/com/airbnb/lottie/parser/ShapeDataParser.java
new file mode 100644
index 0000000..1abe7a1
--- /dev/null
+++ b/lottie/src/main/java/com/airbnb/lottie/parser/ShapeDataParser.java
@@ -0,0 +1,94 @@
+package com.airbnb.lottie.parser;
+
+import android.graphics.PointF;
+import android.util.JsonReader;
+import android.util.JsonToken;
+
+import com.airbnb.lottie.model.CubicCurveData;
+import com.airbnb.lottie.model.content.ShapeData;
+import com.airbnb.lottie.utils.JsonUtils;
+import com.airbnb.lottie.utils.MiscUtils;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class ShapeDataParser implements ValueParser<ShapeData> {
+  public static final ShapeDataParser INSTANCE = new ShapeDataParser();
+
+  private ShapeDataParser() {}
+
+  @Override public ShapeData parse(JsonReader reader, float scale) throws IOException {
+    // Sometimes the points data is in a array of length 1. Sometimes the data is at the top
+    // level.
+    if (reader.peek() == JsonToken.BEGIN_ARRAY) {
+      reader.beginArray();
+    }
+
+    boolean closed = false;
+    List<PointF> pointsArray = null;
+    List<PointF> inTangents = null;
+    List<PointF> outTangents = null;
+    reader.beginObject();
+
+    while (reader.hasNext()) {
+      switch (reader.nextName()) {
+        case "c":
+          closed = reader.nextBoolean();
+          break;
+        case "v":
+          pointsArray =  JsonUtils.jsonToPoints(reader, scale);
+          break;
+        case "i":
+          inTangents =  JsonUtils.jsonToPoints(reader, scale);
+          break;
+        case "o":
+          outTangents =  JsonUtils.jsonToPoints(reader, scale);
+          break;
+      }
+    }
+
+    reader.endObject();
+
+    if (reader.peek() == JsonToken.END_ARRAY) {
+      reader.endArray();
+    }
+
+    if (pointsArray == null || inTangents == null || outTangents == null) {
+      throw new IllegalArgumentException("Shape data was missing information.");
+    }
+
+    if (pointsArray.isEmpty()) {
+      return new ShapeData(new PointF(), false, Collections.<CubicCurveData>emptyList());
+    }
+
+    int length = pointsArray.size();
+    PointF vertex = pointsArray.get(0);
+    PointF initialPoint = vertex;
+    List<CubicCurveData> curves = new ArrayList<>(length);
+
+    for (int i = 1; i < length; i++) {
+      vertex = pointsArray.get(i);
+      PointF previousVertex = pointsArray.get(i - 1);
+      PointF cp1 = outTangents.get(i - 1);
+      PointF cp2 = inTangents.get(i);
+      PointF shapeCp1 = MiscUtils.addPoints(previousVertex, cp1);
+      PointF shapeCp2 = MiscUtils.addPoints(vertex, cp2);
+      curves.add(new CubicCurveData(shapeCp1, shapeCp2, vertex));
+    }
+
+    if (closed) {
+      vertex = pointsArray.get(0);
+      PointF previousVertex = pointsArray.get(length - 1);
+      PointF cp1 = outTangents.get(length - 1);
+      PointF cp2 = inTangents.get(0);
+
+      PointF shapeCp1 = MiscUtils.addPoints(previousVertex, cp1);
+      PointF shapeCp2 = MiscUtils.addPoints(vertex, cp2);
+
+      curves.add(new CubicCurveData(shapeCp1, shapeCp2, vertex));
+    }
+    return new ShapeData(initialPoint, closed, curves);
+  }
+}
diff --git a/lottie/src/main/java/com/airbnb/lottie/parser/ShapeFillParser.java b/lottie/src/main/java/com/airbnb/lottie/parser/ShapeFillParser.java
index dedce47..f530622 100644
--- a/lottie/src/main/java/com/airbnb/lottie/parser/ShapeFillParser.java
+++ b/lottie/src/main/java/com/airbnb/lottie/parser/ShapeFillParser.java
@@ -28,10 +28,10 @@
           name = reader.nextString();
           break;
         case "c":
-          color = AnimatableColorValue.Factory.newInstance(reader, composition);
+          color = AnimatableValueParser.parseColor(reader, composition);
           break;
         case "o":
-          opacity = AnimatableIntegerValue.Factory.newInstance(reader, composition);
+          opacity = AnimatableValueParser.parseInteger(reader, composition);
           break;
         case "fillEnabled":
           fillEnabled = reader.nextBoolean();
diff --git a/lottie/src/main/java/com/airbnb/lottie/parser/ShapePathParser.java b/lottie/src/main/java/com/airbnb/lottie/parser/ShapePathParser.java
index 37b1fca..e3f7083 100644
--- a/lottie/src/main/java/com/airbnb/lottie/parser/ShapePathParser.java
+++ b/lottie/src/main/java/com/airbnb/lottie/parser/ShapePathParser.java
@@ -27,7 +27,7 @@
           ind = reader.nextInt();
           break;
         case "ks":
-          shape = AnimatableShapeValue.Factory.newInstance(reader, composition);
+          shape = AnimatableValueParser.parseShapeData(reader, composition);
           break;
         default:
           reader.skipValue();
diff --git a/lottie/src/main/java/com/airbnb/lottie/parser/ShapeStrokeParser.java b/lottie/src/main/java/com/airbnb/lottie/parser/ShapeStrokeParser.java
index 46b6cdc..59a6181 100644
--- a/lottie/src/main/java/com/airbnb/lottie/parser/ShapeStrokeParser.java
+++ b/lottie/src/main/java/com/airbnb/lottie/parser/ShapeStrokeParser.java
@@ -34,13 +34,13 @@
           name = reader.nextString();
           break;
         case "c":
-          color = AnimatableColorValue.Factory.newInstance(reader, composition);
+          color = AnimatableValueParser.parseColor(reader, composition);
           break;
         case "w":
-          width = AnimatableFloatValue.Factory.newInstance(reader, composition);
+          width = AnimatableValueParser.parseFloat(reader, composition);
           break;
         case "o":
-          opacity = AnimatableIntegerValue.Factory.newInstance(reader, composition);
+          opacity = AnimatableValueParser.parseInteger(reader, composition);
           break;
         case "lc":
           capType = ShapeStroke.LineCapType.values()[reader.nextInt() - 1];
@@ -61,7 +61,7 @@
                   n = reader.nextString();
                   break;
                 case "v":
-                  val = AnimatableFloatValue.Factory.newInstance(reader, composition);
+                  val = AnimatableValueParser.parseFloat(reader, composition);
                   break;
                 default:
                   reader.skipValue();
diff --git a/lottie/src/main/java/com/airbnb/lottie/parser/ShapeTrimPathParser.java b/lottie/src/main/java/com/airbnb/lottie/parser/ShapeTrimPathParser.java
index e88a429..ff449d6 100644
--- a/lottie/src/main/java/com/airbnb/lottie/parser/ShapeTrimPathParser.java
+++ b/lottie/src/main/java/com/airbnb/lottie/parser/ShapeTrimPathParser.java
@@ -23,13 +23,13 @@
     while (reader.hasNext()) {
       switch (reader.nextName()) {
         case "s":
-          start = AnimatableFloatValue.Factory.newInstance(reader, composition, false);
+          start = AnimatableValueParser.parseFloat(reader, composition, false);
           break;
         case "e":
-          end = AnimatableFloatValue.Factory.newInstance(reader, composition, false);
+          end = AnimatableValueParser.parseFloat(reader, composition, false);
           break;
         case "o":
-          offset = AnimatableFloatValue.Factory.newInstance(reader, composition, false);
+          offset = AnimatableValueParser.parseFloat(reader, composition, false);
           break;
         case "nm":
           name = reader.nextString();
diff --git a/lottie/src/main/java/com/airbnb/lottie/parser/ValueParser.java b/lottie/src/main/java/com/airbnb/lottie/parser/ValueParser.java
new file mode 100644
index 0000000..3e61e2d
--- /dev/null
+++ b/lottie/src/main/java/com/airbnb/lottie/parser/ValueParser.java
@@ -0,0 +1,9 @@
+package com.airbnb.lottie.parser;
+
+import android.util.JsonReader;
+
+import java.io.IOException;
+
+public interface ValueParser<V> {
+  V parse(JsonReader reader, float scale) throws IOException;
+}
diff --git a/lottie/src/main/java/com/airbnb/lottie/value/ScaleXY.java b/lottie/src/main/java/com/airbnb/lottie/value/ScaleXY.java
index 069545f..4d8c348 100644
--- a/lottie/src/main/java/com/airbnb/lottie/value/ScaleXY.java
+++ b/lottie/src/main/java/com/airbnb/lottie/value/ScaleXY.java
@@ -1,12 +1,5 @@
 package com.airbnb.lottie.value;
 
-import android.util.JsonReader;
-import android.util.JsonToken;
-
-import com.airbnb.lottie.model.animatable.AnimatableValue;
-
-import java.io.IOException;
-
 public class ScaleXY {
   private final float scaleX;
   private final float scaleY;
@@ -31,27 +24,4 @@
   @Override public String toString() {
     return getScaleX() + "x" + getScaleY();
   }
-
-  public static class Factory implements AnimatableValue.Factory<ScaleXY> {
-    public static final Factory INSTANCE = new Factory();
-
-    private Factory() {
-    }
-
-    @Override public ScaleXY valueFromObject(JsonReader reader, float scale) throws IOException {
-      boolean isArray = reader.peek() == JsonToken.BEGIN_ARRAY;
-      if (isArray) {
-        reader.beginArray();
-      }
-      float sx = (float) reader.nextDouble();
-      float sy = (float) reader.nextDouble();
-      while (reader.hasNext()) {
-        reader.skipValue();
-      }
-      if (isArray) {
-        reader.endArray();
-      }
-      return new ScaleXY(sx / 100f * scale, sy / 100f * scale);
-    }
-  }
 }