1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.animation;
18
19import java.util.ArrayList;
20import java.util.Arrays;
21import java.util.List;
22
23import android.animation.Keyframe.IntKeyframe;
24import android.animation.Keyframe.FloatKeyframe;
25import android.animation.Keyframe.ObjectKeyframe;
26import android.graphics.Path;
27import android.util.Log;
28
29/**
30 * This class holds a collection of Keyframe objects and is called by ValueAnimator to calculate
31 * values between those keyframes for a given animation. The class internal to the animation
32 * package because it is an implementation detail of how Keyframes are stored and used.
33 */
34class KeyframeSet implements Keyframes {
35
36    int mNumKeyframes;
37
38    Keyframe mFirstKeyframe;
39    Keyframe mLastKeyframe;
40    TimeInterpolator mInterpolator; // only used in the 2-keyframe case
41    List<Keyframe> mKeyframes; // only used when there are not 2 keyframes
42    TypeEvaluator mEvaluator;
43
44
45    public KeyframeSet(Keyframe... keyframes) {
46        mNumKeyframes = keyframes.length;
47        // immutable list
48        mKeyframes = Arrays.asList(keyframes);
49        mFirstKeyframe = keyframes[0];
50        mLastKeyframe = keyframes[mNumKeyframes - 1];
51        mInterpolator = mLastKeyframe.getInterpolator();
52    }
53
54    /**
55     * If subclass has variables that it calculates based on the Keyframes, it should reset them
56     * when this method is called because Keyframe contents might have changed.
57     */
58    @Override
59    public void invalidateCache() {
60    }
61
62    public List<Keyframe> getKeyframes() {
63        return mKeyframes;
64    }
65
66    public static KeyframeSet ofInt(int... values) {
67        int numKeyframes = values.length;
68        IntKeyframe keyframes[] = new IntKeyframe[Math.max(numKeyframes,2)];
69        if (numKeyframes == 1) {
70            keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f);
71            keyframes[1] = (IntKeyframe) Keyframe.ofInt(1f, values[0]);
72        } else {
73            keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f, values[0]);
74            for (int i = 1; i < numKeyframes; ++i) {
75                keyframes[i] =
76                        (IntKeyframe) Keyframe.ofInt((float) i / (numKeyframes - 1), values[i]);
77            }
78        }
79        return new IntKeyframeSet(keyframes);
80    }
81
82    public static KeyframeSet ofFloat(float... values) {
83        boolean badValue = false;
84        int numKeyframes = values.length;
85        FloatKeyframe keyframes[] = new FloatKeyframe[Math.max(numKeyframes,2)];
86        if (numKeyframes == 1) {
87            keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f);
88            keyframes[1] = (FloatKeyframe) Keyframe.ofFloat(1f, values[0]);
89            if (Float.isNaN(values[0])) {
90                badValue = true;
91            }
92        } else {
93            keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f, values[0]);
94            for (int i = 1; i < numKeyframes; ++i) {
95                keyframes[i] =
96                        (FloatKeyframe) Keyframe.ofFloat((float) i / (numKeyframes - 1), values[i]);
97                if (Float.isNaN(values[i])) {
98                    badValue = true;
99                }
100            }
101        }
102        if (badValue) {
103            Log.w("Animator", "Bad value (NaN) in float animator");
104        }
105        return new FloatKeyframeSet(keyframes);
106    }
107
108    public static KeyframeSet ofKeyframe(Keyframe... keyframes) {
109        // if all keyframes of same primitive type, create the appropriate KeyframeSet
110        int numKeyframes = keyframes.length;
111        boolean hasFloat = false;
112        boolean hasInt = false;
113        boolean hasOther = false;
114        for (int i = 0; i < numKeyframes; ++i) {
115            if (keyframes[i] instanceof FloatKeyframe) {
116                hasFloat = true;
117            } else if (keyframes[i] instanceof IntKeyframe) {
118                hasInt = true;
119            } else {
120                hasOther = true;
121            }
122        }
123        if (hasFloat && !hasInt && !hasOther) {
124            FloatKeyframe floatKeyframes[] = new FloatKeyframe[numKeyframes];
125            for (int i = 0; i < numKeyframes; ++i) {
126                floatKeyframes[i] = (FloatKeyframe) keyframes[i];
127            }
128            return new FloatKeyframeSet(floatKeyframes);
129        } else if (hasInt && !hasFloat && !hasOther) {
130            IntKeyframe intKeyframes[] = new IntKeyframe[numKeyframes];
131            for (int i = 0; i < numKeyframes; ++i) {
132                intKeyframes[i] = (IntKeyframe) keyframes[i];
133            }
134            return new IntKeyframeSet(intKeyframes);
135        } else {
136            return new KeyframeSet(keyframes);
137        }
138    }
139
140    public static KeyframeSet ofObject(Object... values) {
141        int numKeyframes = values.length;
142        ObjectKeyframe keyframes[] = new ObjectKeyframe[Math.max(numKeyframes,2)];
143        if (numKeyframes == 1) {
144            keyframes[0] = (ObjectKeyframe) Keyframe.ofObject(0f);
145            keyframes[1] = (ObjectKeyframe) Keyframe.ofObject(1f, values[0]);
146        } else {
147            keyframes[0] = (ObjectKeyframe) Keyframe.ofObject(0f, values[0]);
148            for (int i = 1; i < numKeyframes; ++i) {
149                keyframes[i] = (ObjectKeyframe) Keyframe.ofObject((float) i / (numKeyframes - 1), values[i]);
150            }
151        }
152        return new KeyframeSet(keyframes);
153    }
154
155    public static PathKeyframes ofPath(Path path) {
156        return new PathKeyframes(path);
157    }
158
159    public static PathKeyframes ofPath(Path path, float error) {
160        return new PathKeyframes(path, error);
161    }
162
163    /**
164     * Sets the TypeEvaluator to be used when calculating animated values. This object
165     * is required only for KeyframeSets that are not either IntKeyframeSet or FloatKeyframeSet,
166     * both of which assume their own evaluator to speed up calculations with those primitive
167     * types.
168     *
169     * @param evaluator The TypeEvaluator to be used to calculate animated values.
170     */
171    public void setEvaluator(TypeEvaluator evaluator) {
172        mEvaluator = evaluator;
173    }
174
175    @Override
176    public Class getType() {
177        return mFirstKeyframe.getType();
178    }
179
180    @Override
181    public KeyframeSet clone() {
182        List<Keyframe> keyframes = mKeyframes;
183        int numKeyframes = mKeyframes.size();
184        final Keyframe[] newKeyframes = new Keyframe[numKeyframes];
185        for (int i = 0; i < numKeyframes; ++i) {
186            newKeyframes[i] = keyframes.get(i).clone();
187        }
188        KeyframeSet newSet = new KeyframeSet(newKeyframes);
189        return newSet;
190    }
191
192    /**
193     * Gets the animated value, given the elapsed fraction of the animation (interpolated by the
194     * animation's interpolator) and the evaluator used to calculate in-between values. This
195     * function maps the input fraction to the appropriate keyframe interval and a fraction
196     * between them and returns the interpolated value. Note that the input fraction may fall
197     * outside the [0-1] bounds, if the animation's interpolator made that happen (e.g., a
198     * spring interpolation that might send the fraction past 1.0). We handle this situation by
199     * just using the two keyframes at the appropriate end when the value is outside those bounds.
200     *
201     * @param fraction The elapsed fraction of the animation
202     * @return The animated value.
203     */
204    public Object getValue(float fraction) {
205
206        // Special-case optimization for the common case of only two keyframes
207        if (mNumKeyframes == 2) {
208            if (mInterpolator != null) {
209                fraction = mInterpolator.getInterpolation(fraction);
210            }
211            return mEvaluator.evaluate(fraction, mFirstKeyframe.getValue(),
212                    mLastKeyframe.getValue());
213        }
214        if (fraction <= 0f) {
215            final Keyframe nextKeyframe = mKeyframes.get(1);
216            final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
217            if (interpolator != null) {
218                fraction = interpolator.getInterpolation(fraction);
219            }
220            final float prevFraction = mFirstKeyframe.getFraction();
221            float intervalFraction = (fraction - prevFraction) /
222                (nextKeyframe.getFraction() - prevFraction);
223            return mEvaluator.evaluate(intervalFraction, mFirstKeyframe.getValue(),
224                    nextKeyframe.getValue());
225        } else if (fraction >= 1f) {
226            final Keyframe prevKeyframe = mKeyframes.get(mNumKeyframes - 2);
227            final TimeInterpolator interpolator = mLastKeyframe.getInterpolator();
228            if (interpolator != null) {
229                fraction = interpolator.getInterpolation(fraction);
230            }
231            final float prevFraction = prevKeyframe.getFraction();
232            float intervalFraction = (fraction - prevFraction) /
233                (mLastKeyframe.getFraction() - prevFraction);
234            return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(),
235                    mLastKeyframe.getValue());
236        }
237        Keyframe prevKeyframe = mFirstKeyframe;
238        for (int i = 1; i < mNumKeyframes; ++i) {
239            Keyframe nextKeyframe = mKeyframes.get(i);
240            if (fraction < nextKeyframe.getFraction()) {
241                final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
242                if (interpolator != null) {
243                    fraction = interpolator.getInterpolation(fraction);
244                }
245                final float prevFraction = prevKeyframe.getFraction();
246                float intervalFraction = (fraction - prevFraction) /
247                    (nextKeyframe.getFraction() - prevFraction);
248                return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(),
249                        nextKeyframe.getValue());
250            }
251            prevKeyframe = nextKeyframe;
252        }
253        // shouldn't reach here
254        return mLastKeyframe.getValue();
255    }
256
257    @Override
258    public String toString() {
259        String returnVal = " ";
260        for (int i = 0; i < mNumKeyframes; ++i) {
261            returnVal += mKeyframes.get(i).getValue() + "  ";
262        }
263        return returnVal;
264    }
265}
266