Principle and Application of Three Kinds of android Animation

Keywords: Attribute Android xml Mobile

android animation is currently divided into three categories:

  • Tween Animation View animation is a progressive animation that produces animation effects by constantly transforming images (translation, zooming, rotation, transparency) of objects in the scene. And View animation supports customization.

  • Drawable Animation frame animation, which produces animation effect by sequentially playing a series of images, can be understood as picture switching animation. OOM is easily caused by too many pictures.

  • Property Animation Attribute Animation, which was also introduced after android 3.0, can be used in the mobile version of android 4.0, by dynamically changing the attributes of the object to achieve the animation effect.

Tween Animation

The four transformation effects of View animation correspond to four subclasses of Animation: Translate Animation, Scale Animation, Rotate Animation and Alpha Animation. These animations can be defined either by XML or by code dynamically.

TranslateAnimation

view animation in horizontal and vertical directions

public TranslateAnimation(float fromXDelta, float toXDelta, float fromYDelta, float toYDelta)

fromXDelta and fromYDelta represent the starting coordinates on the X and Y axes respectively, (0, 0) this represents the coordinates of the current view, toXDelta and toYDelta represent the final target, respectively. If only the X axis moves or the Y axis moves, then the coordinates corresponding to the non-moving coordinates can be set to 0.

RotateAnimation

Rotary animation effect

public RotateAnimation(float fromDegrees, float toDegrees, int pivotXType, float pivotXValue,int pivotYType, float pivotYValue)

The 3rd and 5th parameters can be selected in several cases: Animation.RELATIVE_TO_PARENT. Compared with the parent control, 1f represents the width or height of the whole parent control, 0.5f represents the height or half of the width of the parent control, Animation.RELATIVE_TO_SELF, Animation.ABSOLUTE: This represents the absolute coordinates of the parent control.

The first two parameters are the angle of rotation, and the last four parameters are used to define the center of rotation.

ScaleAnimation

Animation effects for zooming in and out

public ScaleAnimation(float fromX, float toX, float fromY, float toY,
            int pivotXType, float pivotXValue, int pivotYType, float pivotYValue)

Like rotary animation, zoom animation can also set the center of zoom, the first four parameters set the start zoom ratio and the end zoom ratio. fromX: Scaling size on the starting X coordinate, toX: Scaling size on the ending X coordinate, fromY: Scaling size on the starting Y coordinate, toY: Scaling size on the ending Y coordinate. For this scaling size, 0 means invisible, 1 means original size, and so on, 1.5 means 1.5 times.

AlphaAniamtion

Transparency animation effect

public AlphaAnimation(float fromAlpha, float toAlpha)

fromAlpha denotes the transparency at the beginning of the animation, toAlpha denotes the transparency at the end of the animation. This range of values is 0-1, 0 denotes complete transparency, and 1 denotes total opacity.

