| /* |
| * Copyright (C) 2011 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; |
| |
| import android.animation.Animator; |
| import android.animation.TimeInterpolator; |
| import android.animation.ValueAnimator; |
| import android.graphics.RenderNode; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.Set; |
| |
| /** |
| * This class enables automatic and optimized animation of select properties on View objects. |
| * If only one or two properties on a View object are being animated, then using an |
| * {@link android.animation.ObjectAnimator} is fine; the property setters called by ObjectAnimator |
| * are well equipped to do the right thing to set the property and invalidate the view |
| * appropriately. But if several properties are animated simultaneously, or if you just want a |
| * more convenient syntax to animate a specific property, then ViewPropertyAnimator might be |
| * more well-suited to the task. |
| * |
| * <p>This class may provide better performance for several simultaneous animations, because |
| * it will optimize invalidate calls to take place only once for several properties instead of each |
| * animated property independently causing its own invalidation. Also, the syntax of using this |
| * class could be easier to use because the caller need only tell the View object which |
| * property to animate, and the value to animate either to or by, and this class handles the |
| * details of configuring the underlying Animator class and starting it.</p> |
| * |
| * <p>This class is not constructed by the caller, but rather by the View whose properties |
| * it will animate. Calls to {@link android.view.View#animate()} will return a reference |
| * to the appropriate ViewPropertyAnimator object for that View.</p> |
| * |
| */ |
| public class ViewPropertyAnimator { |
| |
| /** |
| * The View whose properties are being animated by this class. This is set at |
| * construction time. |
| */ |
| final View mView; |
| |
| /** |
| * The duration of the underlying Animator object. By default, we don't set the duration |
| * on the Animator and just use its default duration. If the duration is ever set on this |
| * Animator, then we use the duration that it was set to. |
| */ |
| private long mDuration; |
| |
| /** |
| * A flag indicating whether the duration has been set on this object. If not, we don't set |
| * the duration on the underlying Animator, but instead just use its default duration. |
| */ |
| private boolean mDurationSet = false; |
| |
| /** |
| * The startDelay of the underlying Animator object. By default, we don't set the startDelay |
| * on the Animator and just use its default startDelay. If the startDelay is ever set on this |
| * Animator, then we use the startDelay that it was set to. |
| */ |
| private long mStartDelay = 0; |
| |
| /** |
| * A flag indicating whether the startDelay has been set on this object. If not, we don't set |
| * the startDelay on the underlying Animator, but instead just use its default startDelay. |
| */ |
| private boolean mStartDelaySet = false; |
| |
| /** |
| * The interpolator of the underlying Animator object. By default, we don't set the interpolator |
| * on the Animator and just use its default interpolator. If the interpolator is ever set on |
| * this Animator, then we use the interpolator that it was set to. |
| */ |
| private TimeInterpolator mInterpolator; |
| |
| /** |
| * A flag indicating whether the interpolator has been set on this object. If not, we don't set |
| * the interpolator on the underlying Animator, but instead just use its default interpolator. |
| */ |
| private boolean mInterpolatorSet = false; |
| |
| /** |
| * Listener for the lifecycle events of the underlying ValueAnimator object. |
| */ |
| private Animator.AnimatorListener mListener = null; |
| |
| /** |
| * Listener for the update events of the underlying ValueAnimator object. |
| */ |
| private ValueAnimator.AnimatorUpdateListener mUpdateListener = null; |
| |
| /** |
| * A lazily-created ValueAnimator used in order to get some default animator properties |
| * (duration, start delay, interpolator, etc.). |
| */ |
| private ValueAnimator mTempValueAnimator; |
| |
| /** |
| * This listener is the mechanism by which the underlying Animator causes changes to the |
| * properties currently being animated, as well as the cleanup after an animation is |
| * complete. |
| */ |
| private AnimatorEventListener mAnimatorEventListener = new AnimatorEventListener(); |
| |
| /** |
| * This list holds the properties that have been asked to animate. We allow the caller to |
| * request several animations prior to actually starting the underlying animator. This |
| * enables us to run one single animator to handle several properties in parallel. Each |
| * property is tossed onto the pending list until the animation actually starts (which is |
| * done by posting it onto mView), at which time the pending list is cleared and the properties |
| * on that list are added to the list of properties associated with that animator. |
| */ |
| ArrayList<NameValuesHolder> mPendingAnimations = new ArrayList<NameValuesHolder>(); |
| private Runnable mPendingSetupAction; |
| private Runnable mPendingCleanupAction; |
| private Runnable mPendingOnStartAction; |
| private Runnable mPendingOnEndAction; |
| |
| /** |
| * Constants used to associate a property being requested and the mechanism used to set |
| * the property (this class calls directly into View to set the properties in question). |
| */ |
| static final int NONE = 0x0000; |
| static final int TRANSLATION_X = 0x0001; |
| static final int TRANSLATION_Y = 0x0002; |
| static final int TRANSLATION_Z = 0x0004; |
| static final int SCALE_X = 0x0008; |
| static final int SCALE_Y = 0x0010; |
| static final int ROTATION = 0x0020; |
| static final int ROTATION_X = 0x0040; |
| static final int ROTATION_Y = 0x0080; |
| static final int X = 0x0100; |
| static final int Y = 0x0200; |
| static final int Z = 0x0400; |
| static final int ALPHA = 0x0800; |
| |
| private static final int TRANSFORM_MASK = TRANSLATION_X | TRANSLATION_Y | TRANSLATION_Z | |
| SCALE_X | SCALE_Y | ROTATION | ROTATION_X | ROTATION_Y | X | Y | Z; |
| |
| /** |
| * The mechanism by which the user can request several properties that are then animated |
| * together works by posting this Runnable to start the underlying Animator. Every time |
| * a property animation is requested, we cancel any previous postings of the Runnable |
| * and re-post it. This means that we will only ever run the Runnable (and thus start the |
| * underlying animator) after the caller is done setting the properties that should be |
| * animated together. |
| */ |
| private Runnable mAnimationStarter = new Runnable() { |
| @Override |
| public void run() { |
| startAnimation(); |
| } |
| }; |
| |
| /** |
| * This class holds information about the overall animation being run on the set of |
| * properties. The mask describes which properties are being animated and the |
| * values holder is the list of all property/value objects. |
| */ |
| private static class PropertyBundle { |
| int mPropertyMask; |
| ArrayList<NameValuesHolder> mNameValuesHolder; |
| |
| PropertyBundle(int propertyMask, ArrayList<NameValuesHolder> nameValuesHolder) { |
| mPropertyMask = propertyMask; |
| mNameValuesHolder = nameValuesHolder; |
| } |
| |
| /** |
| * Removes the given property from being animated as a part of this |
| * PropertyBundle. If the property was a part of this bundle, it returns |
| * true to indicate that it was, in fact, canceled. This is an indication |
| * to the caller that a cancellation actually occurred. |
| * |
| * @param propertyConstant The property whose cancellation is requested. |
| * @return true if the given property is a part of this bundle and if it |
| * has therefore been canceled. |
| */ |
| boolean cancel(int propertyConstant) { |
| if ((mPropertyMask & propertyConstant) != 0 && mNameValuesHolder != null) { |
| int count = mNameValuesHolder.size(); |
| for (int i = 0; i < count; ++i) { |
| NameValuesHolder nameValuesHolder = mNameValuesHolder.get(i); |
| if (nameValuesHolder.mNameConstant == propertyConstant) { |
| mNameValuesHolder.remove(i); |
| mPropertyMask &= ~propertyConstant; |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| } |
| |
| /** |
| * This list tracks the list of properties being animated by any particular animator. |
| * In most situations, there would only ever be one animator running at a time. But it is |
| * possible to request some properties to animate together, then while those properties |
| * are animating, to request some other properties to animate together. The way that |
| * works is by having this map associate the group of properties being animated with the |
| * animator handling the animation. On every update event for an Animator, we ask the |
| * map for the associated properties and set them accordingly. |
| */ |
| private HashMap<Animator, PropertyBundle> mAnimatorMap = |
| new HashMap<Animator, PropertyBundle>(); |
| private HashMap<Animator, Runnable> mAnimatorSetupMap; |
| private HashMap<Animator, Runnable> mAnimatorCleanupMap; |
| private HashMap<Animator, Runnable> mAnimatorOnStartMap; |
| private HashMap<Animator, Runnable> mAnimatorOnEndMap; |
| |
| /** |
| * This is the information we need to set each property during the animation. |
| * mNameConstant is used to set the appropriate field in View, and the from/delta |
| * values are used to calculate the animated value for a given animation fraction |
| * during the animation. |
| */ |
| static class NameValuesHolder { |
| int mNameConstant; |
| float mFromValue; |
| float mDeltaValue; |
| NameValuesHolder(int nameConstant, float fromValue, float deltaValue) { |
| mNameConstant = nameConstant; |
| mFromValue = fromValue; |
| mDeltaValue = deltaValue; |
| } |
| } |
| |
| /** |
| * Constructor, called by View. This is private by design, as the user should only |
| * get a ViewPropertyAnimator by calling View.animate(). |
| * |
| * @param view The View associated with this ViewPropertyAnimator |
| */ |
| ViewPropertyAnimator(View view) { |
| mView = view; |
| view.ensureTransformationInfo(); |
| } |
| |
| /** |
| * Sets the duration for the underlying animator that animates the requested properties. |
| * By default, the animator uses the default value for ValueAnimator. Calling this method |
| * will cause the declared value to be used instead. |
| * @param duration The length of ensuing property animations, in milliseconds. The value |
| * cannot be negative. |
| * @return This object, allowing calls to methods in this class to be chained. |
| */ |
| public ViewPropertyAnimator setDuration(long duration) { |
| if (duration < 0) { |
| throw new IllegalArgumentException("Animators cannot have negative duration: " + |
| duration); |
| } |
| mDurationSet = true; |
| mDuration = duration; |
| return this; |
| } |
| |
| /** |
| * Returns the current duration of property animations. If the duration was set on this |
| * object, that value is returned. Otherwise, the default value of the underlying Animator |
| * is returned. |
| * |
| * @see #setDuration(long) |
| * @return The duration of animations, in milliseconds. |
| */ |
| public long getDuration() { |
| if (mDurationSet) { |
| return mDuration; |
| } else { |
| // Just return the default from ValueAnimator, since that's what we'd get if |
| // the value has not been set otherwise |
| if (mTempValueAnimator == null) { |
| mTempValueAnimator = new ValueAnimator(); |
| } |
| return mTempValueAnimator.getDuration(); |
| } |
| } |
| |
| /** |
| * Returns the current startDelay of property animations. If the startDelay was set on this |
| * object, that value is returned. Otherwise, the default value of the underlying Animator |
| * is returned. |
| * |
| * @see #setStartDelay(long) |
| * @return The startDelay of animations, in milliseconds. |
| */ |
| public long getStartDelay() { |
| if (mStartDelaySet) { |
| return mStartDelay; |
| } else { |
| // Just return the default from ValueAnimator (0), since that's what we'd get if |
| // the value has not been set otherwise |
| return 0; |
| } |
| } |
| |
| /** |
| * Sets the startDelay for the underlying animator that animates the requested properties. |
| * By default, the animator uses the default value for ValueAnimator. Calling this method |
| * will cause the declared value to be used instead. |
| * @param startDelay The delay of ensuing property animations, in milliseconds. The value |
| * cannot be negative. |
| * @return This object, allowing calls to methods in this class to be chained. |
| */ |
| public ViewPropertyAnimator setStartDelay(long startDelay) { |
| if (startDelay < 0) { |
| throw new IllegalArgumentException("Animators cannot have negative start " + |
| "delay: " + startDelay); |
| } |
| mStartDelaySet = true; |
| mStartDelay = startDelay; |
| return this; |
| } |
| |
| /** |
| * Sets the interpolator for the underlying animator that animates the requested properties. |
| * By default, the animator uses the default interpolator for ValueAnimator. Calling this method |
| * will cause the declared object to be used instead. |
| * |
| * @param interpolator The TimeInterpolator to be used for ensuing property animations. A value |
| * of <code>null</code> will result in linear interpolation. |
| * @return This object, allowing calls to methods in this class to be chained. |
| */ |
| public ViewPropertyAnimator setInterpolator(TimeInterpolator interpolator) { |
| mInterpolatorSet = true; |
| mInterpolator = interpolator; |
| return this; |
| } |
| |
| /** |
| * Returns the timing interpolator that this animation uses. |
| * |
| * @return The timing interpolator for this animation. |
| */ |
| public TimeInterpolator getInterpolator() { |
| if (mInterpolatorSet) { |
| return mInterpolator; |
| } else { |
| // Just return the default from ValueAnimator, since that's what we'd get if |
| // the value has not been set otherwise |
| if (mTempValueAnimator == null) { |
| mTempValueAnimator = new ValueAnimator(); |
| } |
| return mTempValueAnimator.getInterpolator(); |
| } |
| } |
| |
| /** |
| * Sets a listener for events in the underlying Animators that run the property |
| * animations. |
| * |
| * @see Animator.AnimatorListener |
| * |
| * @param listener The listener to be called with AnimatorListener events. A value of |
| * <code>null</code> removes any existing listener. |
| * @return This object, allowing calls to methods in this class to be chained. |
| */ |
| public ViewPropertyAnimator setListener(Animator.AnimatorListener listener) { |
| mListener = listener; |
| return this; |
| } |
| |
| Animator.AnimatorListener getListener() { |
| return mListener; |
| } |
| |
| /** |
| * Sets a listener for update events in the underlying ValueAnimator that runs |
| * the property animations. Note that the underlying animator is animating between |
| * 0 and 1 (these values are then turned into the actual property values internally |
| * by ViewPropertyAnimator). So the animator cannot give information on the current |
| * values of the properties being animated by this ViewPropertyAnimator, although |
| * the view object itself can be queried to get the current values. |
| * |
| * @see android.animation.ValueAnimator.AnimatorUpdateListener |
| * |
| * @param listener The listener to be called with update events. A value of |
| * <code>null</code> removes any existing listener. |
| * @return This object, allowing calls to methods in this class to be chained. |
| */ |
| public ViewPropertyAnimator setUpdateListener(ValueAnimator.AnimatorUpdateListener listener) { |
| mUpdateListener = listener; |
| return this; |
| } |
| |
| ValueAnimator.AnimatorUpdateListener getUpdateListener() { |
| return mUpdateListener; |
| } |
| |
| /** |
| * Starts the currently pending property animations immediately. Calling <code>start()</code> |
| * is optional because all animations start automatically at the next opportunity. However, |
| * if the animations are needed to start immediately and synchronously (not at the time when |
| * the next event is processed by the hierarchy, which is when the animations would begin |
| * otherwise), then this method can be used. |
| */ |
| public void start() { |
| mView.removeCallbacks(mAnimationStarter); |
| startAnimation(); |
| } |
| |
| /** |
| * Cancels all property animations that are currently running or pending. |
| */ |
| public void cancel() { |
| if (mAnimatorMap.size() > 0) { |
| HashMap<Animator, PropertyBundle> mAnimatorMapCopy = |
| (HashMap<Animator, PropertyBundle>)mAnimatorMap.clone(); |
| Set<Animator> animatorSet = mAnimatorMapCopy.keySet(); |
| for (Animator runningAnim : animatorSet) { |
| runningAnim.cancel(); |
| } |
| } |
| mPendingAnimations.clear(); |
| mPendingSetupAction = null; |
| mPendingCleanupAction = null; |
| mPendingOnStartAction = null; |
| mPendingOnEndAction = null; |
| mView.removeCallbacks(mAnimationStarter); |
| } |
| |
| /** |
| * This method will cause the View's <code>x</code> property to be animated to the |
| * specified value. Animations already running on the property will be canceled. |
| * |
| * @param value The value to be animated to. |
| * @see View#setX(float) |
| * @return This object, allowing calls to methods in this class to be chained. |
| */ |
| public ViewPropertyAnimator x(float value) { |
| animateProperty(X, value); |
| return this; |
| } |
| |
| /** |
| * This method will cause the View's <code>x</code> property to be animated by the |
| * specified value. Animations already running on the property will be canceled. |
| * |
| * @param value The amount to be animated by, as an offset from the current value. |
| * @see View#setX(float) |
| * @return This object, allowing calls to methods in this class to be chained. |
| */ |
| public ViewPropertyAnimator xBy(float value) { |
| animatePropertyBy(X, value); |
| return this; |
| } |
| |
| /** |
| * This method will cause the View's <code>y</code> property to be animated to the |
| * specified value. Animations already running on the property will be canceled. |
| * |
| * @param value The value to be animated to. |
| * @see View#setY(float) |
| * @return This object, allowing calls to methods in this class to be chained. |
| */ |
| public ViewPropertyAnimator y(float value) { |
| animateProperty(Y, value); |
| return this; |
| } |
| |
| /** |
| * This method will cause the View's <code>y</code> property to be animated by the |
| * specified value. Animations already running on the property will be canceled. |
| * |
| * @param value The amount to be animated by, as an offset from the current value. |
| * @see View#setY(float) |
| * @return This object, allowing calls to methods in this class to be chained. |
| */ |
| public ViewPropertyAnimator yBy(float value) { |
| animatePropertyBy(Y, value); |
| return this; |
| } |
| |
| /** |
| * This method will cause the View's <code>z</code> property to be animated to the |
| * specified value. Animations already running on the property will be canceled. |
| * |
| * @param value The value to be animated to. |
| * @see View#setZ(float) |
| * @return This object, allowing calls to methods in this class to be chained. |
| */ |
| public ViewPropertyAnimator z(float value) { |
| animateProperty(Z, value); |
| return this; |
| } |
| |
| /** |
| * This method will cause the View's <code>z</code> property to be animated by the |
| * specified value. Animations already running on the property will be canceled. |
| * |
| * @param value The amount to be animated by, as an offset from the current value. |
| * @see View#setZ(float) |
| * @return This object, allowing calls to methods in this class to be chained. |
| */ |
| public ViewPropertyAnimator zBy(float value) { |
| animatePropertyBy(Z, value); |
| return this; |
| } |
| |
| /** |
| * This method will cause the View's <code>rotation</code> property to be animated to the |
| * specified value. Animations already running on the property will be canceled. |
| * |
| * @param value The value to be animated to. |
| * @see View#setRotation(float) |
| * @return This object, allowing calls to methods in this class to be chained. |
| */ |
| public ViewPropertyAnimator rotation(float value) { |
| animateProperty(ROTATION, value); |
| return this; |
| } |
| |
| /** |
| * This method will cause the View's <code>rotation</code> property to be animated by the |
| * specified value. Animations already running on the property will be canceled. |
| * |
| * @param value The amount to be animated by, as an offset from the current value. |
| * @see View#setRotation(float) |
| * @return This object, allowing calls to methods in this class to be chained. |
| */ |
| public ViewPropertyAnimator rotationBy(float value) { |
| animatePropertyBy(ROTATION, value); |
| return this; |
| } |
| |
| /** |
| * This method will cause the View's <code>rotationX</code> property to be animated to the |
| * specified value. Animations already running on the property will be canceled. |
| * |
| * @param value The value to be animated to. |
| * @see View#setRotationX(float) |
| * @return This object, allowing calls to methods in this class to be chained. |
| */ |
| public ViewPropertyAnimator rotationX(float value) { |
| animateProperty(ROTATION_X, value); |
| return this; |
| } |
| |
| /** |
| * This method will cause the View's <code>rotationX</code> property to be animated by the |
| * specified value. Animations already running on the property will be canceled. |
| * |
| * @param value The amount to be animated by, as an offset from the current value. |
| * @see View#setRotationX(float) |
| * @return This object, allowing calls to methods in this class to be chained. |
| */ |
| public ViewPropertyAnimator rotationXBy(float value) { |
| animatePropertyBy(ROTATION_X, value); |
| return this; |
| } |
| |
| /** |
| * This method will cause the View's <code>rotationY</code> property to be animated to the |
| * specified value. Animations already running on the property will be canceled. |
| * |
| * @param value The value to be animated to. |
| * @see View#setRotationY(float) |
| * @return This object, allowing calls to methods in this class to be chained. |
| */ |
| public ViewPropertyAnimator rotationY(float value) { |
| animateProperty(ROTATION_Y, value); |
| return this; |
| } |
| |
| /** |
| * This method will cause the View's <code>rotationY</code> property to be animated by the |
| * specified value. Animations already running on the property will be canceled. |
| * |
| * @param value The amount to be animated by, as an offset from the current value. |
| * @see View#setRotationY(float) |
| * @return This object, allowing calls to methods in this class to be chained. |
| */ |
| public ViewPropertyAnimator rotationYBy(float value) { |
| animatePropertyBy(ROTATION_Y, value); |
| return this; |
| } |
| |
| /** |
| * This method will cause the View's <code>translationX</code> property to be animated to the |
| * specified value. Animations already running on the property will be canceled. |
| * |
| * @param value The value to be animated to. |
| * @see View#setTranslationX(float) |
| * @return This object, allowing calls to methods in this class to be chained. |
| */ |
| public ViewPropertyAnimator translationX(float value) { |
| animateProperty(TRANSLATION_X, value); |
| return this; |
| } |
| |
| /** |
| * This method will cause the View's <code>translationX</code> property to be animated by the |
| * specified value. Animations already running on the property will be canceled. |
| * |
| * @param value The amount to be animated by, as an offset from the current value. |
| * @see View#setTranslationX(float) |
| * @return This object, allowing calls to methods in this class to be chained. |
| */ |
| public ViewPropertyAnimator translationXBy(float value) { |
| animatePropertyBy(TRANSLATION_X, value); |
| return this; |
| } |
| |
| /** |
| * This method will cause the View's <code>translationY</code> property to be animated to the |
| * specified value. Animations already running on the property will be canceled. |
| * |
| * @param value The value to be animated to. |
| * @see View#setTranslationY(float) |
| * @return This object, allowing calls to methods in this class to be chained. |
| */ |
| public ViewPropertyAnimator translationY(float value) { |
| animateProperty(TRANSLATION_Y, value); |
| return this; |
| } |
| |
| /** |
| * This method will cause the View's <code>translationY</code> property to be animated by the |
| * specified value. Animations already running on the property will be canceled. |
| * |
| * @param value The amount to be animated by, as an offset from the current value. |
| * @see View#setTranslationY(float) |
| * @return This object, allowing calls to methods in this class to be chained. |
| */ |
| public ViewPropertyAnimator translationYBy(float value) { |
| animatePropertyBy(TRANSLATION_Y, value); |
| return this; |
| } |
| |
| /** |
| * This method will cause the View's <code>translationZ</code> property to be animated to the |
| * specified value. Animations already running on the property will be canceled. |
| * |
| * @param value The value to be animated to. |
| * @see View#setTranslationZ(float) |
| * @return This object, allowing calls to methods in this class to be chained. |
| */ |
| public ViewPropertyAnimator translationZ(float value) { |
| animateProperty(TRANSLATION_Z, value); |
| return this; |
| } |
| |
| /** |
| * This method will cause the View's <code>translationZ</code> property to be animated by the |
| * specified value. Animations already running on the property will be canceled. |
| * |
| * @param value The amount to be animated by, as an offset from the current value. |
| * @see View#setTranslationZ(float) |
| * @return This object, allowing calls to methods in this class to be chained. |
| */ |
| public ViewPropertyAnimator translationZBy(float value) { |
| animatePropertyBy(TRANSLATION_Z, value); |
| return this; |
| } |
| /** |
| * This method will cause the View's <code>scaleX</code> property to be animated to the |
| * specified value. Animations already running on the property will be canceled. |
| * |
| * @param value The value to be animated to. |
| * @see View#setScaleX(float) |
| * @return This object, allowing calls to methods in this class to be chained. |
| */ |
| public ViewPropertyAnimator scaleX(float value) { |
| animateProperty(SCALE_X, value); |
| return this; |
| } |
| |
| /** |
| * This method will cause the View's <code>scaleX</code> property to be animated by the |
| * specified value. Animations already running on the property will be canceled. |
| * |
| * @param value The amount to be animated by, as an offset from the current value. |
| * @see View#setScaleX(float) |
| * @return This object, allowing calls to methods in this class to be chained. |
| */ |
| public ViewPropertyAnimator scaleXBy(float value) { |
| animatePropertyBy(SCALE_X, value); |
| return this; |
| } |
| |
| /** |
| * This method will cause the View's <code>scaleY</code> property to be animated to the |
| * specified value. Animations already running on the property will be canceled. |
| * |
| * @param value The value to be animated to. |
| * @see View#setScaleY(float) |
| * @return This object, allowing calls to methods in this class to be chained. |
| */ |
| public ViewPropertyAnimator scaleY(float value) { |
| animateProperty(SCALE_Y, value); |
| return this; |
| } |
| |
| /** |
| * This method will cause the View's <code>scaleY</code> property to be animated by the |
| * specified value. Animations already running on the property will be canceled. |
| * |
| * @param value The amount to be animated by, as an offset from the current value. |
| * @see View#setScaleY(float) |
| * @return This object, allowing calls to methods in this class to be chained. |
| */ |
| public ViewPropertyAnimator scaleYBy(float value) { |
| animatePropertyBy(SCALE_Y, value); |
| return this; |
| } |
| |
| /** |
| * This method will cause the View's <code>alpha</code> property to be animated to the |
| * specified value. Animations already running on the property will be canceled. |
| * |
| * @param value The value to be animated to. |
| * @see View#setAlpha(float) |
| * @return This object, allowing calls to methods in this class to be chained. |
| */ |
| public ViewPropertyAnimator alpha(float value) { |
| animateProperty(ALPHA, value); |
| return this; |
| } |
| |
| /** |
| * This method will cause the View's <code>alpha</code> property to be animated by the |
| * specified value. Animations already running on the property will be canceled. |
| * |
| * @param value The amount to be animated by, as an offset from the current value. |
| * @see View#setAlpha(float) |
| * @return This object, allowing calls to methods in this class to be chained. |
| */ |
| public ViewPropertyAnimator alphaBy(float value) { |
| animatePropertyBy(ALPHA, value); |
| return this; |
| } |
| |
| /** |
| * The View associated with this ViewPropertyAnimator will have its |
| * {@link View#setLayerType(int, android.graphics.Paint) layer type} set to |
| * {@link View#LAYER_TYPE_HARDWARE} for the duration of the next animation. |
| * As stated in the documentation for {@link View#LAYER_TYPE_HARDWARE}, |
| * the actual type of layer used internally depends on the runtime situation of the |
| * view. If the activity and this view are hardware-accelerated, then the layer will be |
| * accelerated as well. If the activity or the view is not accelerated, then the layer will |
| * effectively be the same as {@link View#LAYER_TYPE_SOFTWARE}. |
| * |
| * <p>This state is not persistent, either on the View or on this ViewPropertyAnimator: the |
| * layer type of the View will be restored when the animation ends to what it was when this |
| * method was called, and this setting on ViewPropertyAnimator is only valid for the next |
| * animation. Note that calling this method and then independently setting the layer type of |
| * the View (by a direct call to {@link View#setLayerType(int, android.graphics.Paint)}) will |
| * result in some inconsistency, including having the layer type restored to its pre-withLayer() |
| * value when the animation ends.</p> |
| * |
| * @see View#setLayerType(int, android.graphics.Paint) |
| * @return This object, allowing calls to methods in this class to be chained. |
| */ |
| public ViewPropertyAnimator withLayer() { |
| mPendingSetupAction= new Runnable() { |
| @Override |
| public void run() { |
| mView.setLayerType(View.LAYER_TYPE_HARDWARE, null); |
| if (mView.isAttachedToWindow()) { |
| mView.buildLayer(); |
| } |
| } |
| }; |
| final int currentLayerType = mView.getLayerType(); |
| mPendingCleanupAction = new Runnable() { |
| @Override |
| public void run() { |
| mView.setLayerType(currentLayerType, null); |
| } |
| }; |
| if (mAnimatorSetupMap == null) { |
| mAnimatorSetupMap = new HashMap<Animator, Runnable>(); |
| } |
| if (mAnimatorCleanupMap == null) { |
| mAnimatorCleanupMap = new HashMap<Animator, Runnable>(); |
| } |
| |
| return this; |
| } |
| |
| /** |
| * Specifies an action to take place when the next animation runs. If there is a |
| * {@link #setStartDelay(long) startDelay} set on this ViewPropertyAnimator, then the |
| * action will run after that startDelay expires, when the actual animation begins. |
| * This method, along with {@link #withEndAction(Runnable)}, is intended to help facilitate |
| * choreographing ViewPropertyAnimator animations with other animations or actions |
| * in the application. |
| * |
| * @param runnable The action to run when the next animation starts. |
| * @return This object, allowing calls to methods in this class to be chained. |
| */ |
| public ViewPropertyAnimator withStartAction(Runnable runnable) { |
| mPendingOnStartAction = runnable; |
| if (runnable != null && mAnimatorOnStartMap == null) { |
| mAnimatorOnStartMap = new HashMap<Animator, Runnable>(); |
| } |
| return this; |
| } |
| |
| /** |
| * Specifies an action to take place when the next animation ends. The action is only |
| * run if the animation ends normally; if the ViewPropertyAnimator is canceled during |
| * that animation, the runnable will not run. |
| * This method, along with {@link #withStartAction(Runnable)}, is intended to help facilitate |
| * choreographing ViewPropertyAnimator animations with other animations or actions |
| * in the application. |
| * |
| * <p>For example, the following code animates a view to x=200 and then back to 0:</p> |
| * <pre> |
| * Runnable endAction = new Runnable() { |
| * public void run() { |
| * view.animate().x(0); |
| * } |
| * }; |
| * view.animate().x(200).withEndAction(endAction); |
| * </pre> |
| * |
| * @param runnable The action to run when the next animation ends. |
| * @return This object, allowing calls to methods in this class to be chained. |
| */ |
| public ViewPropertyAnimator withEndAction(Runnable runnable) { |
| mPendingOnEndAction = runnable; |
| if (runnable != null && mAnimatorOnEndMap == null) { |
| mAnimatorOnEndMap = new HashMap<Animator, Runnable>(); |
| } |
| return this; |
| } |
| |
| boolean hasActions() { |
| return mPendingSetupAction != null |
| || mPendingCleanupAction != null |
| || mPendingOnStartAction != null |
| || mPendingOnEndAction != null; |
| } |
| |
| /** |
| * Starts the underlying Animator for a set of properties. We use a single animator that |
| * simply runs from 0 to 1, and then use that fractional value to set each property |
| * value accordingly. |
| */ |
| private void startAnimation() { |
| mView.setHasTransientState(true); |
| ValueAnimator animator = ValueAnimator.ofFloat(1.0f); |
| ArrayList<NameValuesHolder> nameValueList = |
| (ArrayList<NameValuesHolder>) mPendingAnimations.clone(); |
| mPendingAnimations.clear(); |
| int propertyMask = 0; |
| int propertyCount = nameValueList.size(); |
| for (int i = 0; i < propertyCount; ++i) { |
| NameValuesHolder nameValuesHolder = nameValueList.get(i); |
| propertyMask |= nameValuesHolder.mNameConstant; |
| } |
| mAnimatorMap.put(animator, new PropertyBundle(propertyMask, nameValueList)); |
| if (mPendingSetupAction != null) { |
| mAnimatorSetupMap.put(animator, mPendingSetupAction); |
| mPendingSetupAction = null; |
| } |
| if (mPendingCleanupAction != null) { |
| mAnimatorCleanupMap.put(animator, mPendingCleanupAction); |
| mPendingCleanupAction = null; |
| } |
| if (mPendingOnStartAction != null) { |
| mAnimatorOnStartMap.put(animator, mPendingOnStartAction); |
| mPendingOnStartAction = null; |
| } |
| if (mPendingOnEndAction != null) { |
| mAnimatorOnEndMap.put(animator, mPendingOnEndAction); |
| mPendingOnEndAction = null; |
| } |
| animator.addUpdateListener(mAnimatorEventListener); |
| animator.addListener(mAnimatorEventListener); |
| if (mStartDelaySet) { |
| animator.setStartDelay(mStartDelay); |
| } |
| if (mDurationSet) { |
| animator.setDuration(mDuration); |
| } |
| if (mInterpolatorSet) { |
| animator.setInterpolator(mInterpolator); |
| } |
| animator.start(); |
| } |
| |
| /** |
| * Utility function, called by the various x(), y(), etc. methods. This stores the |
| * constant name for the property along with the from/delta values that will be used to |
| * calculate and set the property during the animation. This structure is added to the |
| * pending animations, awaiting the eventual start() of the underlying animator. A |
| * Runnable is posted to start the animation, and any pending such Runnable is canceled |
| * (which enables us to end up starting just one animator for all of the properties |
| * specified at one time). |
| * |
| * @param constantName The specifier for the property being animated |
| * @param toValue The value to which the property will animate |
| */ |
| private void animateProperty(int constantName, float toValue) { |
| float fromValue = getValue(constantName); |
| float deltaValue = toValue - fromValue; |
| animatePropertyBy(constantName, fromValue, deltaValue); |
| } |
| |
| /** |
| * Utility function, called by the various xBy(), yBy(), etc. methods. This method is |
| * just like animateProperty(), except the value is an offset from the property's |
| * current value, instead of an absolute "to" value. |
| * |
| * @param constantName The specifier for the property being animated |
| * @param byValue The amount by which the property will change |
| */ |
| private void animatePropertyBy(int constantName, float byValue) { |
| float fromValue = getValue(constantName); |
| animatePropertyBy(constantName, fromValue, byValue); |
| } |
| |
| /** |
| * Utility function, called by animateProperty() and animatePropertyBy(), which handles the |
| * details of adding a pending animation and posting the request to start the animation. |
| * |
| * @param constantName The specifier for the property being animated |
| * @param startValue The starting value of the property |
| * @param byValue The amount by which the property will change |
| */ |
| private void animatePropertyBy(int constantName, float startValue, float byValue) { |
| // First, cancel any existing animations on this property |
| if (mAnimatorMap.size() > 0) { |
| Animator animatorToCancel = null; |
| Set<Animator> animatorSet = mAnimatorMap.keySet(); |
| for (Animator runningAnim : animatorSet) { |
| PropertyBundle bundle = mAnimatorMap.get(runningAnim); |
| if (bundle.cancel(constantName)) { |
| // property was canceled - cancel the animation if it's now empty |
| // Note that it's safe to break out here because every new animation |
| // on a property will cancel a previous animation on that property, so |
| // there can only ever be one such animation running. |
| if (bundle.mPropertyMask == NONE) { |
| // the animation is no longer changing anything - cancel it |
| animatorToCancel = runningAnim; |
| break; |
| } |
| } |
| } |
| if (animatorToCancel != null) { |
| animatorToCancel.cancel(); |
| } |
| } |
| |
| NameValuesHolder nameValuePair = new NameValuesHolder(constantName, startValue, byValue); |
| mPendingAnimations.add(nameValuePair); |
| mView.removeCallbacks(mAnimationStarter); |
| mView.postOnAnimation(mAnimationStarter); |
| } |
| |
| /** |
| * This method handles setting the property values directly in the View object's fields. |
| * propertyConstant tells it which property should be set, value is the value to set |
| * the property to. |
| * |
| * @param propertyConstant The property to be set |
| * @param value The value to set the property to |
| */ |
| private void setValue(int propertyConstant, float value) { |
| final RenderNode renderNode = mView.mRenderNode; |
| switch (propertyConstant) { |
| case TRANSLATION_X: |
| renderNode.setTranslationX(value); |
| break; |
| case TRANSLATION_Y: |
| renderNode.setTranslationY(value); |
| break; |
| case TRANSLATION_Z: |
| renderNode.setTranslationZ(value); |
| break; |
| case ROTATION: |
| renderNode.setRotationZ(value); |
| break; |
| case ROTATION_X: |
| renderNode.setRotationX(value); |
| break; |
| case ROTATION_Y: |
| renderNode.setRotationY(value); |
| break; |
| case SCALE_X: |
| renderNode.setScaleX(value); |
| break; |
| case SCALE_Y: |
| renderNode.setScaleY(value); |
| break; |
| case X: |
| renderNode.setTranslationX(value - mView.mLeft); |
| break; |
| case Y: |
| renderNode.setTranslationY(value - mView.mTop); |
| break; |
| case Z: |
| renderNode.setTranslationZ(value - renderNode.getElevation()); |
| break; |
| case ALPHA: |
| mView.setAlphaInternal(value); |
| renderNode.setAlpha(value); |
| break; |
| } |
| } |
| |
| /** |
| * This method gets the value of the named property from the View object. |
| * |
| * @param propertyConstant The property whose value should be returned |
| * @return float The value of the named property |
| */ |
| private float getValue(int propertyConstant) { |
| final RenderNode node = mView.mRenderNode; |
| switch (propertyConstant) { |
| case TRANSLATION_X: |
| return node.getTranslationX(); |
| case TRANSLATION_Y: |
| return node.getTranslationY(); |
| case TRANSLATION_Z: |
| return node.getTranslationZ(); |
| case ROTATION: |
| return node.getRotationZ(); |
| case ROTATION_X: |
| return node.getRotationX(); |
| case ROTATION_Y: |
| return node.getRotationY(); |
| case SCALE_X: |
| return node.getScaleX(); |
| case SCALE_Y: |
| return node.getScaleY(); |
| case X: |
| return mView.mLeft + node.getTranslationX(); |
| case Y: |
| return mView.mTop + node.getTranslationY(); |
| case Z: |
| return node.getElevation() + node.getTranslationZ(); |
| case ALPHA: |
| return mView.getAlpha(); |
| } |
| return 0; |
| } |
| |
| /** |
| * Utility class that handles the various Animator events. The only ones we care |
| * about are the end event (which we use to clean up the animator map when an animator |
| * finishes) and the update event (which we use to calculate the current value of each |
| * property and then set it on the view object). |
| */ |
| private class AnimatorEventListener |
| implements Animator.AnimatorListener, ValueAnimator.AnimatorUpdateListener { |
| @Override |
| public void onAnimationStart(Animator animation) { |
| if (mAnimatorSetupMap != null) { |
| Runnable r = mAnimatorSetupMap.get(animation); |
| if (r != null) { |
| r.run(); |
| } |
| mAnimatorSetupMap.remove(animation); |
| } |
| if (mAnimatorOnStartMap != null) { |
| Runnable r = mAnimatorOnStartMap.get(animation); |
| if (r != null) { |
| r.run(); |
| } |
| mAnimatorOnStartMap.remove(animation); |
| } |
| if (mListener != null) { |
| mListener.onAnimationStart(animation); |
| } |
| } |
| |
| @Override |
| public void onAnimationCancel(Animator animation) { |
| if (mListener != null) { |
| mListener.onAnimationCancel(animation); |
| } |
| if (mAnimatorOnEndMap != null) { |
| mAnimatorOnEndMap.remove(animation); |
| } |
| } |
| |
| @Override |
| public void onAnimationRepeat(Animator animation) { |
| if (mListener != null) { |
| mListener.onAnimationRepeat(animation); |
| } |
| } |
| |
| @Override |
| public void onAnimationEnd(Animator animation) { |
| mView.setHasTransientState(false); |
| if (mAnimatorCleanupMap != null) { |
| Runnable r = mAnimatorCleanupMap.get(animation); |
| if (r != null) { |
| r.run(); |
| } |
| mAnimatorCleanupMap.remove(animation); |
| } |
| if (mListener != null) { |
| mListener.onAnimationEnd(animation); |
| } |
| if (mAnimatorOnEndMap != null) { |
| Runnable r = mAnimatorOnEndMap.get(animation); |
| if (r != null) { |
| r.run(); |
| } |
| mAnimatorOnEndMap.remove(animation); |
| } |
| mAnimatorMap.remove(animation); |
| } |
| |
| /** |
| * Calculate the current value for each property and set it on the view. Invalidate |
| * the view object appropriately, depending on which properties are being animated. |
| * |
| * @param animation The animator associated with the properties that need to be |
| * set. This animator holds the animation fraction which we will use to calculate |
| * the current value of each property. |
| */ |
| @Override |
| public void onAnimationUpdate(ValueAnimator animation) { |
| PropertyBundle propertyBundle = mAnimatorMap.get(animation); |
| if (propertyBundle == null) { |
| // Shouldn't happen, but just to play it safe |
| return; |
| } |
| |
| boolean hardwareAccelerated = mView.isHardwareAccelerated(); |
| |
| // alpha requires slightly different treatment than the other (transform) properties. |
| // The logic in setAlpha() is not simply setting mAlpha, plus the invalidation |
| // logic is dependent on how the view handles an internal call to onSetAlpha(). |
| // We track what kinds of properties are set, and how alpha is handled when it is |
| // set, and perform the invalidation steps appropriately. |
| boolean alphaHandled = false; |
| if (!hardwareAccelerated) { |
| mView.invalidateParentCaches(); |
| } |
| float fraction = animation.getAnimatedFraction(); |
| int propertyMask = propertyBundle.mPropertyMask; |
| if ((propertyMask & TRANSFORM_MASK) != 0) { |
| mView.invalidateViewProperty(hardwareAccelerated, false); |
| } |
| ArrayList<NameValuesHolder> valueList = propertyBundle.mNameValuesHolder; |
| if (valueList != null) { |
| int count = valueList.size(); |
| for (int i = 0; i < count; ++i) { |
| NameValuesHolder values = valueList.get(i); |
| float value = values.mFromValue + fraction * values.mDeltaValue; |
| if (values.mNameConstant == ALPHA) { |
| alphaHandled = mView.setAlphaNoInvalidation(value); |
| } else { |
| setValue(values.mNameConstant, value); |
| } |
| } |
| } |
| if ((propertyMask & TRANSFORM_MASK) != 0) { |
| if (!hardwareAccelerated) { |
| mView.mPrivateFlags |= View.PFLAG_DRAWN; // force another invalidation |
| } |
| } |
| // invalidate(false) in all cases except if alphaHandled gets set to true |
| // via the call to setAlphaNoInvalidation(), above |
| if (alphaHandled) { |
| mView.invalidate(true); |
| } else { |
| mView.invalidateViewProperty(false, false); |
| } |
| if (mUpdateListener != null) { |
| mUpdateListener.onAnimationUpdate(animation); |
| } |
| } |
| } |
| } |