• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.airbnb.lottie.animation.keyframe;
2 
3 import static com.airbnb.lottie.LottieProperty.TRANSFORM_ANCHOR_POINT;
4 import static com.airbnb.lottie.LottieProperty.TRANSFORM_END_OPACITY;
5 import static com.airbnb.lottie.LottieProperty.TRANSFORM_OPACITY;
6 import static com.airbnb.lottie.LottieProperty.TRANSFORM_POSITION;
7 import static com.airbnb.lottie.LottieProperty.TRANSFORM_POSITION_X;
8 import static com.airbnb.lottie.LottieProperty.TRANSFORM_POSITION_Y;
9 import static com.airbnb.lottie.LottieProperty.TRANSFORM_ROTATION;
10 import static com.airbnb.lottie.LottieProperty.TRANSFORM_SCALE;
11 import static com.airbnb.lottie.LottieProperty.TRANSFORM_SKEW;
12 import static com.airbnb.lottie.LottieProperty.TRANSFORM_SKEW_ANGLE;
13 import static com.airbnb.lottie.LottieProperty.TRANSFORM_START_OPACITY;
14 
15 import android.graphics.Matrix;
16 import android.graphics.PointF;
17 
18 import androidx.annotation.Nullable;
19 
20 import com.airbnb.lottie.model.animatable.AnimatableTransform;
21 import com.airbnb.lottie.model.layer.BaseLayer;
22 import com.airbnb.lottie.value.Keyframe;
23 import com.airbnb.lottie.value.LottieValueCallback;
24 import com.airbnb.lottie.value.ScaleXY;
25 
26 import java.util.Collections;
27 
28 public class TransformKeyframeAnimation {
29   private final Matrix matrix = new Matrix();
30   private final Matrix skewMatrix1;
31   private final Matrix skewMatrix2;
32   private final Matrix skewMatrix3;
33   private final float[] skewValues;
34 
35   @Nullable private BaseKeyframeAnimation<PointF, PointF> anchorPoint;
36   @Nullable private BaseKeyframeAnimation<?, PointF> position;
37   @Nullable private BaseKeyframeAnimation<ScaleXY, ScaleXY> scale;
38   @Nullable private BaseKeyframeAnimation<Float, Float> rotation;
39   @Nullable private BaseKeyframeAnimation<Integer, Integer> opacity;
40   @Nullable private FloatKeyframeAnimation skew;
41   @Nullable private FloatKeyframeAnimation skewAngle;
42 
43   // Used for repeaters
44   @Nullable private BaseKeyframeAnimation<?, Float> startOpacity;
45   @Nullable private BaseKeyframeAnimation<?, Float> endOpacity;
46 
TransformKeyframeAnimation(AnimatableTransform animatableTransform)47   public TransformKeyframeAnimation(AnimatableTransform animatableTransform) {
48     anchorPoint = animatableTransform.getAnchorPoint() == null ? null : animatableTransform.getAnchorPoint().createAnimation();
49     position = animatableTransform.getPosition() == null ? null : animatableTransform.getPosition().createAnimation();
50     scale = animatableTransform.getScale() == null ? null : animatableTransform.getScale().createAnimation();
51     rotation = animatableTransform.getRotation() == null ? null : animatableTransform.getRotation().createAnimation();
52     skew = animatableTransform.getSkew() == null ? null : (FloatKeyframeAnimation) animatableTransform.getSkew().createAnimation();
53     if (skew != null) {
54       skewMatrix1 = new Matrix();
55       skewMatrix2 = new Matrix();
56       skewMatrix3 = new Matrix();
57       skewValues = new float[9];
58     } else {
59       skewMatrix1 = null;
60       skewMatrix2 = null;
61       skewMatrix3 = null;
62       skewValues = null;
63     }
64     skewAngle = animatableTransform.getSkewAngle() == null ? null : (FloatKeyframeAnimation) animatableTransform.getSkewAngle().createAnimation();
65     if (animatableTransform.getOpacity() != null) {
66       opacity = animatableTransform.getOpacity().createAnimation();
67     }
68     if (animatableTransform.getStartOpacity() != null) {
69       startOpacity = animatableTransform.getStartOpacity().createAnimation();
70     } else {
71       startOpacity = null;
72     }
73     if (animatableTransform.getEndOpacity() != null) {
74       endOpacity = animatableTransform.getEndOpacity().createAnimation();
75     } else {
76       endOpacity = null;
77     }
78   }
79 
addAnimationsToLayer(BaseLayer layer)80   public void addAnimationsToLayer(BaseLayer layer) {
81     layer.addAnimation(opacity);
82     layer.addAnimation(startOpacity);
83     layer.addAnimation(endOpacity);
84 
85     layer.addAnimation(anchorPoint);
86     layer.addAnimation(position);
87     layer.addAnimation(scale);
88     layer.addAnimation(rotation);
89     layer.addAnimation(skew);
90     layer.addAnimation(skewAngle);
91   }
92 
addListener(final BaseKeyframeAnimation.AnimationListener listener)93   public void addListener(final BaseKeyframeAnimation.AnimationListener listener) {
94     if (opacity != null) {
95       opacity.addUpdateListener(listener);
96     }
97     if (startOpacity != null) {
98       startOpacity.addUpdateListener(listener);
99     }
100     if (endOpacity != null) {
101       endOpacity.addUpdateListener(listener);
102     }
103 
104     if (anchorPoint != null) {
105       anchorPoint.addUpdateListener(listener);
106     }
107     if (position != null) {
108       position.addUpdateListener(listener);
109     }
110     if (scale != null) {
111       scale.addUpdateListener(listener);
112     }
113     if (rotation != null) {
114       rotation.addUpdateListener(listener);
115     }
116     if (skew != null) {
117       skew.addUpdateListener(listener);
118     }
119     if (skewAngle != null) {
120       skewAngle.addUpdateListener(listener);
121     }
122   }
123 
setProgress(float progress)124   public void setProgress(float progress) {
125     if (opacity != null) {
126       opacity.setProgress(progress);
127     }
128     if (startOpacity != null) {
129       startOpacity.setProgress(progress);
130     }
131     if (endOpacity != null) {
132       endOpacity.setProgress(progress);
133     }
134 
135     if (anchorPoint != null) {
136       anchorPoint.setProgress(progress);
137     }
138     if (position != null) {
139       position.setProgress(progress);
140     }
141     if (scale != null) {
142       scale.setProgress(progress);
143     }
144     if (rotation != null) {
145       rotation.setProgress(progress);
146     }
147     if (skew != null) {
148       skew.setProgress(progress);
149     }
150     if (skewAngle != null) {
151       skewAngle.setProgress(progress);
152     }
153   }
154 
getOpacity()155   @Nullable public BaseKeyframeAnimation<?, Integer> getOpacity() {
156     return opacity;
157   }
158 
getStartOpacity()159   @Nullable public BaseKeyframeAnimation<?, Float> getStartOpacity() {
160     return startOpacity;
161   }
162 
getEndOpacity()163   @Nullable public BaseKeyframeAnimation<?, Float> getEndOpacity() {
164     return endOpacity;
165   }
166 
getMatrix()167   public Matrix getMatrix() {
168     matrix.reset();
169     BaseKeyframeAnimation<?, PointF> position = this.position;
170     if (position != null) {
171       PointF positionValue = position.getValue();
172       if (positionValue != null && (positionValue.x != 0 || positionValue.y != 0)) {
173         matrix.preTranslate(positionValue.x, positionValue.y);
174       }
175     }
176 
177     BaseKeyframeAnimation<Float, Float> rotation = this.rotation;
178     if (rotation != null) {
179       float rotationValue;
180       if (rotation instanceof ValueCallbackKeyframeAnimation) {
181         rotationValue = rotation.getValue();
182       } else {
183         rotationValue = ((FloatKeyframeAnimation) rotation).getFloatValue();
184       }
185       if (rotationValue != 0f) {
186         matrix.preRotate(rotationValue);
187       }
188     }
189 
190     FloatKeyframeAnimation skew = this.skew;
191     if (skew != null) {
192       float mCos = skewAngle == null ? 0f : (float) Math.cos(Math.toRadians(-skewAngle.getFloatValue() + 90));
193       float mSin = skewAngle == null ? 1f : (float) Math.sin(Math.toRadians(-skewAngle.getFloatValue() + 90));
194       float aTan = (float) Math.tan(Math.toRadians(skew.getFloatValue()));
195       clearSkewValues();
196       skewValues[0] = mCos;
197       skewValues[1] = mSin;
198       skewValues[3] = -mSin;
199       skewValues[4] = mCos;
200       skewValues[8] = 1f;
201       skewMatrix1.setValues(skewValues);
202       clearSkewValues();
203       skewValues[0] = 1f;
204       skewValues[3] = aTan;
205       skewValues[4] = 1f;
206       skewValues[8] = 1f;
207       skewMatrix2.setValues(skewValues);
208       clearSkewValues();
209       skewValues[0] = mCos;
210       skewValues[1] = -mSin;
211       skewValues[3] = mSin;
212       skewValues[4] = mCos;
213       skewValues[8] = 1;
214       skewMatrix3.setValues(skewValues);
215       skewMatrix2.preConcat(skewMatrix1);
216       skewMatrix3.preConcat(skewMatrix2);
217 
218       matrix.preConcat(skewMatrix3);
219     }
220 
221     BaseKeyframeAnimation<ScaleXY, ScaleXY> scale = this.scale;
222     if (scale != null) {
223       ScaleXY scaleTransform = scale.getValue();
224       if (scaleTransform.getScaleX() != 1f || scaleTransform.getScaleY() != 1f) {
225         matrix.preScale(scaleTransform.getScaleX(), scaleTransform.getScaleY());
226       }
227     }
228 
229     BaseKeyframeAnimation<PointF, PointF> anchorPoint = this.anchorPoint;
230     if (anchorPoint != null) {
231       PointF anchorPointValue = anchorPoint.getValue();
232       if (anchorPointValue.x != 0 || anchorPointValue.y != 0) {
233         matrix.preTranslate(-anchorPointValue.x, -anchorPointValue.y);
234       }
235     }
236 
237     return matrix;
238   }
239 
clearSkewValues()240   private void clearSkewValues() {
241     for (int i = 0; i < 9; i++) {
242       skewValues[i] = 0f;
243     }
244   }
245 
246   /**
247    * TODO: see if we can use this for the main {@link #getMatrix()} method.
248    */
getMatrixForRepeater(float amount)249   public Matrix getMatrixForRepeater(float amount) {
250     PointF position = this.position == null ? null : this.position.getValue();
251     ScaleXY scale = this.scale == null ? null : this.scale.getValue();
252 
253     matrix.reset();
254     if (position != null) {
255       matrix.preTranslate(position.x * amount, position.y * amount);
256     }
257     if (scale != null) {
258       matrix.preScale(
259           (float) Math.pow(scale.getScaleX(), amount),
260           (float) Math.pow(scale.getScaleY(), amount));
261     }
262     if (this.rotation != null) {
263       float rotation = this.rotation.getValue();
264       PointF anchorPoint = this.anchorPoint == null ? null : this.anchorPoint.getValue();
265       matrix.preRotate(rotation * amount, anchorPoint == null ? 0f : anchorPoint.x, anchorPoint == null ? 0f : anchorPoint.y);
266     }
267 
268     return matrix;
269   }
270 
271   /**
272    * Returns whether the callback was applied.
273    */
274   @SuppressWarnings("unchecked")
applyValueCallback(T property, @Nullable LottieValueCallback<T> callback)275   public <T> boolean applyValueCallback(T property, @Nullable LottieValueCallback<T> callback) {
276     if (property == TRANSFORM_ANCHOR_POINT) {
277       if (anchorPoint == null) {
278         anchorPoint = new ValueCallbackKeyframeAnimation<>((LottieValueCallback<PointF>) callback, new PointF());
279       } else {
280         anchorPoint.setValueCallback((LottieValueCallback<PointF>) callback);
281       }
282     } else if (property == TRANSFORM_POSITION) {
283       if (position == null) {
284         position = new ValueCallbackKeyframeAnimation<>((LottieValueCallback<PointF>) callback, new PointF());
285       } else {
286         position.setValueCallback((LottieValueCallback<PointF>) callback);
287       }
288     } else if (property == TRANSFORM_POSITION_X && position instanceof SplitDimensionPathKeyframeAnimation) {
289       ((SplitDimensionPathKeyframeAnimation) position).setXValueCallback((LottieValueCallback<Float>) callback);
290     } else if (property == TRANSFORM_POSITION_Y && position instanceof SplitDimensionPathKeyframeAnimation) {
291       ((SplitDimensionPathKeyframeAnimation) position).setYValueCallback((LottieValueCallback<Float>) callback);
292     } else if (property == TRANSFORM_SCALE) {
293       if (scale == null) {
294         scale = new ValueCallbackKeyframeAnimation<>((LottieValueCallback<ScaleXY>) callback, new ScaleXY());
295       } else {
296         scale.setValueCallback((LottieValueCallback<ScaleXY>) callback);
297       }
298     } else if (property == TRANSFORM_ROTATION) {
299       if (rotation == null) {
300         rotation = new ValueCallbackKeyframeAnimation<>((LottieValueCallback<Float>) callback, 0f);
301       } else {
302         rotation.setValueCallback((LottieValueCallback<Float>) callback);
303       }
304     } else if (property == TRANSFORM_OPACITY) {
305       if (opacity == null) {
306         opacity = new ValueCallbackKeyframeAnimation<>((LottieValueCallback<Integer>) callback, 100);
307       } else {
308         opacity.setValueCallback((LottieValueCallback<Integer>) callback);
309       }
310     } else if (property == TRANSFORM_START_OPACITY) {
311       if (startOpacity == null) {
312         startOpacity = new ValueCallbackKeyframeAnimation<>((LottieValueCallback<Float>) callback, 100f);
313       } else {
314         startOpacity.setValueCallback((LottieValueCallback<Float>) callback);
315       }
316     } else if (property == TRANSFORM_END_OPACITY) {
317       if (endOpacity == null) {
318         endOpacity = new ValueCallbackKeyframeAnimation<>((LottieValueCallback<Float>) callback, 100f);
319       } else {
320         endOpacity.setValueCallback((LottieValueCallback<Float>) callback);
321       }
322     } else if (property == TRANSFORM_SKEW) {
323       if (skew == null) {
324         skew = new FloatKeyframeAnimation(Collections.singletonList(new Keyframe<>(0f)));
325       }
326       skew.setValueCallback((LottieValueCallback<Float>) callback);
327     } else if (property == TRANSFORM_SKEW_ANGLE) {
328       if (skewAngle == null) {
329         skewAngle = new FloatKeyframeAnimation(Collections.singletonList(new Keyframe<>(0f)));
330       }
331       skewAngle.setValueCallback((LottieValueCallback<Float>) callback);
332     } else {
333       return false;
334     }
335     return true;
336   }
337 }
338