/* * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.view.animation; import android.content.Context; import android.content.res.TypedArray; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; import java.util.Random; /** * A layout animation controller is used to animated a layout's, or a view * group's, children. Each child uses the same animation but for every one of * them, the animation starts at a different time. A layout animation controller * is used by {@link android.view.ViewGroup} to compute the delay by which each * child's animation start must be offset. The delay is computed by using * characteristics of each child, like its index in the view group. * * This standard implementation computes the delay by multiplying a fixed * amount of miliseconds by the index of the child in its parent view group. * Subclasses are supposed to override * {@link #getDelayForView(android.view.View)} to implement a different way * of computing the delay. For instance, a * {@link android.view.animation.GridLayoutAnimationController} will compute the * delay based on the column and row indices of the child in its parent view * group. * * Information used to compute the animation delay of each child are stored * in an instance of * {@link android.view.animation.LayoutAnimationController.AnimationParameters}, * itself stored in the {@link android.view.ViewGroup.LayoutParams} of the view. * * @attr ref android.R.styleable#LayoutAnimation_delay * @attr ref android.R.styleable#LayoutAnimation_animationOrder * @attr ref android.R.styleable#LayoutAnimation_interpolator * @attr ref android.R.styleable#LayoutAnimation_animation */ public class LayoutAnimationController { /** * Distributes the animation delays in the order in which view were added * to their view group. */ public static final int ORDER_NORMAL = 0; /** * Distributes the animation delays in the reverse order in which view were * added to their view group. */ public static final int ORDER_REVERSE = 1; /** * Randomly distributes the animation delays. */ public static final int ORDER_RANDOM = 2; /** * The animation applied on each child of the view group on which this * layout animation controller is set. */ protected Animation mAnimation; /** * The randomizer used when the order is set to random. Subclasses should * use this object to avoid creating their own. */ protected Random mRandomizer; /** * The interpolator used to interpolate the delays. */ protected Interpolator mInterpolator; private float mDelay; private int mOrder; private long mDuration; private long mMaxDelay; /** * Creates a new layout animation controller from external resources. * * @param context the Context the view group is running in, through which * it can access the resources * @param attrs the attributes of the XML tag that is inflating the * layout animation controller */ public LayoutAnimationController(Context context, AttributeSet attrs) { TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.LayoutAnimation); Animation.Description d = Animation.Description.parseValue( a.peekValue(com.android.internal.R.styleable.LayoutAnimation_delay)); mDelay = d.value; mOrder = a.getInt(com.android.internal.R.styleable.LayoutAnimation_animationOrder, ORDER_NORMAL); int resource = a.getResourceId(com.android.internal.R.styleable.LayoutAnimation_animation, 0); if (resource > 0) { setAnimation(context, resource); } resource = a.getResourceId(com.android.internal.R.styleable.LayoutAnimation_interpolator, 0); if (resource > 0) { setInterpolator(context, resource); } a.recycle(); } /** * Creates a new layout animation controller with a delay of 50% * and the specified animation. * * @param animation the animation to use on each child of the view group */ public LayoutAnimationController(Animation animation) { this(animation, 0.5f); } /** * Creates a new layout animation controller with the specified delay * and the specified animation. * * @param animation the animation to use on each child of the view group * @param delay the delay by which each child's animation must be offset */ public LayoutAnimationController(Animation animation, float delay) { mDelay = delay; setAnimation(animation); } /** * Returns the order used to compute the delay of each child's animation. * * @return one of {@link #ORDER_NORMAL}, {@link #ORDER_REVERSE} or * {@link #ORDER_RANDOM) * * @attr ref android.R.styleable#LayoutAnimation_animationOrder */ public int getOrder() { return mOrder; } /** * Sets the order used to compute the delay of each child's animation. * * @param order one of {@link #ORDER_NORMAL}, {@link #ORDER_REVERSE} or * {@link #ORDER_RANDOM} * * @attr ref android.R.styleable#LayoutAnimation_animationOrder */ public void setOrder(int order) { mOrder = order; } /** * Sets the animation to be run on each child of the view group on which * this layout animation controller is . * * @param context the context from which the animation must be inflated * @param resourceID the resource identifier of the animation * * @see #setAnimation(Animation) * @see #getAnimation() * * @attr ref android.R.styleable#LayoutAnimation_animation */ public void setAnimation(Context context, int resourceID) { setAnimation(AnimationUtils.loadAnimation(context, resourceID)); } /** * Sets the animation to be run on each child of the view group on which * this layout animation controller is . * * @param animation the animation to run on each child of the view group * @see #setAnimation(android.content.Context, int) * @see #getAnimation() * * @attr ref android.R.styleable#LayoutAnimation_animation */ public void setAnimation(Animation animation) { mAnimation = animation; mAnimation.setFillBefore(true); } /** * Returns the animation applied to each child of the view group on which * this controller is set. * * @return an {@link android.view.animation.Animation} instance * * @see #setAnimation(android.content.Context, int) * @see #setAnimation(Animation) */ public Animation getAnimation() { return mAnimation; } /** * Sets the interpolator used to interpolate the delays between the * children. * * @param context the context from which the interpolator must be inflated * @param resourceID the resource identifier of the interpolator * * @see #getInterpolator() * @see #setInterpolator(Interpolator) * * @attr ref android.R.styleable#LayoutAnimation_interpolator */ public void setInterpolator(Context context, int resourceID) { setInterpolator(AnimationUtils.loadInterpolator(context, resourceID)); } /** * Sets the interpolator used to interpolate the delays between the * children. * * @param interpolator the interpolator * * @see #getInterpolator() * @see #setInterpolator(Interpolator) * * @attr ref android.R.styleable#LayoutAnimation_interpolator */ public void setInterpolator(Interpolator interpolator) { mInterpolator = interpolator; } /** * Returns the interpolator used to interpolate the delays between the * children. * * @return an {@link android.view.animation.Interpolator} */ public Interpolator getInterpolator() { return mInterpolator; } /** * Returns the delay by which the children's animation are offset. The * delay is expressed as a fraction of the animation duration. * * @return a fraction of the animation duration * * @see #setDelay(float) */ public float getDelay() { return mDelay; } /** * Sets the delay, as a fraction of the animation duration, by which the * children's animations are offset. The general formula is: * *
* child animation delay = child index * delay * animation duration ** * @param delay a fraction of the animation duration * * @see #getDelay() */ public void setDelay(float delay) { mDelay = delay; } /** * Indicates whether two children's animations will overlap. Animations * overlap when the delay is lower than 100% (or 1.0). * * @return true if animations will overlap, false otherwise */ public boolean willOverlap() { return mDelay < 1.0f; } /** * Starts the animation. */ public void start() { mDuration = mAnimation.getDuration(); mMaxDelay = Long.MIN_VALUE; mAnimation.setStartTime(-1); } /** * Returns the animation to be applied to the specified view. The returned * animation is delayed by an offset computed according to the information * provided by * {@link android.view.animation.LayoutAnimationController.AnimationParameters}. * This method is called by view groups to obtain the animation to set on * a specific child. * * @param view the view to animate * @return an animation delayed by the number of milliseconds returned by * {@link #getDelayForView(android.view.View)} * * @see #getDelay() * @see #setDelay(float) * @see #getDelayForView(android.view.View) */ public final Animation getAnimationForView(View view) { final long delay = getDelayForView(view) + mAnimation.getStartOffset(); mMaxDelay = Math.max(mMaxDelay, delay); try { final Animation animation = mAnimation.clone(); animation.setStartOffset(delay); return animation; } catch (CloneNotSupportedException e) { return null; } } /** * Indicates whether the layout animation is over or not. A layout animation * is considered done when the animation with the longest delay is done. * * @return true if all of the children's animations are over, false otherwise */ public boolean isDone() { return AnimationUtils.currentAnimationTimeMillis() > mAnimation.getStartTime() + mMaxDelay + mDuration; } /** * Returns the amount of milliseconds by which the specified view's * animation must be delayed or offset. Subclasses should override this * method to return a suitable value. * * This implementation returns
child animation delay
* milliseconds where:
*
* * child animation delay = child index * delay ** * The index is retrieved from the * {@link android.view.animation.LayoutAnimationController.AnimationParameters} * found in the view's {@link android.view.ViewGroup.LayoutParams}. * * @param view the view for which to obtain the animation's delay * @return a delay in milliseconds * * @see #getAnimationForView(android.view.View) * @see #getDelay() * @see #getTransformedIndex(android.view.animation.LayoutAnimationController.AnimationParameters) * @see android.view.ViewGroup.LayoutParams */ protected long getDelayForView(View view) { ViewGroup.LayoutParams lp = view.getLayoutParams(); AnimationParameters params = lp.layoutAnimationParameters; if (params == null) { return 0; } final float delay = mDelay * mAnimation.getDuration(); final long viewDelay = (long) (getTransformedIndex(params) * delay); final float totalDelay = delay * params.count; if (mInterpolator == null) { mInterpolator = new LinearInterpolator(); } float normalizedDelay = viewDelay / totalDelay; normalizedDelay = mInterpolator.getInterpolation(normalizedDelay); return (long) (normalizedDelay * totalDelay); } /** * Transforms the index stored in * {@link android.view.animation.LayoutAnimationController.AnimationParameters} * by the order returned by {@link #getOrder()}. Subclasses should override * this method to provide additional support for other types of ordering. * This method should be invoked by * {@link #getDelayForView(android.view.View)} prior to any computation. * * @param params the animation parameters containing the index * @return a transformed index */ protected int getTransformedIndex(AnimationParameters params) { switch (getOrder()) { case ORDER_REVERSE: return params.count - 1 - params.index; case ORDER_RANDOM: if (mRandomizer == null) { mRandomizer = new Random(); } return (int) (params.count * mRandomizer.nextFloat()); case ORDER_NORMAL: default: return params.index; } } /** * The set of parameters that has to be attached to each view contained in * the view group animated by the layout animation controller. These * parameters are used to compute the start time of each individual view's * animation. */ public static class AnimationParameters { /** * The number of children in the view group containing the view to which * these parameters are attached. */ public int count; /** * The index of the view to which these parameters are attached in its * containing view group. */ public int index; } }