1/*
2 * Copyright (C) 2007 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.view.animation;
18
19import android.annotation.AnimRes;
20import android.annotation.InterpolatorRes;
21import android.content.Context;
22import android.content.res.TypedArray;
23import android.util.AttributeSet;
24import android.view.View;
25import android.view.ViewGroup;
26
27import java.util.Random;
28
29/**
30 * A layout animation controller is used to animated a layout's, or a view
31 * group's, children. Each child uses the same animation but for every one of
32 * them, the animation starts at a different time. A layout animation controller
33 * is used by {@link android.view.ViewGroup} to compute the delay by which each
34 * child's animation start must be offset. The delay is computed by using
35 * characteristics of each child, like its index in the view group.
36 *
37 * This standard implementation computes the delay by multiplying a fixed
38 * amount of miliseconds by the index of the child in its parent view group.
39 * Subclasses are supposed to override
40 * {@link #getDelayForView(android.view.View)} to implement a different way
41 * of computing the delay. For instance, a
42 * {@link android.view.animation.GridLayoutAnimationController} will compute the
43 * delay based on the column and row indices of the child in its parent view
44 * group.
45 *
46 * Information used to compute the animation delay of each child are stored
47 * in an instance of
48 * {@link android.view.animation.LayoutAnimationController.AnimationParameters},
49 * itself stored in the {@link android.view.ViewGroup.LayoutParams} of the view.
50 *
51 * @attr ref android.R.styleable#LayoutAnimation_delay
52 * @attr ref android.R.styleable#LayoutAnimation_animationOrder
53 * @attr ref android.R.styleable#LayoutAnimation_interpolator
54 * @attr ref android.R.styleable#LayoutAnimation_animation
55 */
56public class LayoutAnimationController {
57    /**
58     * Distributes the animation delays in the order in which view were added
59     * to their view group.
60     */
61    public static final int ORDER_NORMAL  = 0;
62
63    /**
64     * Distributes the animation delays in the reverse order in which view were
65     * added to their view group.
66     */
67    public static final int ORDER_REVERSE = 1;
68
69    /**
70     * Randomly distributes the animation delays.
71     */
72    public static final int ORDER_RANDOM  = 2;
73
74    /**
75     * The animation applied on each child of the view group on which this
76     * layout animation controller is set.
77     */
78    protected Animation mAnimation;
79
80    /**
81     * The randomizer used when the order is set to random. Subclasses should
82     * use this object to avoid creating their own.
83     */
84    protected Random mRandomizer;
85
86    /**
87     * The interpolator used to interpolate the delays.
88     */
89    protected Interpolator mInterpolator;
90
91    private float mDelay;
92    private int mOrder;
93
94    private long mDuration;
95    private long mMaxDelay;
96
97    /**
98     * Creates a new layout animation controller from external resources.
99     *
100     * @param context the Context the view  group is running in, through which
101     *        it can access the resources
102     * @param attrs the attributes of the XML tag that is inflating the
103     *        layout animation controller
104     */
105    public LayoutAnimationController(Context context, AttributeSet attrs) {
106        TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.LayoutAnimation);
107
108        Animation.Description d = Animation.Description.parseValue(
109                a.peekValue(com.android.internal.R.styleable.LayoutAnimation_delay));
110        mDelay = d.value;
111
112        mOrder = a.getInt(com.android.internal.R.styleable.LayoutAnimation_animationOrder, ORDER_NORMAL);
113
114        int resource = a.getResourceId(com.android.internal.R.styleable.LayoutAnimation_animation, 0);
115        if (resource > 0) {
116            setAnimation(context, resource);
117        }
118
119        resource = a.getResourceId(com.android.internal.R.styleable.LayoutAnimation_interpolator, 0);
120        if (resource > 0) {
121            setInterpolator(context, resource);
122        }
123
124        a.recycle();
125    }
126
127    /**
128     * Creates a new layout animation controller with a delay of 50%
129     * and the specified animation.
130     *
131     * @param animation the animation to use on each child of the view group
132     */
133    public LayoutAnimationController(Animation animation) {
134        this(animation, 0.5f);
135    }
136
137    /**
138     * Creates a new layout animation controller with the specified delay
139     * and the specified animation.
140     *
141     * @param animation the animation to use on each child of the view group
142     * @param delay the delay by which each child's animation must be offset
143     */
144    public LayoutAnimationController(Animation animation, float delay) {
145        mDelay = delay;
146        setAnimation(animation);
147    }
148
149    /**
150     * Returns the order used to compute the delay of each child's animation.
151     *
152     * @return one of {@link #ORDER_NORMAL}, {@link #ORDER_REVERSE} or
153     *         {@link #ORDER_RANDOM)
154     *
155     * @attr ref android.R.styleable#LayoutAnimation_animationOrder
156     */
157    public int getOrder() {
158        return mOrder;
159    }
160
161    /**
162     * Sets the order used to compute the delay of each child's animation.
163     *
164     * @param order one of {@link #ORDER_NORMAL}, {@link #ORDER_REVERSE} or
165     *        {@link #ORDER_RANDOM}
166     *
167     * @attr ref android.R.styleable#LayoutAnimation_animationOrder
168     */
169    public void setOrder(int order) {
170        mOrder = order;
171    }
172
173    /**
174     * Sets the animation to be run on each child of the view group on which
175     * this layout animation controller is .
176     *
177     * @param context the context from which the animation must be inflated
178     * @param resourceID the resource identifier of the animation
179     *
180     * @see #setAnimation(Animation)
181     * @see #getAnimation()
182     *
183     * @attr ref android.R.styleable#LayoutAnimation_animation
184     */
185    public void setAnimation(Context context, @AnimRes int resourceID) {
186        setAnimation(AnimationUtils.loadAnimation(context, resourceID));
187    }
188
189    /**
190     * Sets the animation to be run on each child of the view group on which
191     * this layout animation controller is .
192     *
193     * @param animation the animation to run on each child of the view group
194
195     * @see #setAnimation(android.content.Context, int)
196     * @see #getAnimation()
197     *
198     * @attr ref android.R.styleable#LayoutAnimation_animation
199     */
200    public void setAnimation(Animation animation) {
201        mAnimation = animation;
202        mAnimation.setFillBefore(true);
203    }
204
205    /**
206     * Returns the animation applied to each child of the view group on which
207     * this controller is set.
208     *
209     * @return an {@link android.view.animation.Animation} instance
210     *
211     * @see #setAnimation(android.content.Context, int)
212     * @see #setAnimation(Animation)
213     */
214    public Animation getAnimation() {
215        return mAnimation;
216    }
217
218    /**
219     * Sets the interpolator used to interpolate the delays between the
220     * children.
221     *
222     * @param context the context from which the interpolator must be inflated
223     * @param resourceID the resource identifier of the interpolator
224     *
225     * @see #getInterpolator()
226     * @see #setInterpolator(Interpolator)
227     *
228     * @attr ref android.R.styleable#LayoutAnimation_interpolator
229     */
230    public void setInterpolator(Context context, @InterpolatorRes int resourceID) {
231        setInterpolator(AnimationUtils.loadInterpolator(context, resourceID));
232    }
233
234    /**
235     * Sets the interpolator used to interpolate the delays between the
236     * children.
237     *
238     * @param interpolator the interpolator
239     *
240     * @see #getInterpolator()
241     * @see #setInterpolator(Interpolator)
242     *
243     * @attr ref android.R.styleable#LayoutAnimation_interpolator
244     */
245    public void setInterpolator(Interpolator interpolator) {
246        mInterpolator = interpolator;
247    }
248
249    /**
250     * Returns the interpolator used to interpolate the delays between the
251     * children.
252     *
253     * @return an {@link android.view.animation.Interpolator}
254     */
255    public Interpolator getInterpolator() {
256        return mInterpolator;
257    }
258
259    /**
260     * Returns the delay by which the children's animation are offset. The
261     * delay is expressed as a fraction of the animation duration.
262     *
263     * @return a fraction of the animation duration
264     *
265     * @see #setDelay(float)
266     */
267    public float getDelay() {
268        return mDelay;
269    }
270
271    /**
272     * Sets the delay, as a fraction of the animation duration, by which the
273     * children's animations are offset. The general formula is:
274     *
275     * <pre>
276     * child animation delay = child index * delay * animation duration
277     * </pre>
278     *
279     * @param delay a fraction of the animation duration
280     *
281     * @see #getDelay()
282     */
283    public void setDelay(float delay) {
284        mDelay = delay;
285    }
286
287    /**
288     * Indicates whether two children's animations will overlap. Animations
289     * overlap when the delay is lower than 100% (or 1.0).
290     *
291     * @return true if animations will overlap, false otherwise
292     */
293    public boolean willOverlap() {
294        return mDelay < 1.0f;
295    }
296
297    /**
298     * Starts the animation.
299     */
300    public void start() {
301        mDuration = mAnimation.getDuration();
302        mMaxDelay = Long.MIN_VALUE;
303        mAnimation.setStartTime(-1);
304    }
305
306    /**
307     * Returns the animation to be applied to the specified view. The returned
308     * animation is delayed by an offset computed according to the information
309     * provided by
310     * {@link android.view.animation.LayoutAnimationController.AnimationParameters}.
311     * This method is called by view groups to obtain the animation to set on
312     * a specific child.
313     *
314     * @param view the view to animate
315     * @return an animation delayed by the number of milliseconds returned by
316     *         {@link #getDelayForView(android.view.View)}
317     *
318     * @see #getDelay()
319     * @see #setDelay(float)
320     * @see #getDelayForView(android.view.View)
321     */
322    public final Animation getAnimationForView(View view) {
323        final long delay = getDelayForView(view) + mAnimation.getStartOffset();
324        mMaxDelay = Math.max(mMaxDelay, delay);
325
326        try {
327            final Animation animation = mAnimation.clone();
328            animation.setStartOffset(delay);
329            return animation;
330        } catch (CloneNotSupportedException e) {
331            return null;
332        }
333    }
334
335    /**
336     * Indicates whether the layout animation is over or not. A layout animation
337     * is considered done when the animation with the longest delay is done.
338     *
339     * @return true if all of the children's animations are over, false otherwise
340     */
341    public boolean isDone() {
342        return AnimationUtils.currentAnimationTimeMillis() >
343                mAnimation.getStartTime() + mMaxDelay + mDuration;
344    }
345
346    /**
347     * Returns the amount of milliseconds by which the specified view's
348     * animation must be delayed or offset. Subclasses should override this
349     * method to return a suitable value.
350     *
351     * This implementation returns <code>child animation delay</code>
352     * milliseconds where:
353     *
354     * <pre>
355     * child animation delay = child index * delay
356     * </pre>
357     *
358     * The index is retrieved from the
359     * {@link android.view.animation.LayoutAnimationController.AnimationParameters}
360     * found in the view's {@link android.view.ViewGroup.LayoutParams}.
361     *
362     * @param view the view for which to obtain the animation's delay
363     * @return a delay in milliseconds
364     *
365     * @see #getAnimationForView(android.view.View)
366     * @see #getDelay()
367     * @see #getTransformedIndex(android.view.animation.LayoutAnimationController.AnimationParameters)
368     * @see android.view.ViewGroup.LayoutParams
369     */
370    protected long getDelayForView(View view) {
371        ViewGroup.LayoutParams lp = view.getLayoutParams();
372        AnimationParameters params = lp.layoutAnimationParameters;
373
374        if (params == null) {
375            return 0;
376        }
377
378        final float delay = mDelay * mAnimation.getDuration();
379        final long viewDelay = (long) (getTransformedIndex(params) * delay);
380        final float totalDelay = delay * params.count;
381
382        if (mInterpolator == null) {
383            mInterpolator = new LinearInterpolator();
384        }
385
386        float normalizedDelay = viewDelay / totalDelay;
387        normalizedDelay = mInterpolator.getInterpolation(normalizedDelay);
388
389        return (long) (normalizedDelay * totalDelay);
390    }
391
392    /**
393     * Transforms the index stored in
394     * {@link android.view.animation.LayoutAnimationController.AnimationParameters}
395     * by the order returned by {@link #getOrder()}. Subclasses should override
396     * this method to provide additional support for other types of ordering.
397     * This method should be invoked by
398     * {@link #getDelayForView(android.view.View)} prior to any computation.
399     *
400     * @param params the animation parameters containing the index
401     * @return a transformed index
402     */
403    protected int getTransformedIndex(AnimationParameters params) {
404        switch (getOrder()) {
405            case ORDER_REVERSE:
406                return params.count - 1 - params.index;
407            case ORDER_RANDOM:
408                if (mRandomizer == null) {
409                    mRandomizer = new Random();
410                }
411                return (int) (params.count * mRandomizer.nextFloat());
412            case ORDER_NORMAL:
413            default:
414                return params.index;
415        }
416    }
417
418    /**
419     * The set of parameters that has to be attached to each view contained in
420     * the view group animated by the layout animation controller. These
421     * parameters are used to compute the start time of each individual view's
422     * animation.
423     */
424    public static class AnimationParameters {
425        /**
426         * The number of children in the view group containing the view to which
427         * these parameters are attached.
428         */
429        public int count;
430
431        /**
432         * The index of the view to which these parameters are attached in its
433         * containing view group.
434         */
435        public int index;
436    }
437}
438