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 android.animation.Keyframe.IntKeyframe;
22import android.animation.Keyframe.FloatKeyframe;
23import android.animation.Keyframe.ObjectKeyframe;
24import android.util.Log;
25
26/**
27 * This class holds a collection of Keyframe objects and is called by ValueAnimator to calculate
28 * values between those keyframes for a given animation. The class internal to the animation
29 * package because it is an implementation detail of how Keyframes are stored and used.
30 */
31class KeyframeSet {
32
33    int mNumKeyframes;
34
35    Keyframe mFirstKeyframe;
36    Keyframe mLastKeyframe;
37    TimeInterpolator mInterpolator; // only used in the 2-keyframe case
38    ArrayList<Keyframe> mKeyframes; // only used when there are not 2 keyframes
39    TypeEvaluator mEvaluator;
40
41
42    public KeyframeSet(Keyframe... keyframes) {
43        mNumKeyframes = keyframes.length;
44        mKeyframes = new ArrayList<Keyframe>();
45        mKeyframes.addAll(Arrays.asList(keyframes));
46        mFirstKeyframe = mKeyframes.get(0);
47        mLastKeyframe = mKeyframes.get(mNumKeyframes - 1);
48        mInterpolator = mLastKeyframe.getInterpolator();
49    }
50
51    public static KeyframeSet ofInt(int... values) {
52        int numKeyframes = values.length;
53        IntKeyframe keyframes[] = new IntKeyframe[Math.max(numKeyframes,2)];
54        if (numKeyframes == 1) {
55            keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f);
56            keyframes[1] = (IntKeyframe) Keyframe.ofInt(1f, values[0]);
57        } else {
58            keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f, values[0]);
59            for (int i = 1; i < numKeyframes; ++i) {
60                keyframes[i] =
61                        (IntKeyframe) Keyframe.ofInt((float) i / (numKeyframes - 1), values[i]);
62            }
63        }
64        return new IntKeyframeSet(keyframes);
65    }
66
67    public static KeyframeSet ofFloat(float... values) {
68        boolean badValue = false;
69        int numKeyframes = values.length;
70        FloatKeyframe keyframes[] = new FloatKeyframe[Math.max(numKeyframes,2)];
71        if (numKeyframes == 1) {
72            keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f);
73            keyframes[1] = (FloatKeyframe) Keyframe.ofFloat(1f, values[0]);
74            if (Float.isNaN(values[0])) {
75                badValue = true;
76            }
77        } else {
78            keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f, values[0]);
79            for (int i = 1; i < numKeyframes; ++i) {
80                keyframes[i] =
81                        (FloatKeyframe) Keyframe.ofFloat((float) i / (numKeyframes - 1), values[i]);
82                if (Float.isNaN(values[i])) {
83                    badValue = true;
84                }
85            }
86        }
87        if (badValue) {
88            Log.w("Animator", "Bad value (NaN) in float animator");
89        }
90        return new FloatKeyframeSet(keyframes);
91    }
92
93    public static KeyframeSet ofKeyframe(Keyframe... keyframes) {
94        // if all keyframes of same primitive type, create the appropriate KeyframeSet
95        int numKeyframes = keyframes.length;
96        boolean hasFloat = false;
97        boolean hasInt = false;
98        boolean hasOther = false;
99        for (int i = 0; i < numKeyframes; ++i) {
100            if (keyframes[i] instanceof FloatKeyframe) {
101                hasFloat = true;
102            } else if (keyframes[i] instanceof IntKeyframe) {
103                hasInt = true;
104            } else {
105                hasOther = true;
106            }
107        }
108        if (hasFloat && !hasInt && !hasOther) {
109            FloatKeyframe floatKeyframes[] = new FloatKeyframe[numKeyframes];
110            for (int i = 0; i < numKeyframes; ++i) {
111                floatKeyframes[i] = (FloatKeyframe) keyframes[i];
112            }
113            return new FloatKeyframeSet(floatKeyframes);
114        } else if (hasInt && !hasFloat && !hasOther) {
115            IntKeyframe intKeyframes[] = new IntKeyframe[numKeyframes];
116            for (int i = 0; i < numKeyframes; ++i) {
117                intKeyframes[i] = (IntKeyframe) keyframes[i];
118            }
119            return new IntKeyframeSet(intKeyframes);
120        } else {
121            return new KeyframeSet(keyframes);
122        }
123    }
124
125    public static KeyframeSet ofObject(Object... values) {
126        int numKeyframes = values.length;
127        ObjectKeyframe keyframes[] = new ObjectKeyframe[Math.max(numKeyframes,2)];
128        if (numKeyframes == 1) {
129            keyframes[0] = (ObjectKeyframe) Keyframe.ofObject(0f);
130            keyframes[1] = (ObjectKeyframe) Keyframe.ofObject(1f, values[0]);
131        } else {
132            keyframes[0] = (ObjectKeyframe) Keyframe.ofObject(0f, values[0]);
133            for (int i = 1; i < numKeyframes; ++i) {
134                keyframes[i] = (ObjectKeyframe) Keyframe.ofObject((float) i / (numKeyframes - 1), values[i]);
135            }
136        }
137        return new KeyframeSet(keyframes);
138    }
139
140    /**
141     * Sets the TypeEvaluator to be used when calculating animated values. This object
142     * is required only for KeyframeSets that are not either IntKeyframeSet or FloatKeyframeSet,
143     * both of which assume their own evaluator to speed up calculations with those primitive
144     * types.
145     *
146     * @param evaluator The TypeEvaluator to be used to calculate animated values.
147     */
148    public void setEvaluator(TypeEvaluator evaluator) {
149        mEvaluator = evaluator;
150    }
151
152    @Override
153    public KeyframeSet clone() {
154        ArrayList<Keyframe> keyframes = mKeyframes;
155        int numKeyframes = mKeyframes.size();
156        Keyframe[] newKeyframes = new Keyframe[numKeyframes];
157        for (int i = 0; i < numKeyframes; ++i) {
158            newKeyframes[i] = keyframes.get(i).clone();
159        }
160        KeyframeSet newSet = new KeyframeSet(newKeyframes);
161        return newSet;
162    }
163
164    /**
165     * Gets the animated value, given the elapsed fraction of the animation (interpolated by the
166     * animation's interpolator) and the evaluator used to calculate in-between values. This
167     * function maps the input fraction to the appropriate keyframe interval and a fraction
168     * between them and returns the interpolated value. Note that the input fraction may fall
169     * outside the [0-1] bounds, if the animation's interpolator made that happen (e.g., a
170     * spring interpolation that might send the fraction past 1.0). We handle this situation by
171     * just using the two keyframes at the appropriate end when the value is outside those bounds.
172     *
173     * @param fraction The elapsed fraction of the animation
174     * @return The animated value.
175     */
176    public Object getValue(float fraction) {
177
178        // Special-case optimization for the common case of only two keyframes
179        if (mNumKeyframes == 2) {
180            if (mInterpolator != null) {
181                fraction = mInterpolator.getInterpolation(fraction);
182            }
183            return mEvaluator.evaluate(fraction, mFirstKeyframe.getValue(),
184                    mLastKeyframe.getValue());
185        }
186        if (fraction <= 0f) {
187            final Keyframe nextKeyframe = mKeyframes.get(1);
188            final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
189            if (interpolator != null) {
190                fraction = interpolator.getInterpolation(fraction);
191            }
192            final float prevFraction = mFirstKeyframe.getFraction();
193            float intervalFraction = (fraction - prevFraction) /
194                (nextKeyframe.getFraction() - prevFraction);
195            return mEvaluator.evaluate(intervalFraction, mFirstKeyframe.getValue(),
196                    nextKeyframe.getValue());
197        } else if (fraction >= 1f) {
198            final Keyframe prevKeyframe = mKeyframes.get(mNumKeyframes - 2);
199            final TimeInterpolator interpolator = mLastKeyframe.getInterpolator();
200            if (interpolator != null) {
201                fraction = interpolator.getInterpolation(fraction);
202            }
203            final float prevFraction = prevKeyframe.getFraction();
204            float intervalFraction = (fraction - prevFraction) /
205                (mLastKeyframe.getFraction() - prevFraction);
206            return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(),
207                    mLastKeyframe.getValue());
208        }
209        Keyframe prevKeyframe = mFirstKeyframe;
210        for (int i = 1; i < mNumKeyframes; ++i) {
211            Keyframe nextKeyframe = mKeyframes.get(i);
212            if (fraction < nextKeyframe.getFraction()) {
213                final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
214                if (interpolator != null) {
215                    fraction = interpolator.getInterpolation(fraction);
216                }
217                final float prevFraction = prevKeyframe.getFraction();
218                float intervalFraction = (fraction - prevFraction) /
219                    (nextKeyframe.getFraction() - prevFraction);
220                return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(),
221                        nextKeyframe.getValue());
222            }
223            prevKeyframe = nextKeyframe;
224        }
225        // shouldn't reach here
226        return mLastKeyframe.getValue();
227    }
228
229    @Override
230    public String toString() {
231        String returnVal = " ";
232        for (int i = 0; i < mNumKeyframes; ++i) {
233            returnVal += mKeyframes.get(i).getValue() + "  ";
234        }
235        return returnVal;
236    }
237}
238