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