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