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