| /* |
| * Copyright (C) 2010 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.animation; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import android.animation.Keyframe.IntKeyframe; |
| import android.animation.Keyframe.FloatKeyframe; |
| import android.animation.Keyframe.ObjectKeyframe; |
| import android.util.Log; |
| |
| /** |
| * This class holds a collection of Keyframe objects and is called by ValueAnimator to calculate |
| * values between those keyframes for a given animation. The class internal to the animation |
| * package because it is an implementation detail of how Keyframes are stored and used. |
| */ |
| class KeyframeSet { |
| |
| int mNumKeyframes; |
| |
| Keyframe mFirstKeyframe; |
| Keyframe mLastKeyframe; |
| TimeInterpolator mInterpolator; // only used in the 2-keyframe case |
| ArrayList<Keyframe> mKeyframes; // only used when there are not 2 keyframes |
| TypeEvaluator mEvaluator; |
| |
| |
| public KeyframeSet(Keyframe... keyframes) { |
| mNumKeyframes = keyframes.length; |
| mKeyframes = new ArrayList<Keyframe>(); |
| mKeyframes.addAll(Arrays.asList(keyframes)); |
| mFirstKeyframe = mKeyframes.get(0); |
| mLastKeyframe = mKeyframes.get(mNumKeyframes - 1); |
| mInterpolator = mLastKeyframe.getInterpolator(); |
| } |
| |
| public static KeyframeSet ofInt(int... values) { |
| int numKeyframes = values.length; |
| IntKeyframe keyframes[] = new IntKeyframe[Math.max(numKeyframes,2)]; |
| if (numKeyframes == 1) { |
| keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f); |
| keyframes[1] = (IntKeyframe) Keyframe.ofInt(1f, values[0]); |
| } else { |
| keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f, values[0]); |
| for (int i = 1; i < numKeyframes; ++i) { |
| keyframes[i] = |
| (IntKeyframe) Keyframe.ofInt((float) i / (numKeyframes - 1), values[i]); |
| } |
| } |
| return new IntKeyframeSet(keyframes); |
| } |
| |
| public static KeyframeSet ofFloat(float... values) { |
| boolean badValue = false; |
| int numKeyframes = values.length; |
| FloatKeyframe keyframes[] = new FloatKeyframe[Math.max(numKeyframes,2)]; |
| if (numKeyframes == 1) { |
| keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f); |
| keyframes[1] = (FloatKeyframe) Keyframe.ofFloat(1f, values[0]); |
| if (Float.isNaN(values[0])) { |
| badValue = true; |
| } |
| } else { |
| keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f, values[0]); |
| for (int i = 1; i < numKeyframes; ++i) { |
| keyframes[i] = |
| (FloatKeyframe) Keyframe.ofFloat((float) i / (numKeyframes - 1), values[i]); |
| if (Float.isNaN(values[i])) { |
| badValue = true; |
| } |
| } |
| } |
| if (badValue) { |
| Log.w("Animator", "Bad value (NaN) in float animator"); |
| } |
| return new FloatKeyframeSet(keyframes); |
| } |
| |
| public static KeyframeSet ofKeyframe(Keyframe... keyframes) { |
| // if all keyframes of same primitive type, create the appropriate KeyframeSet |
| int numKeyframes = keyframes.length; |
| boolean hasFloat = false; |
| boolean hasInt = false; |
| boolean hasOther = false; |
| for (int i = 0; i < numKeyframes; ++i) { |
| if (keyframes[i] instanceof FloatKeyframe) { |
| hasFloat = true; |
| } else if (keyframes[i] instanceof IntKeyframe) { |
| hasInt = true; |
| } else { |
| hasOther = true; |
| } |
| } |
| if (hasFloat && !hasInt && !hasOther) { |
| FloatKeyframe floatKeyframes[] = new FloatKeyframe[numKeyframes]; |
| for (int i = 0; i < numKeyframes; ++i) { |
| floatKeyframes[i] = (FloatKeyframe) keyframes[i]; |
| } |
| return new FloatKeyframeSet(floatKeyframes); |
| } else if (hasInt && !hasFloat && !hasOther) { |
| IntKeyframe intKeyframes[] = new IntKeyframe[numKeyframes]; |
| for (int i = 0; i < numKeyframes; ++i) { |
| intKeyframes[i] = (IntKeyframe) keyframes[i]; |
| } |
| return new IntKeyframeSet(intKeyframes); |
| } else { |
| return new KeyframeSet(keyframes); |
| } |
| } |
| |
| public static KeyframeSet ofObject(Object... values) { |
| int numKeyframes = values.length; |
| ObjectKeyframe keyframes[] = new ObjectKeyframe[Math.max(numKeyframes,2)]; |
| if (numKeyframes == 1) { |
| keyframes[0] = (ObjectKeyframe) Keyframe.ofObject(0f); |
| keyframes[1] = (ObjectKeyframe) Keyframe.ofObject(1f, values[0]); |
| } else { |
| keyframes[0] = (ObjectKeyframe) Keyframe.ofObject(0f, values[0]); |
| for (int i = 1; i < numKeyframes; ++i) { |
| keyframes[i] = (ObjectKeyframe) Keyframe.ofObject((float) i / (numKeyframes - 1), values[i]); |
| } |
| } |
| return new KeyframeSet(keyframes); |
| } |
| |
| /** |
| * Sets the TypeEvaluator to be used when calculating animated values. This object |
| * is required only for KeyframeSets that are not either IntKeyframeSet or FloatKeyframeSet, |
| * both of which assume their own evaluator to speed up calculations with those primitive |
| * types. |
| * |
| * @param evaluator The TypeEvaluator to be used to calculate animated values. |
| */ |
| public void setEvaluator(TypeEvaluator evaluator) { |
| mEvaluator = evaluator; |
| } |
| |
| @Override |
| public KeyframeSet clone() { |
| ArrayList<Keyframe> keyframes = mKeyframes; |
| int numKeyframes = mKeyframes.size(); |
| Keyframe[] newKeyframes = new Keyframe[numKeyframes]; |
| for (int i = 0; i < numKeyframes; ++i) { |
| newKeyframes[i] = keyframes.get(i).clone(); |
| } |
| KeyframeSet newSet = new KeyframeSet(newKeyframes); |
| return newSet; |
| } |
| |
| /** |
| * Gets the animated value, given the elapsed fraction of the animation (interpolated by the |
| * animation's interpolator) and the evaluator used to calculate in-between values. This |
| * function maps the input fraction to the appropriate keyframe interval and a fraction |
| * between them and returns the interpolated value. Note that the input fraction may fall |
| * outside the [0-1] bounds, if the animation's interpolator made that happen (e.g., a |
| * spring interpolation that might send the fraction past 1.0). We handle this situation by |
| * just using the two keyframes at the appropriate end when the value is outside those bounds. |
| * |
| * @param fraction The elapsed fraction of the animation |
| * @return The animated value. |
| */ |
| public Object getValue(float fraction) { |
| |
| // Special-case optimization for the common case of only two keyframes |
| if (mNumKeyframes == 2) { |
| if (mInterpolator != null) { |
| fraction = mInterpolator.getInterpolation(fraction); |
| } |
| return mEvaluator.evaluate(fraction, mFirstKeyframe.getValue(), |
| mLastKeyframe.getValue()); |
| } |
| if (fraction <= 0f) { |
| final Keyframe nextKeyframe = mKeyframes.get(1); |
| final TimeInterpolator interpolator = nextKeyframe.getInterpolator(); |
| if (interpolator != null) { |
| fraction = interpolator.getInterpolation(fraction); |
| } |
| final float prevFraction = mFirstKeyframe.getFraction(); |
| float intervalFraction = (fraction - prevFraction) / |
| (nextKeyframe.getFraction() - prevFraction); |
| return mEvaluator.evaluate(intervalFraction, mFirstKeyframe.getValue(), |
| nextKeyframe.getValue()); |
| } else if (fraction >= 1f) { |
| final Keyframe prevKeyframe = mKeyframes.get(mNumKeyframes - 2); |
| final TimeInterpolator interpolator = mLastKeyframe.getInterpolator(); |
| if (interpolator != null) { |
| fraction = interpolator.getInterpolation(fraction); |
| } |
| final float prevFraction = prevKeyframe.getFraction(); |
| float intervalFraction = (fraction - prevFraction) / |
| (mLastKeyframe.getFraction() - prevFraction); |
| return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(), |
| mLastKeyframe.getValue()); |
| } |
| Keyframe prevKeyframe = mFirstKeyframe; |
| for (int i = 1; i < mNumKeyframes; ++i) { |
| Keyframe nextKeyframe = mKeyframes.get(i); |
| if (fraction < nextKeyframe.getFraction()) { |
| final TimeInterpolator interpolator = nextKeyframe.getInterpolator(); |
| if (interpolator != null) { |
| fraction = interpolator.getInterpolation(fraction); |
| } |
| final float prevFraction = prevKeyframe.getFraction(); |
| float intervalFraction = (fraction - prevFraction) / |
| (nextKeyframe.getFraction() - prevFraction); |
| return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(), |
| nextKeyframe.getValue()); |
| } |
| prevKeyframe = nextKeyframe; |
| } |
| // shouldn't reach here |
| return mLastKeyframe.getValue(); |
| } |
| |
| @Override |
| public String toString() { |
| String returnVal = " "; |
| for (int i = 0; i < mNumKeyframes; ++i) { |
| returnVal += mKeyframes.get(i).getValue() + " "; |
| } |
| return returnVal; |
| } |
| } |