Moved Layer parsing to Parser classes
diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieDrawable.java b/lottie/src/main/java/com/airbnb/lottie/LottieDrawable.java
index 209cd72..5a4d8c9 100644
--- a/lottie/src/main/java/com/airbnb/lottie/LottieDrawable.java
+++ b/lottie/src/main/java/com/airbnb/lottie/LottieDrawable.java
@@ -24,7 +24,7 @@
 import com.airbnb.lottie.manager.ImageAssetManager;
 import com.airbnb.lottie.model.KeyPath;
 import com.airbnb.lottie.model.layer.CompositionLayer;
-import com.airbnb.lottie.model.layer.Layer;
+import com.airbnb.lottie.parser.LayerParser;
 import com.airbnb.lottie.utils.LottieValueAnimator;
 import com.airbnb.lottie.value.LottieValueCallback;
 
@@ -220,7 +220,7 @@
 
   private void buildCompositionLayer() {
     compositionLayer = new CompositionLayer(
-        this, Layer.Factory.newInstance(composition), composition.getLayers(), composition);
+        this, LayerParser.parse(composition), composition.getLayers(), composition);
   }
 
   public void clearComposition() {
diff --git a/lottie/src/main/java/com/airbnb/lottie/model/layer/Layer.java b/lottie/src/main/java/com/airbnb/lottie/model/layer/Layer.java
index 989b925..6f30be3 100644
--- a/lottie/src/main/java/com/airbnb/lottie/model/layer/Layer.java
+++ b/lottie/src/main/java/com/airbnb/lottie/model/layer/Layer.java
@@ -1,9 +1,6 @@
 package com.airbnb.lottie.model.layer;
 
-import android.graphics.Color;
-import android.graphics.Rect;
 import android.support.annotation.Nullable;
-import android.util.JsonReader;
 
 import com.airbnb.lottie.LottieComposition;
 import com.airbnb.lottie.animation.Keyframe;
@@ -13,12 +10,7 @@
 import com.airbnb.lottie.model.animatable.AnimatableTransform;
 import com.airbnb.lottie.model.content.ContentModel;
 import com.airbnb.lottie.model.content.Mask;
-import com.airbnb.lottie.model.content.ShapeGroup;
-import com.airbnb.lottie.utils.Utils;
 
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
 
@@ -34,7 +26,7 @@
     Unknown
   }
 