Common methods of animation

  • setDuration(long durationMillis)
    This represents the time required to set the animation's display time, which is the time required for the animation from the initial state to the end state. The duration Millis parameter is the length of the animation's display time in milliseconds.

  • setStartOffset(long startOffset)
    This represents the start time of the animation, and startOffset represents the start time of the animation, in milliseconds (sometimes we may not want to execute the animation immediately after startAnimation, but we need to wait for a while to animate to use this).

  • setFillAfter(boolean fillAfter)
    The default value is flase, which means that the position of the animation is not reserved after the animation is finished. That is to say, we will automatically restore to the original state after the animation effect is finished. True means that the state of the animation is reserved at the end of the animation. This value is usually set to true.

  • startAnimation(Animation animation)
    This is used on the view object to indicate the beginning of animation. The parameters are the four types we mentioned above (note that the four types above are inherited from Animation). For example, I now have an image view object image, so when I want to start animation, I can use ImageView. start Animation (rotate Animation);

  • setInterpolator(Interpolator i)
    This representation sets the speed of animation change, where android provides many types of Interpolator-type changers:
    SetInterpolator (new Accelerate Interpolator (float factor): This Accelerate Interpolator represents acceleration. The factor parameter can be set to a multiple of acceleration. The larger the value, the faster the animation. Of course, it can also be the default new Accelerate Interpolator () which defaults to 1.
    There are several other types of transmission:

  • Accelerate Decelerate Interpolator () indicates acceleration followed by deceleration

  • Decelerate Interpolator (float factor) indicates deceleration
  • Cycle Interpolator () represents a sinusoidal change in rate
  • Linear Interpolator () denotes uniform velocity
  • Overshoot Interpolator () goes beyond the destination value and then slowly changes to the destination value
  • Bounce Interpolator () jumps. When it reaches the destination value, the value jumps. For example, the destination value is 100. The following values may be 85, 77, 70, 80, 90, 100 in turn.
  • Anticipate Overshoot Interpolator adds a reverse transcendence, first changes in the opposite direction, and then speeds up the playback, which will exceed the destination value and then slowly move to the destination value.
  • Anticipate Interpolator reverses, first changes a paragraph in the opposite direction and then speeds up the playback
  • -

Let's explore the source code for Accelerate Interpolator

/**
 * An interpolator where the rate of change starts out slowly and
 * and then accelerates.
 *
 */
@HasNativeInterpolator
public class AccelerateInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
    private final float mFactor;
    private final double mDoubleFactor;

    public AccelerateInterpolator() {
        mFactor = 1.0f;
        mDoubleFactor = 2.0;
    }

    /**
     * Constructor
     *
     * @param factor Degree to which the animation should be eased. Seting
     *        factor to 1.0f produces a y=x^2 parabola. Increasing factor above
     *        1.0f  exaggerates the ease-in effect (i.e., it starts even
     *        slower and ends evens faster)
     */
    public AccelerateInterpolator(float factor) {
        mFactor = factor;
        mDoubleFactor = 2 * mFactor;
    }

    public AccelerateInterpolator(Context context, AttributeSet attrs) {
        this(context.getResources(), context.getTheme(), attrs);
    }

    /** @hide */
    public AccelerateInterpolator(Resources res, Theme theme, AttributeSet attrs) {
        TypedArray a;
        if (theme != null) {
            a = theme.obtainStyledAttributes(attrs, R.styleable.AccelerateInterpolator, 0, 0);
        } else {
            a = res.obtainAttributes(attrs, R.styleable.AccelerateInterpolator);
        }

        mFactor = a.getFloat(R.styleable.AccelerateInterpolator_factor, 1.0f);
        mDoubleFactor = 2 * mFactor;
        setChangingConfiguration(a.getChangingConfigurations());
        a.recycle();
    }

    public float getInterpolation(float input) {
        if (mFactor == 1.0f) {
            return input * input;
        } else {
            return (float)Math.pow(input, mDoubleFactor);
        }
    }

    /** @hide */
    @Override
    public long createNativeInterpolator() {
        return NativeInterpolatorFactoryHelper.createAccelerateInterpolator(mFactor);
    }
}

In addition to the constructor, the important function of the source code is:

public float getInterpolation(float input) {
        if (mFactor == 1.0f) {
            return input * input;
        } else {
            return (float)Math.pow(input, mDoubleFactor);
        }
    }

Track this method call to the getTransformation() method in the Animation.java class

