4

drawablesでいくつかをアニメーション化しようとしています。完全なパスに沿っていくつかの曲線に沿ってアニメーション化Androidするパスを設定しました。PathEvaluator

(たとえば 6 秒)を設定するdurationと、長さに関係なく設定したカーブの数にデュレーションが分割され、一部のセグメントではアニメーションが遅くなり、他のセグメントでは速すぎます。

これiOSを使用して修正できます

animation.calculationMode   = kCAAnimationCubicPaced;
animation.timingFunction    = ...;

これにより、iOS はパス全体を中間点に滑らかにし、各セグメントの長さに応じてデュレーションを広げることができます。Androidで同じ結果を得る方法はありますか?

(パスを個別のセグメントに分割し、各セグメントに独自の期間を手動で割り当てることに加えて、これは本当に見苦しく、維持できません)。

4

3 に答える 3

2

ギルの答えを使ってみましたが、アニメーションの仕方に合わなかったのです。Gil はsAnimationをアニメーション化するために使用されるクラスを作成しましたView。custom では使用できないObjectAnimator.ofObject()カスタムクラスをアニメーション化するために使用していました。ValuePropertiesAnimation

だからこれは私がやったことです:

  1. その評価メソッドを拡張PathEvaluatorしてオーバーライドします。
  2. ギルのロジックを使用して、パスの全長とセグメント化された長さを計算します
  3. PathEvaluator.evaluatefor eachPathPointが値0..1tで呼び出されるため、与えられた補間時間を正規化する必要があったため、増分になり、セグメントごとにゼロにはなりません。
  4. 与えられた start/end を無視するPathPointので、現在の位置は、セグメントの長さに応じて、パスに沿って開始前または終了後にすることができます。
  5. 計算された現在の進行状況を my super ( PathEvaluator) に渡して、実際の位置を計算します。

これはコードです:

public class NormalizedEvaluator extends PathEvaluator {
    private static final float BEZIER_LENGTH_ACCURACY = 0.001f;
    private List<PathPoint> mPathPoints;
    private float mOverallLength;
    private Map<PathPoint, Double> mSegmentLengths = new HashMap<PathPoint, Double>();

    public NormalizedEvaluator(List<PathPoint> pathPoints) {
        mPathPoints = pathPoints;

        if (mPathPoints == null || mPathPoints.size() < 2) {
            Log.e("CurveAnimation",
                    "There must be at least 2 points on the path. There will be an exception soon!");
        }

        calculateOverallLength();
    }

    @Override
    public PathPoint evaluate(float interpolatedTime, PathPoint ignoredStartPoint,
            PathPoint ignoredEndPoint) {

        float index = getStartIndexOfPoint(ignoredStartPoint);
        float normalizedInterpolatedTime = (interpolatedTime + index) / (mPathPoints.size() - 1);

        PathPoint[] startEndPart = getStartEndForTime(normalizedInterpolatedTime);

        PathPoint startPoint = startEndPart[0];
        PathPoint endPoint = startEndPart[1];

        float startTime = getStartTimeOfPoint(startPoint);
        float endTime = getStartTimeOfPoint(endPoint);
        float progress = (normalizedInterpolatedTime - startTime) / (endTime - startTime);

        return super.evaluate(progress, startPoint, endPoint);
    }

    private PathPoint[] getStartEndForTime(float time) {
        double length = 0;

        if (time == 1) {
            return new PathPoint[] { mPathPoints.get(mPathPoints.size() - 2),
                    mPathPoints.get(mPathPoints.size() - 1) };
        }

        PathPoint[] result = new PathPoint[2];

        for (int i = 0; i < mPathPoints.size() - 1; i++) {
            length += calculateLengthFromIndex(i);
            if (length / mOverallLength >= time) {
                result[0] = mPathPoints.get(i);
                result[1] = mPathPoints.get(i + 1);
                break;
            }
        }

        return result;
    }

    private float getStartIndexOfPoint(PathPoint point) {

        for (int ii = 0; ii < mPathPoints.size(); ii++) {
            PathPoint current = mPathPoints.get(ii);
            if (current == point) {
                return ii;
            }
        }
        return -1;
    }

    private float getStartTimeOfPoint(PathPoint point) {
        float result = 0;
        int index = 0;

        while (mPathPoints.get(index) != point && index < mPathPoints.size() - 1) {
            result += (calculateLengthFromIndex(index) / mOverallLength);
            index++;
        }

        return result;

    }

    private void calculateOverallLength() {
        mOverallLength = 0;
        mSegmentLengths.clear();

        double segmentLength;

        for (int i = 0; i < mPathPoints.size() - 1; i++) {
            segmentLength = calculateLengthFromIndex(i);
            mSegmentLengths.put(mPathPoints.get(i + 1), segmentLength);
            mOverallLength += segmentLength;
        }
    }

    private double calculateLengthFromIndex(int index) {
        PathPoint start = mPathPoints.get(index);
        PathPoint end = mPathPoints.get(index + 1);
        return calculateLength(start, end);
    }

    private double calculateLength(PathPoint start, PathPoint end) {
        if (mSegmentLengths.containsKey(end)) {
            return mSegmentLengths.get(end);
        } else if (end.mOperation == PathPoint.LINE) {
            return calculateLength(start.mX, end.mX, start.mY, end.mY);
        } else if (end.mOperation == PathPoint.CURVE) {
            return calculateBezeirLength(start, end);
        } else {
            return 0;
        }
    }

    private double calculateLength(float x0, float x1, float y0, float y1) {
        return Math.sqrt(((x0 - x1) * (x0 - x1)) + ((y0 - y1) * (y0 - y1)));
    }