-  enum MatteType {
+  public enum MatteType {
     None,
     Add,
     Invert,
@@ -63,7 +55,7 @@
   private final List<Keyframe<Float>> inOutKeyframes;
   private final MatteType matteType;
 
-  private Layer(List<ContentModel> shapes, LottieComposition composition, String layerName, long layerId,
+  public Layer(List<ContentModel> shapes, LottieComposition composition, String layerName, long layerId,
       LayerType layerType, long parentId, @Nullable String refId, List<Mask> masks,
       AnimatableTransform transform, int solidWidth, int solidHeight, int solidColor,
       float timeStretch, float startFrame, int preCompWidth, int preCompHeight,
@@ -209,209 +201,4 @@
     }
     return sb.toString();
   }
-
-  public static class Factory {
-    private Factory() {
-    }
-
-    public static Layer newInstance(LottieComposition composition) {
-      Rect bounds = composition.getBounds();
-      return new Layer(
-          Collections.<ContentModel>emptyList(), composition, "__container", -1,
-          LayerType.PreComp, -1, null, Collections.<Mask>emptyList(),
-          AnimatableTransform.Factory.newInstance(), 0, 0, 0, 0, 0,
-          bounds.width(), bounds.height(), null, null, Collections.<Keyframe<Float>>emptyList(),
-          MatteType.None, null);
-    }
-
-    public static Layer newInstance(
-        JsonReader reader, LottieComposition composition) throws IOException{
-      String layerName = null;
-      LayerType layerType = null;
-      String refId = null;
-      long layerId = 0;
-      int solidWidth = 0;
-      int solidHeight = 0;
-      int solidColor = 0;
-      int preCompWidth = 0;
-      int preCompHeight = 0;
-      long parentId = -1;
-      float timeStretch = 1f;
-      float startFrame = 0f;
-      float inFrame = 0f;
-      float outFrame = 0f;
-      String cl = null;
-
-      MatteType matteType = MatteType.None;
-      AnimatableTransform transform = null;
-      AnimatableTextFrame text = null;
-      AnimatableTextProperties textProperties = null;
-      AnimatableFloatValue timeRemapping = null;
-
-      List<Mask> masks = new ArrayList<>();
-      List<ContentModel> shapes = new ArrayList<>();
-
-      reader.beginObject();
-      while (reader.hasNext()) {
-        switch (reader.nextName()) {
-          case "nm":
-            layerName = reader.nextString();
-            break;
-          case "ind":
-            layerId = reader.nextInt();
-            break;
-          case "refId":
-            refId = reader.nextString();
-            break;
-          case "ty":
-            int layerTypeInt = reader.nextInt();
-            if (layerTypeInt < LayerType.Unknown.ordinal()) {
-              layerType = LayerType.values()[layerTypeInt];
-            } else {
-              layerType = LayerType.Unknown;
-            }
-            break;
-          case "parent":
-            parentId = reader.nextInt();
-            break;
-          case "sw":
-            solidWidth = (int) (reader.nextInt() * Utils.dpScale());
-            break;
-          case "sh":
-            solidHeight = (int) (reader.nextInt() * Utils.dpScale());
-            break;
-          case "sc":
-            solidColor = Color.parseColor(reader.nextString());
-            break;
-          case "ks":
-            transform = AnimatableTransform.Factory.newInstance(reader, composition);
-            break;
-          case "tt":
-            matteType = MatteType.values()[reader.nextInt()];
-            break;
-          case "masksProperties":
-            reader.beginArray();
-            while (reader.hasNext()) {
-              masks.add(Mask.Factory.newMask(reader, composition));
-            }
-            reader.endArray();
-            break;
-          case "shapes":
-            reader.beginArray();
-            while (reader.hasNext()) {
-              ContentModel shape = ShapeGroup.shapeItemWithJson(reader, composition);
-              if (shape != null) {
-                shapes.add(shape);
-              }
-            }
-            reader.endArray();
-            break;
-          case "t":
-            reader.beginObject();
-            while (reader.hasNext()) {
-              switch (reader.nextName()) {
-                case "d":
-                  text = AnimatableTextFrame.Factory.newInstance(reader, composition);
-                  break;
-                case "a":
-                  reader.beginArray();
-                  if (reader.hasNext()) {
-                    textProperties = AnimatableTextProperties.Factory.newInstance(reader, composition);
-                  }
-                  while (reader.hasNext()) {
-                    reader.skipValue();
-                  }
-                  reader.endArray();
-                  break;
-                default:
-                  reader.skipValue();
-              }
-            }
-            reader.endObject();
-            break;
-          case "ef":
-            reader.beginArray();
-            List<String> effectNames = new ArrayList<>();
-            while (reader.hasNext()) {
-              reader.beginObject();
-              while (reader.hasNext()) {
-                switch (reader.nextName()) {
-                  case "nm":
-                    effectNames.add(reader.nextString());
-                    break;
-                  default:
-                    reader.skipValue();
-
-                }
-              }
-              reader.endObject();
-            }
-            reader.endArray();
-            composition.addWarning("Lottie doesn't support layer effects. If you are using them for " +
-                " fills, strokes, trim paths etc. then try adding them directly as contents " +
-                " in your shape. Found: " + effectNames);
-            break;
-          case "sr":
-            timeStretch = (float) reader.nextDouble();
-            break;
-          case "st":
-            startFrame = (float) reader.nextDouble();
-            break;
-          case "w":
-            preCompWidth = (int) (reader.nextInt() * Utils.dpScale());
-            break;
-          case "h":
-            preCompHeight = (int) (reader.nextInt() * Utils.dpScale());
-            break;
-          case "ip":
-            inFrame = (float) reader.nextDouble();
-            break;
-          case "op":
-            outFrame = (float) reader.nextDouble();
-            break;
-          case "tm":
-            timeRemapping = AnimatableFloatValue.Factory.newInstance(reader, composition, false);
-            break;
-          case "cl":
-            cl = reader.nextString();
-            break;
-          default:
-            reader.skipValue();
-        }
-      }
-      reader.endObject();
-
-      // Bodymovin pre-scales the in frame and out frame by the time stretch. However, that will
-      // cause the stretch to be double counted since the in out animation gets treated the same
-      // as all other animations and will have stretch applied to it again.
-      inFrame /= timeStretch;
-      outFrame /= timeStretch;
-
-      List<Keyframe<Float>> inOutKeyframes = new ArrayList<>();
-      // Before the in frame
-      if (inFrame > 0) {
-        Keyframe<Float> preKeyframe = new Keyframe<>(composition, 0f, 0f, null, 0f, inFrame);
-        inOutKeyframes.add(preKeyframe);
-      }
-
-      // The + 1 is because the animation should be visible on the out frame itself.
-      outFrame = (outFrame > 0 ? outFrame : composition.getEndFrame()) + 1;
-      Keyframe<Float> visibleKeyframe =
-          new Keyframe<>(composition, 1f, 1f, null, inFrame, outFrame);
-      inOutKeyframes.add(visibleKeyframe);
-
-      Keyframe<Float> outKeyframe = new Keyframe<>(
-          composition, 0f, 0f, null, outFrame, Float.MAX_VALUE);
-      inOutKeyframes.add(outKeyframe);
-
-      if (layerName.endsWith(".ai") || "ai".equals(cl)) {
-        composition.addWarning("Convert your Illustrator layers to shape layers.");
-      }
-
-      return new Layer(shapes, composition, layerName, layerId, layerType, parentId, refId,
-          masks, transform, solidWidth, solidHeight, solidColor, timeStretch, startFrame,
-          preCompWidth, preCompHeight, text, textProperties, inOutKeyframes, matteType,
-          timeRemapping);
-    }
-  }
 }
diff --git a/lottie/src/main/java/com/airbnb/lottie/parser/LayerParser.java b/lottie/src/main/java/com/airbnb/lottie/parser/LayerParser.java
new file mode 100644
index 0000000..504dbd8
--- /dev/null
+++ b/lottie/src/main/java/com/airbnb/lottie/parser/LayerParser.java
@@ -0,0 +1,224 @@
+package com.airbnb.lottie.parser;
+
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.util.JsonReader;
+
+import com.airbnb.lottie.LottieComposition;
+import com.airbnb.lottie.animation.Keyframe;
+import com.airbnb.lottie.model.animatable.AnimatableFloatValue;
+import com.airbnb.lottie.model.animatable.AnimatableTextFrame;
+import com.airbnb.lottie.model.animatable.AnimatableTextProperties;
+import com.airbnb.lottie.model.animatable.AnimatableTransform;
+import com.airbnb.lottie.model.content.ContentModel;
+import com.airbnb.lottie.model.content.Mask;
+import com.airbnb.lottie.model.content.ShapeGroup;
+import com.airbnb.lottie.model.layer.Layer;
+import com.airbnb.lottie.utils.Utils;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class LayerParser {
+
+  public static Layer parse(LottieComposition composition) {
+    Rect bounds = composition.getBounds();
+    return new Layer(
+        Collections.<ContentModel>emptyList(), composition, "__container", -1,
+        Layer.LayerType.PreComp, -1, null, Collections.<Mask>emptyList(),
+        AnimatableTransform.Factory.newInstance(), 0, 0, 0, 0, 0,
+        bounds.width(), bounds.height(), null, null, Collections.<Keyframe<Float>>emptyList(),
+        Layer.MatteType.None, null);
+  }
+
+  public static Layer parse(JsonReader reader, LottieComposition composition) throws IOException {
+    String layerName = null;
+    Layer.LayerType layerType = null;
+    String refId = null;
+    long layerId = 0;
+    int solidWidth = 0;
+    int solidHeight = 0;
+    int solidColor = 0;
+    int preCompWidth = 0;
+    int preCompHeight = 0;
+    long parentId = -1;
+    float timeStretch = 1f;
+    float startFrame = 0f;
+    float inFrame = 0f;
+    float outFrame = 0f;
+    String cl = null;
+
+    Layer.MatteType matteType = Layer.MatteType.None;
+    AnimatableTransform transform = null;
+    AnimatableTextFrame text = null;
+    AnimatableTextProperties textProperties = null;
+    AnimatableFloatValue timeRemapping = null;
+
+    List<Mask> masks = new ArrayList<>();
+    List<ContentModel> shapes = new ArrayList<>();
+
+    reader.beginObject();
+    while (reader.hasNext()) {
+      switch (reader.nextName()) {
+        case "nm":
+          layerName = reader.nextString();
+          break;
+        case "ind":
+          layerId = reader.nextInt();
+          break;
+        case "refId":
+          refId = reader.nextString();
+          break;
+        case "ty":
+          int layerTypeInt = reader.nextInt();
+          if (layerTypeInt < Layer.LayerType.Unknown.ordinal()) {
+            layerType = Layer.LayerType.values()[layerTypeInt];
+          } else {
+            layerType = Layer.LayerType.Unknown;
+          }
+          break;
+        case "parent":
+          parentId = reader.nextInt();
+          break;
+        case "sw":
+          solidWidth = (int) (reader.nextInt() * Utils.dpScale());
+          break;
+        case "sh":
+          solidHeight = (int) (reader.nextInt() * Utils.dpScale());
+          break;
+        case "sc":
+          solidColor = Color.parseColor(reader.nextString());
+          break;
+        case "ks":
+          transform = AnimatableTransform.Factory.newInstance(reader, composition);
+          break;
+        case "tt":
+          matteType = Layer.MatteType.values()[reader.nextInt()];
+          break;
+        case "masksProperties":
+          reader.beginArray();
+          while (reader.hasNext()) {
+            masks.add(Mask.Factory.newMask(reader, composition));
+          }
+          reader.endArray();
+          break;
+        case "shapes":
+          reader.beginArray();
+          while (reader.hasNext()) {
+            ContentModel shape = ShapeGroup.shapeItemWithJson(reader, composition);
+            if (shape != null) {
+              shapes.add(shape);
+            }
+          }
+          reader.endArray();
+          break;
+        case "t":
+          reader.beginObject();
+          while (reader.hasNext()) {
+            switch (reader.nextName()) {
+              case "d":
+                text = AnimatableTextFrame.Factory.newInstance(reader, composition);
+                break;
+              case "a":
+                reader.beginArray();
+                if (reader.hasNext()) {
+                  textProperties = AnimatableTextProperties.Factory.newInstance(reader, composition);
+                }
+                while (reader.hasNext()) {
+                  reader.skipValue();
+                }
+                reader.endArray();
+                break;
+              default:
+                reader.skipValue();
+            }
+          }
+          reader.endObject();
+          break;
+        case "ef":
+          reader.beginArray();
+          List<String> effectNames = new ArrayList<>();
+          while (reader.hasNext()) {
+            reader.beginObject();
+            while (reader.hasNext()) {
+              switch (reader.nextName()) {
+                case "nm":
+                  effectNames.add(reader.nextString());
+                  break;
+                default:
+                  reader.skipValue();
+
+              }
+            }
+            reader.endObject();
+          }
+          reader.endArray();
+          composition.addWarning("Lottie doesn't support layer effects. If you are using them for " +
+              " fills, strokes, trim paths etc. then try adding them directly as contents " +
+              " in your shape. Found: " + effectNames);
+          break;
+        case "sr":
+          timeStretch = (float) reader.nextDouble();
+          break;
+        case "st":
+          startFrame = (float) reader.nextDouble();
+          break;
+        case "w":
+          preCompWidth = (int) (reader.nextInt() * Utils.dpScale());
+          break;
+        case "h":
+          preCompHeight = (int) (reader.nextInt() * Utils.dpScale());
+          break;
+        case "ip":
+          inFrame = (float) reader.nextDouble();
+          break;
+        case "op":
+          outFrame = (float) reader.nextDouble();
+          break;
+        case "tm":
+          timeRemapping = AnimatableFloatValue.Factory.newInstance(reader, composition, false);
+          break;
+        case "cl":
+          cl = reader.nextString();
+          break;
+        default:
+          reader.skipValue();
+      }
+    }
+    reader.endObject();
+
+    // Bodymovin pre-scales the in frame and out frame by the time stretch. However, that will
+    // cause the stretch to be double counted since the in out animation gets treated the same
+    // as all other animations and will have stretch applied to it again.
+    inFrame /= timeStretch;
+    outFrame /= timeStretch;
+
+    List<Keyframe<Float>> inOutKeyframes = new ArrayList<>();
+    // Before the in frame
+    if (inFrame > 0) {
+      Keyframe<Float> preKeyframe = new Keyframe<>(composition, 0f, 0f, null, 0f, inFrame);
+      inOutKeyframes.add(preKeyframe);
+    }
+
+    // The + 1 is because the animation should be visible on the out frame itself.
+    outFrame = (outFrame > 0 ? outFrame : composition.getEndFrame()) + 1;
+    Keyframe<Float> visibleKeyframe =
+        new Keyframe<>(composition, 1f, 1f, null, inFrame, outFrame);
+    inOutKeyframes.add(visibleKeyframe);
+
+    Keyframe<Float> outKeyframe = new Keyframe<>(
+        composition, 0f, 0f, null, outFrame, Float.MAX_VALUE);
+    inOutKeyframes.add(outKeyframe);
+
+    if (layerName.endsWith(".ai") || "ai".equals(cl)) {
+      composition.addWarning("Convert your Illustrator layers to shape layers.");
+    }
+
+    return new Layer(shapes, composition, layerName, layerId, layerType, parentId, refId,
+        masks, transform, solidWidth, solidHeight, solidColor, timeStretch, startFrame,
+        preCompWidth, preCompHeight, text, textProperties, inOutKeyframes, matteType,
+        timeRemapping);
+  }
+}
diff --git a/lottie/src/main/java/com/airbnb/lottie/parser/LottieCompositionParser.java b/lottie/src/main/java/com/airbnb/lottie/parser/LottieCompositionParser.java
index 61f6df2..e44eb34 100644
--- a/lottie/src/main/java/com/airbnb/lottie/parser/LottieCompositionParser.java
+++ b/lottie/src/main/java/com/airbnb/lottie/parser/LottieCompositionParser.java
@@ -100,7 +100,7 @@
     int imageCount = 0;
     reader.beginArray();
     while (reader.hasNext()) {
-      Layer layer = Layer.Factory.newInstance(reader, composition);
+      Layer layer = LayerParser.parse(reader, composition);
       if (layer.getLayerType() == Layer.LayerType.Image) {
         imageCount++;
       }
@@ -138,7 +138,7 @@
           case "layers":
             reader.beginArray();
             while (reader.hasNext()) {
-              Layer layer = Layer.Factory.newInstance(reader, composition);
+              Layer layer = LayerParser.parse(reader, composition);
               layerMap.put(layer.getId(), layer);
               layers.add(layer);
             }