/**
     * Gets the transformation to apply at a specified point in time. Implementations of this
     * method should always replace the specified Transformation or document they are doing
     * otherwise.
     *
     * @param currentTime Where we are in the animation. This is wall clock time.
     * @param outTransformation A transformation object that is provided by the
     *        caller and will be filled in by the animation.
     * @return True if the animation is still running
     */
    public boolean getTransformation(long currentTime, Transformation outTransformation) {
        if (mStartTime == -1) {
            mStartTime = currentTime;
        }

        final long startOffset = getStartOffset();
        final long duration = mDuration;
        float normalizedTime;
        if (duration != 0) {
            normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) /
                    (float) duration;
        } else {
            // time is a step-change with a zero duration
            normalizedTime = currentTime < mStartTime ? 0.0f : 1.0f;
        }

        final boolean expired = normalizedTime >= 1.0f || isCanceled();
        mMore = !expired;

        if (!mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);

        if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) {
            if (!mStarted) {
                fireAnimationStart();
                mStarted = true;
                if (NoImagePreloadHolder.USE_CLOSEGUARD) {
                    guard.open("cancel or detach or getTransformation");
                }
            }

            if (mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);

            if (mCycleFlip) {
                normalizedTime = 1.0f - normalizedTime;
            }

            final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
            applyTransformation(interpolatedTime, outTransformation);
        }

        if (expired) {
            if (mRepeatCount == mRepeated || isCanceled()) {
                if (!mEnded) {
                    mEnded = true;
                    guard.close();
                    fireAnimationEnd();
                }
            } else {
                if (mRepeatCount > 0) {
                    mRepeated++;
                }

                if (mRepeatMode == REVERSE) {
                    mCycleFlip = !mCycleFlip;
                }

                mStartTime = -1;
                mMore = true;

                fireAnimationRepeat();
            }
        }

        if (!mMore && mOneMoreTime) {
            mOneMoreTime = false;
            return true;
        }

        return mMore;
    }

The above is the getTransform () method, in which you can also see the getStartOffset() and mDuration that were set up earlier, and so on.
I found this sentence above.

final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);

The parameters are as follows:
normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);

According to the above code, we can also customize our own Interpolator. The definition method is to write a class inherited from the Interpolator interface, and to implement the getInterpolation() method, in which corresponding operations are performed.

Custom Tween Animation

Custom View animation only needs to inherit the abstract class Animation, then rewrite its initialize and applyTranformation methods, do some initialization work in initialize method, and make corresponding matrix transformation in applyTransformation. Camera is often used to simplify the process of matrix transformation.

Drawable Animation

Frame animation is to play a set of pre-defined pictures sequentially, similar to movie play, different from View animation, the system provides another kind of Animation Drawable to use frame animation. First, an Animation Drawable is defined by XML:

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android = "http://schemas.android.com/apk/res/android"
    android:oneshot="false">
    <item android:drawable="@drawable/home1" android:duration="500" />
    <item android:drawable="@drawable/home2" android:duration="500" />
    <item android:drawable="@drawable/home_press" android:duration="500" />
</animation-list>

Then use ImageView as the carrier of animation:

ImageView iv_frame = (ImageView) findViewById(R.id.iv_frame_animation);
        iv_frame.setBackgroundResource(R.drawable.frame_animation);
        AnimationDrawable drawable = (AnimationDrawable) iv_frame.getBackground();
        drawable.start();

Frame animation can be achieved, but frame animation is easy to cause OOM, so when using frame animation, we should try to avoid using larger pictures.

Property Animation

Attribute animation is different from View animation. It extends the object of action. Attribute animation can animate any object. The effect of animation has also been strengthened to achieve more gorgeous animation effect.

Principle:
Property animation requires animation objects to provide the get and set methods of the attribute. According to the initial and final values of the attribute passed by the outside world, property animation calls the set method many times with the effect of animation. The values passed to the set method are different each time. At the end of the animation, the final value is passed.
So we animate the object's attribute "zoky". In order to make the animation effective, we must satisfy two conditions:
1. objcet must provide setZoky methods and getZoky methods (if no initial value is available)
2. The setZoky changes to the attribute Zoky of object must be reflected in some way, such as bringing UI changes.

When the target object does not have get and set attributes, there are three solutions:
1. Add get and set methods to the target object, if there are permissions.
2. Wrap the original object with a class in a composite way, and then implement get and set methods in the class

private static class  ViewWrapper{
        private View mTarget;
        public ViewWrapper(View target){
            mTarget = target;
        }
        public int getWidth() {
            return mTarget.getLayoutParams().width;
        }
        public void setWidth(int width){
            mTarget.getLayoutParams().width = width;
            mTarget.requestLayout();
        }
    }

