1 package com.airbnb.lottie.utils; 2 3 import android.graphics.Path; 4 import android.graphics.PointF; 5 import androidx.annotation.FloatRange; 6 7 import com.airbnb.lottie.animation.content.KeyPathElementContent; 8 import com.airbnb.lottie.model.CubicCurveData; 9 import com.airbnb.lottie.model.KeyPath; 10 import com.airbnb.lottie.model.content.ShapeData; 11 12 import java.util.List; 13 14 public class MiscUtils { 15 private static PointF pathFromDataCurrentPoint = new PointF(); 16 addPoints(PointF p1, PointF p2)17 public static PointF addPoints(PointF p1, PointF p2) { 18 return new PointF(p1.x + p2.x, p1.y + p2.y); 19 } 20 getPathFromData(ShapeData shapeData, Path outPath)21 public static void getPathFromData(ShapeData shapeData, Path outPath) { 22 outPath.reset(); 23 PointF initialPoint = shapeData.getInitialPoint(); 24 outPath.moveTo(initialPoint.x, initialPoint.y); 25 pathFromDataCurrentPoint.set(initialPoint.x, initialPoint.y); 26 for (int i = 0; i < shapeData.getCurves().size(); i++) { 27 CubicCurveData curveData = shapeData.getCurves().get(i); 28 PointF cp1 = curveData.getControlPoint1(); 29 PointF cp2 = curveData.getControlPoint2(); 30 PointF vertex = curveData.getVertex(); 31 32 if (cp1.equals(pathFromDataCurrentPoint) && cp2.equals(vertex)) { 33 // On some phones like Samsung phones, zero valued control points can cause artifacting. 34 // https://github.com/airbnb/lottie-android/issues/275 35 // 36 // This does its best to add a tiny value to the vertex without affecting the final 37 // animation as much as possible. 38 // outPath.rMoveTo(0.01f, 0.01f); 39 outPath.lineTo(vertex.x, vertex.y); 40 } else { 41 outPath.cubicTo(cp1.x, cp1.y, cp2.x, cp2.y, vertex.x, vertex.y); 42 } 43 pathFromDataCurrentPoint.set(vertex.x, vertex.y); 44 } 45 if (shapeData.isClosed()) { 46 outPath.close(); 47 } 48 } 49 lerp(float a, float b, @FloatRange(from = 0f, to = 1f) float percentage)50 public static float lerp(float a, float b, @FloatRange(from = 0f, to = 1f) float percentage) { 51 return a + percentage * (b - a); 52 } 53 lerp(double a, double b, @FloatRange(from = 0f, to = 1f) double percentage)54 public static double lerp(double a, double b, @FloatRange(from = 0f, to = 1f) double percentage) { 55 return a + percentage * (b - a); 56 } 57 lerp(int a, int b, @FloatRange(from = 0f, to = 1f) float percentage)58 public static int lerp(int a, int b, @FloatRange(from = 0f, to = 1f) float percentage) { 59 return (int) (a + percentage * (b - a)); 60 } 61 floorMod(float x, float y)62 static int floorMod(float x, float y) { 63 return floorMod((int) x, (int) y); 64 } 65 floorMod(int x, int y)66 private static int floorMod(int x, int y) { 67 return x - y * floorDiv(x, y); 68 } 69 floorDiv(int x, int y)70 private static int floorDiv(int x, int y) { 71 int r = x / y; 72 boolean sameSign = (x ^ y) >= 0; 73 int mod = x % y; 74 if (!sameSign && mod != 0) { 75 r--; 76 } 77 return r; 78 } 79 clamp(int number, int min, int max)80 public static int clamp(int number, int min, int max) { 81 return Math.max(min, Math.min(max, number)); 82 } 83 clamp(float number, float min, float max)84 public static float clamp(float number, float min, float max) { 85 return Math.max(min, Math.min(max, number)); 86 } 87 contains(float number, float rangeMin, float rangeMax)88 public static boolean contains(float number, float rangeMin, float rangeMax) { 89 return number >= rangeMin && number <= rangeMax; 90 } 91 92 /** 93 * Helper method for any {@link KeyPathElementContent} that will check if the content 94 * fully matches the keypath then will add itself as the final key, resolve it, and add 95 * it to the accumulator list. 96 * 97 * Any {@link KeyPathElementContent} should call through to this as its implementation of 98 * {@link KeyPathElementContent#resolveKeyPath(KeyPath, int, List, KeyPath)}. 99 */ resolveKeyPath(KeyPath keyPath, int depth, List<KeyPath> accumulator, KeyPath currentPartialKeyPath, KeyPathElementContent content)100 public static void resolveKeyPath(KeyPath keyPath, int depth, List<KeyPath> accumulator, 101 KeyPath currentPartialKeyPath, KeyPathElementContent content) { 102 if (keyPath.fullyResolvesTo(content.getName(), depth)) { 103 currentPartialKeyPath = currentPartialKeyPath.addKey(content.getName()); 104 accumulator.add(currentPartialKeyPath.resolve(content)); 105 } 106 } 107 } 108