| package com.airbnb.lottie; |
| |
| import android.graphics.Path; |
| import android.graphics.PointF; |
| |
| import org.json.JSONArray; |
| import org.json.JSONException; |
| import org.json.JSONObject; |
| |
| class AnimatableShapeValue extends BaseAnimatableValue<ShapeData, Path> { |
| private final Path convertTypePath = new Path(); |
| private boolean closed; |
| |
| AnimatableShapeValue(JSONObject json, int frameRate, LottieComposition composition, |
| boolean closed) { |
| super(null, frameRate, composition, true); |
| this.closed = closed; |
| init(json); |
| } |
| |
| @Override protected ShapeData valueFromObject(Object object, float scale) throws JSONException { |
| JSONObject pointsData = null; |
| if (object instanceof JSONArray) { |
| try { |
| Object firstObject = ((JSONArray) object).get(0); |
| if (firstObject instanceof JSONObject && ((JSONObject) firstObject).has("v")) { |
| pointsData = (JSONObject) firstObject; |
| } |
| } catch (JSONException e) { |
| throw new IllegalStateException("Unable to get shape. " + object); |
| } |
| } else if (object instanceof JSONObject && ((JSONObject) object).has("v")) { |
| pointsData = (JSONObject) object; |
| } |
| |
| if (pointsData == null) { |
| return null; |
| } |
| |
| JSONArray pointsArray = null; |
| JSONArray inTangents = null; |
| JSONArray outTangents = null; |
| try { |
| pointsArray = pointsData.getJSONArray("v"); |
| inTangents = pointsData.getJSONArray("i"); |
| outTangents = pointsData.getJSONArray("o"); |
| |
| if (pointsData.has("c")) { |
| // Bodymovin < 4.4 uses "closed" one level up in the json so it is passed in to the |
| // constructor. |
| // Bodymovin 4.4+ has closed here. |
| closed = pointsData.getBoolean("c"); |
| } |
| } catch (JSONException e) { |
| // Do nothing. |
| } |
| |
| if (pointsArray == null || inTangents == null || outTangents == null || |
| pointsArray.length() != inTangents.length() || |
| pointsArray.length() != outTangents.length()) { |
| throw new IllegalStateException("Unable to process points array or tangents. " + pointsData); |
| } |
| |
| ShapeData shape = new ShapeData(); |
| |
| PointF vertex = vertexAtIndex(0, pointsArray); |
| vertex.x *= scale; |
| vertex.y *= scale; |
| shape.setInitialPoint(vertex); |
| |
| for (int i = 1; i < pointsArray.length(); i++) { |
| vertex = vertexAtIndex(i, pointsArray); |
| PointF previousVertex = vertexAtIndex(i - 1, pointsArray); |
| PointF cp1 = vertexAtIndex(i - 1, outTangents); |
| PointF cp2 = vertexAtIndex(i, inTangents); |
| |
| PointF shapeCp1 = MiscUtils.addPoints(previousVertex, cp1); |
| PointF shapeCp2 = MiscUtils.addPoints(vertex, cp2); |
| |
| shapeCp1.x *= scale; |
| shapeCp1.y *= scale; |
| shapeCp2.x *= scale; |
| shapeCp2.y *= scale; |
| vertex.x *= scale; |
| vertex.y *= scale; |
| |
| shape.addCurve(new CubicCurveData(shapeCp1, shapeCp2, vertex)); |
| } |
| |
| if (closed) { |
| vertex = vertexAtIndex(0, pointsArray); |
| PointF previousVertex = vertexAtIndex(pointsArray.length() - 1, pointsArray); |
| PointF cp1 = vertexAtIndex(pointsArray.length() - 1, outTangents); |
| PointF cp2 = vertexAtIndex(0, inTangents); |
| |
| PointF shapeCp1 = MiscUtils.addPoints(previousVertex, cp1); |
| PointF shapeCp2 = MiscUtils.addPoints(vertex, cp2); |
| |
| if (scale != 1f) { |
| shapeCp1.x *= scale; |
| shapeCp1.y *= scale; |
| shapeCp2.x *= scale; |
| shapeCp2.y *= scale; |
| vertex.x *= scale; |
| vertex.y *= scale; |
| } |
| |
| shape.addCurve(new CubicCurveData(shapeCp1, shapeCp2, vertex)); |
| shape.setClosed(true); |
| } |
| return shape; |
| |
| } |
| |
| private PointF vertexAtIndex(int idx, JSONArray points) { |
| if (idx >= points.length()) { |
| throw new IllegalArgumentException( |
| "Invalid index " + idx + ". There are only " + points.length() + " points."); |
| } |
| |
| try { |
| JSONArray pointArray = points.getJSONArray(idx); |
| Object x = pointArray.get(0); |
| Object y = pointArray.get(1); |
| return new PointF( |
| x instanceof Double ? new Float((Double) x) : (int) x, |
| y instanceof Double ? new Float((Double) y) : (int) y); |
| } catch (JSONException e) { |
| throw new IllegalArgumentException("Unable to get point.", e); |
| } |
| } |
| |
| @Override public KeyframeAnimation<Path> createAnimation() { |
| if (!hasAnimation()) { |
| return new StaticKeyframeAnimation<>(convertType(initialValue)); |
| } |
| |
| ShapeKeyframeAnimation animation = |
| new ShapeKeyframeAnimation(duration, composition, keyTimes, keyValues, interpolators); |
| animation.setStartDelay(delay); |
| return animation; |
| } |
| |
| @Override Path convertType(ShapeData shapeData) { |
| convertTypePath.reset(); |
| MiscUtils.getPathFromData(shapeData, convertTypePath); |
| return convertTypePath; |
| } |
| } |