3. Use Value Animator to monitor the animation process, extract fraction (the current progress is the proportion of the whole animation process) in the Value Animation Update method, convert it to the change value of the object, and then assign it to the target object to achieve the change of attributes.

ValueAnimator valueAnimator = ValueAnimator.ofInt(1,100);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            private IntEvaluator mEvaluator = new IntEvaluator();
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int currentValue = (int) animation.getAnimatedValue();
                float fraction = animation.getAnimatedFraction();
                targetView.getLayoutParams().width = mEvaluator.evaluate(fraction,start,end);
                tragetView.requestLayout();
            }
        });

Related classes and concepts

  • Time interpolation: Time difference, definition of animation rate of change, more View animation Linar Interpolator, Accelerate Decelerate Interpolator and so on similar.
  • Animator sets: A collection of animations. You can define a set of animations to be executed together or in a certain order.
  • ObjectAnimator Animation Execution Class
  • ValueAnimator Animation Execution Class
  • Animator Inflater loads XML files for attribute animation
  • Time Interpolator Time Interpolator
  • Type Evaluator type valuation is mainly used to set the value of animation operation properties.

We can also define attribute animation in XML, which can more intuitively reflect the relationship between animation: in XML, tags correspond to AnimatorSet, Value Animator, and Object Animator. The tag's android: ordering attribute has two optional values, "together" and "sequentially". The former means that the sub-animations in the animation set are played simultaneously, the latter means that the sub-animations are played sequentially, and the default attribute of andorid:ordering is "together".
- android:propertyName - The name of the attribute representing the action object of the property animation;
- android:duration - denotes the length of the animation;
- android: valueFrom - Represents the starting value of an attribute
- android: valueTo - denotes the end value of an attribute
- android: startOffset - Represents the time when the animation is delayed.
- android: repeatCount - Represents the number of times an animation is repeated
- Android: repeat mode - Repeat mode of animation, there are two options, "restart" and "reverse", respectively, for continuous repetition and reverse repetition, reverse repetition is the first time after playing, the second time will play backwards. The third heavy start.
- android: value Type - denotes the type of attribute specified by android: propertyName, with two optional options: "intType" and "floatType".

<set
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:ordering="together">
    <objectAnimator
        android:duration="900"
        android:propertyName="scaleX"
        android:valueTo="2.0"
        android:valueType="floatType"/>
    <objectAnimator
        android:duration="900"
        android:propertyName="scaleY"
        android:valueTo="2.0"
        android:valueType="floatType"/>
    <objectAnimator
        android:duration="900"
        android:propertyName="translationX"
        android:valueTo="400"
        android:valueType="floatType"/>
</set>

When a call is needed, only:

        AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(this,R.animator.property_animator);
        set.setTarget(tv_animatior);
        set.start();

that will do

Attribute Animation Monitor

Attribute animation has two main listening interfaces: Animator Update Listerer and AnimarListener.

public static interface AnimatorListener {
        /**
         * <p>Notifies the start of the animation.</p>
         *
         * @param animation The started animation.
         */
        void onAnimationStart(Animator animation);

        /**
         * <p>Notifies the end of the animation. This callback is not invoked
         * for animations with repeat count set to INFINITE.</p>
         *
         * @param animation The animation which reached its end.
         */
        void onAnimationEnd(Animator animation);

        /**
         * <p>Notifies the cancellation of the animation. This callback is not invoked
         * for animations with repeat count set to INFINITE.</p>
         *
         * @param animation The animation which was canceled.
         */
        void onAnimationCancel(Animator animation);

        /**
         * <p>Notifies the repetition of the animation.</p>
         *
         * @param animation The animation which was repeated.
         */
        void onAnimationRepeat(Animator animation);
    }

It monitors the start, end, cancel and repeat of the animation. At the same time, the system also provides the AnimatorListener Adapter adapter class, so we can choose to implement the above four methods.

    public static interface AnimatorUpdateListener {
        /**
         * <p>Notifies the occurrence of another frame of the animation.</p>
         *
         * @param animation The animation which was repeated.
         */
        void onAnimationUpdate(ValueAnimator animation);

    }