    private double calculateBezeirLength(PathPoint start, PathPoint end) {
        double result = 0;
        float x, y, x0, y0;
        float[] xy;

        x0 = start.mX;
        y0 = start.mY;

        for (float progress = BEZIER_LENGTH_ACCURACY; progress <= 1; progress += BEZIER_LENGTH_ACCURACY) {
            xy = getBezierXY(start, end, progress);
            x = xy[0];
            y = xy[1];

            result += calculateLength(x, x0, y, y0);

            x0 = x;
            y0 = y;
        }

        return result;
    }

    private float[] getBezierXY(PathPoint start, PathPoint end, float progress) {
        float[] result = new float[2];

        float oneMinusT, x, y;

        oneMinusT = 1 - progress;
        x = oneMinusT * oneMinusT * oneMinusT * start.mX + 3 * oneMinusT * oneMinusT * progress
                * end.mControl0X + 3 * oneMinusT * progress * progress * end.mControl1X + progress
                * progress * progress * end.mX;
        y = oneMinusT * oneMinusT * oneMinusT * start.mY + 3 * oneMinusT * oneMinusT * progress
                * end.mControl0Y + 3 * oneMinusT * progress * progress * end.mControl1Y + progress
                * progress * progress * end.mY;

        result[0] = x;
        result[1] = y;

        return result;
    }

}

これは使用法です:

NormalizedEvaluator evaluator = new NormalizedEvaluator((List<PathPoint>) path.getPoints());
ObjectAnimator anim = ObjectAnimator.ofObject(object, "position", evaluator, path.getPoints().toArray());
于 2013-10-08T14:19:53.667 に答える
1

更新:車輪を再発明した可能性があることに気付きました。キーフレームの指定をご覧ください。


この種のものが何も利用できないことを見るのは衝撃的です。とにかく、実行時にパスの長さを計算したくない場合は、パスに重みを割り当てる機能を追加できました。アイデアは、パスに重みを割り当て、アニメーションを実行して、それが問題ないと感じたら実行し、それ以外の場合は、各パスに割り当てられた重みを増減するだけです。

次のコードは、質問で指摘した公式の Android サンプルから変更されたコードです。

 // Set up the path we're animating along
    AnimatorPath path = new AnimatorPath();
    path.moveTo(0, 0).setWeight(0);
    path.lineTo(0, 300).setWeight(30);// assign arbitrary weight
    path.curveTo(100, 0, 300, 900, 400, 500).setWeight(70);// assign arbitrary  weight

    final PathPoint[] points = path.getPoints().toArray(new PathPoint[] {});
    mFirstKeyframe = points[0];
    final int numFrames = points.length;
    final PathEvaluator pathEvaluator = new PathEvaluator();
    final ValueAnimator anim = ValueAnimator.ofInt(0, 1);// dummy values
    anim.setDuration(1000);
    anim.setInterpolator(new LinearInterpolator());
    anim.addUpdateListener(new AnimatorUpdateListener() {

        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            float fraction = animation.getAnimatedFraction();
            // Special-case optimization for the common case of only two
            // keyframes
            if (numFrames == 2) {
                PathPoint nextPoint = pathEvaluator.evaluate(fraction,
                        points[0], points[1]);
                setButtonLoc(nextPoint);
            } else {
                PathPoint prevKeyframe = mFirstKeyframe;
                for (int i = 1; i < numFrames; ++i) {
                    PathPoint nextKeyframe = points[i];
                    if (fraction < nextKeyframe.getFraction()) { 
                        final float prevFraction = prevKeyframe
                                .getFraction();
                        float intervalFraction = (fraction - prevFraction)
                                / (nextKeyframe.getFraction() - prevFraction);
                        PathPoint nextPoint = pathEvaluator.evaluate(
                                intervalFraction, prevKeyframe,
                                nextKeyframe);
                        setButtonLoc(nextPoint);
                        break;
                    }
                    prevKeyframe = nextKeyframe;
                }
            }
        }
    });

以上です !!!。

もちろん、他のクラスも変更しましたが、大きなものは何も追加されていません。たとえば、PathPointこれを追加しました:

float mWeight;
float mFraction;
public void setWeight(float weight) {
    mWeight = weight;
}

public float getWeight() {
    return mWeight;
}

public void setFraction(float fraction) {
    mFraction = fraction;
}

public float getFraction() {
    return mFraction;
}

AnimatorPath私はこのようにメソッドを変更しgetPoints()ました:

public Collection<PathPoint> getPoints() {
    // calculate fractions
    float totalWeight = 0.0F;
    for (PathPoint p : mPoints) {
        totalWeight += p.getWeight();
    }

    float lastWeight = 0F;
    for (PathPoint p : mPoints) {
        p.setFraction(lastWeight = lastWeight + p.getWeight() / totalWeight);
    } 
    return mPoints;
}

そして、それはほとんどそれです。ああ、読みやすくするために に Builder パターンを追加したAnimatorPathので、3 つのメソッドはすべて次のように変更されました。

public PathPoint moveTo(float x, float y) {// same for lineTo and curveTo method
    PathPoint p = PathPoint.moveTo(x, y);
    mPoints.add(p);
    return p;
}

注:Interpolator 0 未満または 1 より大きい分数を与える sを処理するには (例: AnticipateOvershootInterpolator)com.nineoldandroids.animation.KeyframeSet.getValue(float fraction)メソッドを参照し、 でロジックを実装しonAnimationUpdate(ValueAnimator animation)ます。

于 2013-10-08T09:23:48.337 に答える