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