For each frame played, onAnimation Update is called once.

Property Animation Principle

Attribute animation starts with the start () method. Let's first look at ObjectAnimator's start method:

@Override
    public void start() {
        // See if any of the current active/pending animators need to be canceled
        AnimationHandler handler = sAnimationHandler.get();
        if (handler != null) {
            int numAnims = handler.mAnimations.size();
            for (int i = numAnims - 1; i >= 0; i--) {
                if (handler.mAnimations.get(i) instanceof ObjectAnimator) {
                    ObjectAnimator anim = (ObjectAnimator) handler.mAnimations.get(i);
                    if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {
                        anim.cancel();
                    }
                }
            }
            numAnims = handler.mPendingAnimations.size();
            for (int i = numAnims - 1; i >= 0; i--) {
                if (handler.mPendingAnimations.get(i) instanceof ObjectAnimator) {
                    ObjectAnimator anim = (ObjectAnimator) handler.mPendingAnimations.get(i);
                    if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {
                        anim.cancel();
                    }
                }
            }
            numAnims = handler.mDelayedAnims.size();
            for (int i = numAnims - 1; i >= 0; i--) {
                if (handler.mDelayedAnims.get(i) instanceof ObjectAnimator) {
                    ObjectAnimator anim = (ObjectAnimator) handler.mDelayedAnims.get(i);
                    if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {
                        anim.cancel();
                    }
                }
            }
        }
        if (DBG) {
            Log.d(LOG_TAG, "Anim target, duration: " + getTarget() + ", " + getDuration());
            for (int i = 0; i < mValues.length; ++i) {
                PropertyValuesHolder pvh = mValues[i];
                Log.d(LOG_TAG, "   Values[" + i + "]: " +
                    pvh.getPropertyName() + ", " + pvh.mKeyframes.getValue(0) + ", " +
                    pvh.mKeyframes.getValue(1));
            }
        }
        super.start();
    }

The above code first determines whether the current animation, waiting animation and delayed animation have the same animation as the current animation, if any, cancel the same animation, and then call the parent Valu's start method.

 private void start(boolean playBackwards) {
        if (Looper.myLooper() == null) {
            throw new AndroidRuntimeException("Animators may only be run on Looper threads");
        }
        mReversing = playBackwards;
        mPlayingBackwards = playBackwards;
        if (playBackwards && mSeekFraction != -1) {
            if (mSeekFraction == 0 && mCurrentIteration == 0) {
                // special case: reversing from seek-to-0 should act as if not seeked at all
                mSeekFraction = 0;
            } else if (mRepeatCount == INFINITE) {
                mSeekFraction = 1 - (mSeekFraction % 1);
            } else {
                mSeekFraction = 1 + mRepeatCount - (mCurrentIteration + mSeekFraction);
            }
            mCurrentIteration = (int) mSeekFraction;
            mSeekFraction = mSeekFraction % 1;
        }
        if (mCurrentIteration > 0 && mRepeatMode == REVERSE &&
                (mCurrentIteration < (mRepeatCount + 1) || mRepeatCount == INFINITE)) {
            // if we were seeked to some other iteration in a reversing animator,
            // figure out the correct direction to start playing based on the iteration
            if (playBackwards) {
                mPlayingBackwards = (mCurrentIteration % 2) == 0;
            } else {
                mPlayingBackwards = (mCurrentIteration % 2) != 0;
            }
        }
        int prevPlayingState = mPlayingState;
        mPlayingState = STOPPED;
        mStarted = true;
        mStartedDelay = false;
        mPaused = false;
        updateScaledDuration(); // in case the scale factor has changed since creation time
        AnimationHandler animationHandler = getOrCreateAnimationHandler();
        animationHandler.mPendingAnimations.add(this);
        if (mStartDelay == 0) {
            // This sets the initial value of the animation, prior to actually starting it running
            if (prevPlayingState != SEEKED) {
                setCurrentPlayTime(0);
            }
            mPlayingState = STOPPED;
            mRunning = true;
            notifyStartListeners();
        }
        animationHandler.start();
    }

