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