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