Finally, the start method of animationHandler is called, which is a Runnable method, then interacts with the JNI layer, and then calls back the doAnimationFrame method.

final boolean doAnimationFrame(long frameTime) {
        if (mPlayingState == STOPPED) {
            mPlayingState = RUNNING;
            if (mSeekFraction < 0) {
                mStartTime = frameTime;
            } else {
                long seekTime = (long) (mDuration * mSeekFraction);
                mStartTime = frameTime - seekTime;
                mSeekFraction = -1;
            }
            mStartTimeCommitted = false; // allow start time to be compensated for jank
        }
        if (mPaused) {
            if (mPauseTime < 0) {
                mPauseTime = frameTime;
            }
            return false;
        } else if (mResumed) {
            mResumed = false;
            if (mPauseTime > 0) {
                // Offset by the duration that the animation was paused
                mStartTime += (frameTime - mPauseTime);
                mStartTimeCommitted = false; // allow start time to be compensated for jank
            }
        }
        // The frame time might be before the start time during the first frame of
        // an animation.  The "current time" must always be on or after the start
        // time to avoid animating frames at negative time intervals.  In practice, this
        // is very rare and only happens when seeking backwards.
        final long currentTime = Math.max(frameTime, mStartTime);
        return animationFrame(currentTime);
    }

We can see that the animationFrame method is finally invoked, and then the animateValue method is invoked internally in the method.

 /**
     * This method is called with the elapsed fraction of the animation during every
     * animation frame. This function turns the elapsed fraction into an interpolated fraction
     * and then into an animated value (from the evaluator. The function is called mostly during
     * animation updates, but it is also called when the <code>end()</code>
     * function is called, to set the final value on the property.
     *
     * <p>Overrides of this method must call the superclass to perform the calculation
     * of the animated value.</p>
     *
     * @param fraction The elapsed fraction of the animation.
     */
    @CallSuper
    void animateValue(float fraction) {
        fraction = mInterpolator.getInterpolation(fraction);
        mCurrentFraction = fraction;
        int numValues = mValues.length;
        for (int i = 0; i < numValues; ++i) {
            mValues[i].calculateValue(fraction);
        }
        if (mUpdateListeners != null) {
            int numListeners = mUpdateListeners.size();
            for (int i = 0; i < numListeners; ++i) {
                mUpdateListeners.get(i).onAnimationUpdate(this);
            }
        }
    }

Among them, calculateValue is to calculate the corresponding value of each frame of animation.
When initializing, if the initial value of the attribute is not provided, the get method will be called, such as the setupValue method in Property ValuesHolder, and it is found that the get method is implemented by reflecting the call method.

private void setupValue(Object target, Keyframe kf) {
        if (mProperty != null) {
            Object value = convertBack(mProperty.get(target));
            kf.setValue(value);
        }
        try {
            if (mGetter == null) {
                Class targetClass = target.getClass();
                setupGetter(targetClass);
                if (mGetter == null) {
                    // Already logged the error - just return to avoid NPE
                    return;
                }
            }
            Object value = convertBack(mGetter.invoke(target));
            kf.setValue(value);
        } catch (InvocationTargetException e) {
            Log.e("PropertyValuesHolder", e.toString());
        } catch (IllegalAccessException e) {
            Log.e("PropertyValuesHolder", e.toString());
        }
    }

Similarly, the set method is invoked by reflection in the setAnimatedValue method.

void setAnimatedValue(Object target) {
        if (mProperty != null) {
            mProperty.set(target, getAnimatedValue());
        }
        if (mSetter != null) {
            try {
                mTmpValueArray[0] = getAnimatedValue();
                mSetter.invoke(target, mTmpValueArray);
            } catch (InvocationTargetException e) {
                Log.e("PropertyValuesHolder", e.toString());
            } catch (IllegalAccessException e) {
                Log.e("PropertyValuesHolder", e.toString());
            }
        }
    }

Reference: Android Exploration of Development Art

Posted by Roble on Sat, 23 Mar 2019 21:57:27 -0700