Skip to content

Instantly share code, notes, and snippets.

@alexjlockwood
Last active January 1, 2018 10:52
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alexjlockwood/0d3e7a4962f5328712506e9e52ce0395 to your computer and use it in GitHub Desktop.
Save alexjlockwood/0d3e7a4962f5328712506e9e52ce0395 to your computer and use it in GitHub Desktop.
ObjectAnimator motion along a path for pre-Lollipop devices (https://j.mp/object-animator-path-motion)
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
public final class ObjectAnimatorCompat {
private static final int MAX_NUM_POINTS = 100;
public static ObjectAnimator ofFloat(
@NonNull Object target,
@Nullable String xPropertyName,
@Nullable String yPropertyName,
@NonNull Path path) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
return ObjectAnimator.ofFloat(target, xPropertyName, yPropertyName, path);
} else {
final ObjectAnimator anim = new ObjectAnimator();
anim.setTarget(target);
animateAlongPath(anim, xPropertyName, yPropertyName, path);
return anim;
}
}
private static void animateAlongPath(
@NonNull ObjectAnimator anim,
@Nullable String xPropertyName,
@Nullable String yPropertyName,
@NonNull Path path) {
if (xPropertyName == null && yPropertyName == null) {
return;
}
// Measure the total length the whole path.
final PathMeasure measureForTotalLength = new PathMeasure(path, false);
float totalLength = 0;
// The sum of the previous contour plus the current one. Using the sum here
// because we want to directly subtract from it later.
final List<Float> contourLengths = new ArrayList<>();
contourLengths.add(0f);
do {
final float pathLength = measureForTotalLength.getLength();
totalLength += pathLength;
contourLengths.add(totalLength);
} while (measureForTotalLength.nextContour());
// Now determine how many sample points we need, and the step for next sample.
final PathMeasure pathMeasure = new PathMeasure(path, false);
final int numPoints = Math.min(MAX_NUM_POINTS, (int) (2 * totalLength) + 1);
final float[] coordsX = new float[numPoints];
final float[] coordsY = new float[numPoints];
final float[] position = new float[2];
int contourIndex = 0;
final float step = totalLength / (numPoints - 1);
float currentDistance = 0;
// For each sample point, determine whether we need to move on to next contour.
// After we find the right contour, then sample it using the current distance value minus
// the previously sampled contours' total length.
for (int i = 0; i < numPoints; i++) {
pathMeasure.getPosTan(currentDistance, position, null);
pathMeasure.getPosTan(currentDistance, position, null);
coordsX[i] = position[0];
coordsY[i] = position[1];
currentDistance += step;
if ((contourIndex + 1) < contourLengths.size()
&& currentDistance > contourLengths.get(contourIndex + 1)) {
currentDistance -= contourLengths.get(contourIndex + 1);
contourIndex++;
pathMeasure.nextContour();
}
}
// Given the x and y value of the sample points, setup the ObjectAnimator properly.
PropertyValuesHolder x = null;
PropertyValuesHolder y = null;
if (xPropertyName != null) {
x = PropertyValuesHolder.ofFloat(xPropertyName, coordsX);
}
if (yPropertyName != null) {
y = PropertyValuesHolder.ofFloat(yPropertyName, coordsY);
}
if (x == null) {
anim.setValues(y);
} else if (y == null) {
anim.setValues(x);
} else {
anim.setValues(x, y);
}
}
private ObjectAnimatorCompat() {}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment