最近发现一个有意思的插值器PathInterpolator,传一个或两个点进去就可当插值器使用。可以看一下两个构造函数,生成的path是一个贝赛尔曲线。
/**
* Create an interpolator for a quadratic Bezier curve. The end points
* <code>(0, 0)</code> and <code>(1, 1)</code> are assumed.
*
* @param controlX The x coordinate of the quadratic Bezier control point.
* @param controlY The y coordinate of the quadratic Bezier control point.
*/
public PathInterpolator(float controlX, float controlY) {
initQuad(controlX, controlY);
}
/**
* Create an interpolator for a cubic Bezier curve. The end points
* <code>(0, 0)</code> and <code>(1, 1)</code> are assumed.
*
* @param controlX1 The x coordinate of the first control point of the cubic Bezier.
* @param controlY1 The y coordinate of the first control point of the cubic Bezier.
* @param controlX2 The x coordinate of the second control point of the cubic Bezier.
* @param controlY2 The y coordinate of the second control point of the cubic Bezier.
*/
public PathInterpolator(float controlX1, float controlY1, float controlX2, float controlY2) {
initCubic(controlX1, controlY1, controlX2, controlY2);
}
private void initQuad(float controlX, float controlY) {
Path path = new Path();
path.moveTo(0, 0);
path.quadTo(controlX, controlY, 1f, 1f);
initPath(path);
}
private void initCubic(float x1, float y1, float x2, float y2) {
Path path = new Path();
path.moveTo(0, 0);
path.cubicTo(x1, y1, x2, y2, 1f, 1f);
initPath(path);
}
两个构造函数生成的贝塞尔曲线都是从,(0,0)到(1,1)点的Path。
曲线生成好了之后,会将其重新解析成若干个点的数组
private void initPath(Path path) {
float[] pointComponents = path.approximate(PRECISION);
int numPoints = pointComponents.length / 3;
if (pointComponents[1] != 0 || pointComponents[2] != 0
|| pointComponents[pointComponents.length - 2] != 1
|| pointComponents[pointComponents.length - 1] != 1) {
//这个Path的起始点必须是(0,0),终止点必须是(1,1)
throw new IllegalArgumentException("The Path must start at (0,0) and end at (1,1)");
}
mX = new float[numPoints];
mY = new float[numPoints];
float prevX = 0;
float prevFraction = 0;
int componentIndex = 0;
for (int i = 0; i < numPoints; i++) {
float fraction = pointComponents[componentIndex++];
float x = pointComponents[componentIndex++];
float y = pointComponents[componentIndex++];
if (fraction == prevFraction && x != prevX) {
//判断这个Path是否连续,这个可以借鉴一下
throw new IllegalArgumentException(
"The Path cannot have discontinuity in the X axis.");
}
if (x < prevX) {
throw new IllegalArgumentException("The Path cannot loop back on itself.");
}
mX[i] = x;
mY[i] = y;
prevX = x;
prevFraction = fraction;
}
}
作为插值器,自然需要复写getInterpolation
@Override
public float getInterpolation(float t) {
if (t <= 0) {
return 0;
} else if (t >= 1) {
return 1;
}
// Do a binary search for the correct x to interpolate between.
int startIndex = 0;
int endIndex = mX.length - 1;
while (endIndex - startIndex > 1) {
int midIndex = (startIndex + endIndex) / 2;
if (t < mX[midIndex]) {
endIndex = midIndex;
} else {
startIndex = midIndex;
}
}
float xRange = mX[endIndex] - mX[startIndex];
if (xRange == 0) {
return mY[startIndex];
}
float tInRange = t - mX[startIndex];
float fraction = tInRange / xRange;
float startY = mY[startIndex];
float endY = mY[endIndex];
return startY + (fraction * (endY - startY));
}
算法里面用二分法找到了对应的index,返回值是一个线性的计算,很简单。
最后看一下path.approximate的算法。
// Returns a float[] with each point along the path represented by 3 floats
// * fractional length along the path that the point resides
// * x coordinate
// * y coordinate
// Note that more than one point may have the same length along the path in
// the case of a move.
// NULL can be returned if the Path is empty.
static jfloatArray approximate(JNIEnv* env, jclass, jlong pathHandle, float acceptableError)
{
SkPath* path = reinterpret_cast<SkPath*>(pathHandle);
SkASSERT(path);
SkPath::Iter pathIter(*path, false);
SkPath::Verb verb;
SkPoint points[4];
std::vector<SkPoint> segmentPoints;
std::vector<float> lengths;
float errorSquared = acceptableError * acceptableError;
while ((verb = pathIter.next(points, false)) != SkPath::kDone_Verb) {
createVerbSegments(verb, points, segmentPoints, lengths, errorSquared);
}
if (segmentPoints.empty()) {
int numVerbs = path->countVerbs();
if (numVerbs == 1) {
addMove(segmentPoints, lengths, path->getPoint(0));
} else {
// Invalid or empty path. Fall back to point(0,0)
addMove(segmentPoints, lengths, SkPoint());
}
}
float totalLength = lengths.back();
if (totalLength == 0) {
// Lone Move instructions should still be able to animate at the same value.
segmentPoints.push_back(segmentPoints.back());
lengths.push_back(1);
totalLength = 1;
}
size_t numPoints = segmentPoints.size();
size_t approximationArraySize = numPoints * 3;
float* approximation = new float[approximationArraySize];
int approximationIndex = 0;
for (size_t i = 0; i < numPoints; i++) {
const SkPoint& point = segmentPoints[i];
approximation[approximationIndex++] = lengths[i] / totalLength;
approximation[approximationIndex++] = point.x();
approximation[approximationIndex++] = point.y();
}
jfloatArray result = env->NewFloatArray(approximationArraySize);
env->SetFloatArrayRegion(result, 0, approximationArraySize, approximation);
delete[] approximation;
return result;
}
};
太深入我没有继续研究,不过返回的点数组是这样的
for (size_t i = 0; i < numPoints; i++) {
const SkPoint& point = segmentPoints[i];
approximation[approximationIndex++] = lengths[i] / totalLength;
approximation[approximationIndex++] = point.x();
approximation[approximationIndex++] = point.y();
}