Android attribute animation principle and use (Kotlin)

The previous article wrote about View animation and frame animation. . . . .

Then this article will be a wave of attribute animation. . . . .

We should all know that the View animation changes only the drawing position of the View, and does not change the properties of the View, such as the coordinates of the four vertices. That is, you move a View from its original position A to a new position B and stay at B. If you click on position B, there will be no response to the click event. For example, it is only available at point A. . . .

So if an animation has a position movement, but it must also handle the click event, the View animation cannot meet the demand. . .

But the property animation   can make up for this shortcoming View animation! ! ! ! ! !

 

Attribute animation was introduced by Android 3.0. It can be said to be an expansion of View animation. It is an enhanced version of View animation, or it can almost completely replace View animation, and attribute animation can make any object, not just View. . . .

Because it can only be used after 3.0, if you want to use the version before 3.0, you can use the open source library: http://nineoldandroids.com/

Property animation is actually two classes, ValueAnimator  and  ObjectAnimator 

ValueAnimator 

Set the start value and end value, and there is a total time. Obtain the excessive value of each time node during this time, and use the value to control the animation target. In fact, this kind of scene is not used much. . . . . . .

ObjectAnimator :

This one is used a lot, and it is basically used to control the animation. You can directly use the task object as the animation object. . . .

 

============【ValueAnimator 】============

Let me talk about  ValueAnimator

Use: ValueAnimator.ofFloat()

    /**
     *      ValueAnimator
     * */
    private fun valueAnimator() {
        // 从 0 过渡到 1
        val valueAnimator = ValueAnimator.ofFloat(0F, 1F)
        // 时间是 100毫秒
        valueAnimator.duration = 100
        // 监听进度
        valueAnimator.addUpdateListener(object : ValueAnimator.AnimatorUpdateListener {
            override fun onAnimationUpdate(animation: ValueAnimator?) {
                //获取动画进度值,通过这个值,去控制动画目标
                //比如 一个View背景颜色的百分比
                val animatedValue = animation?.animatedValue as Float
                Log.d(TAG, "进度:$animatedValue")
            }
        })
        valueAnimator.start()
    }

Look at the log:

From the results, we can see that this transition progress has been called back many times. . . . .

 

Let's look at the source code of ValueAnimator.ofFloat()

 

You can pass in multiple parameters, so:

If ValueAnimator.ofFloat(0F, 1F) represents the transition from 0 to 1,

 

If ValueAnimator.ofFloat(0F, 50F, 3F, 100F) represents the transition from 0 to 50, then from 50 to 3, and then from 3 to 100

On the code:

    /**
     *      ValueAnimator
     * */
    private fun valueAnimator() {
        // 从 0 过渡到 1
        val valueAnimator = ValueAnimator.ofFloat(0F, 50F,3F,100F)
        // 时间是 100毫秒
        valueAnimator.duration = 1000
        // 监听进度
        valueAnimator.addUpdateListener(object : ValueAnimator.AnimatorUpdateListener {
            override fun onAnimationUpdate(animation: ValueAnimator?) {
                //获取动画进度值,通过这个值,去控制动画目标
                //比如 一个View背景颜色的百分比
                val animatedValue = animation?.animatedValue as Float
                Log.d(TAG, "进度:$animatedValue")
            }
        })
        valueAnimator.start()
    }

See the result:

进度:0.0
进度:0.0
进度:0.09472668
进度:0.40268898
进度:0.9233728
进度:1.655303
进度:2.535273
进度:3.670764
进度:5.009651
进度:6.4521832
进度:8.17451
进度:10.087408
进度:12.056963
进度:14.323733
进度:16.763512
进度:19.211622
进度:21.966991
进度:24.87359
进度:27.73996
进度:30.916098
进度:34.217968
进度:37.43199
进度:40.950714
进度:44.566513
进度:48.049103
进度:48.285706          // 接近50时,开始从 50 往 3F去过度
进度:44.67541
进度:41.22999
进度:37.528618
进度:33.79583
进度:30.263401
进度:26.500002
进度:22.736603
进度:19.204176
进度:15.471386
进度:11.770042
进度:8.324593
进度:4.7142982           // 接近3时,开始从 3 往 100F去过度
进度:6.3578963
进度:13.540961
进度:20.555607
进度:27.381924
进度:33.617188
进度:40.022766
进度:46.184475
进度:51.745235
进度:57.384045
进度:62.423447
进度:67.47879
进度:72.21197
进度:76.36032
进度:80.43042
进度:84.14144
进度:87.29662
进度:90.28128
进度:92.878716
进度:94.96303
进度:96.78872
进度:98.20865
进度:99.17078
进度:99.79256
进度:100.0

In addition to ValueAnimator.ofFloat, the more commonly used ones are: ValueAnimator.ofInt()

The principle is the same. I will not show them one by one. . . . . .

 

In addition, we can also call

setStartDelay() method to set the animation delay time,

setRepeatCount()  sets the number of times the animation is played in a loop

setRepeatMode() is the mode of loop playback. Loop modes include RESTART and REVERSE, which respectively mean replay and reverse playback.

These methods are very simple and will not be explained in detail.
 

 

============【ObjectAnimator 】==========

ObjectAnimator  is different. You can animate any object, such as the background color of the View and the alpha value.

From the source code, ObjectAnimator   inherits  ValueAnimator,

So in the final analysis, the   animation is operated in the way that ValueAnimator sets the value. So ValueAnimator is the core of the entire property animation.

So  ValueAnimator   method, in  ObjectAnimator   almost all use of. . . . So  ObjectAnimator   can almost completely replace ValueAnimator   . . . .

In fact, the usage of ObjectAnimator    is similar, look at the code ----

 

We look at transparency (alpha)

private fun objectAnimator() {
        /**
         *      参数1     tv_animator_view 目标View
         *      参数2     需要进行动画的属性 比如:alpha ,这个参数字符乱传会怎样? 后面会说到
         *      参数3...  这里可以传进多个参数,现在代码设置就是:0F, 1F, 0F, 1F
         *                就是从 0(完全透明)过渡到 1 (完全不透明) 再到 0(完全透明) 再到 1 (完全不透明)
         * */
        val objectAnimator = ObjectAnimator.ofFloat(tv_animator_view, "alpha", 0F, 1F, 0F, 1F)
        objectAnimator.duration = 4000
        objectAnimator.start()
    }

OK, run the code, tv_animator_view is indeed displayed as mentioned above:

Transition from completely transparent to completely opaque, then completely transparent, and then completely opaque

learn by analogy,

 

We look rotation (rotation)

val rotationAnimator = ObjectAnimator.ofFloat(tv_animator_view2, "rotation", 0F, -90F,90F,0F)
        rotationAnimator.duration = 10000
        rotationAnimator.start()

Remember what I said in the previous article, the rotation setting

Positive numbers represent the clockwise method,

Negative numbers represent the counterclockwise approach.

So the effect of the above operation is: first rotate from the original position to 90 degrees counterclockwise, then rotate from the current position to 90 degrees clockwise, and then return to the original position.

I will not post the animation renderings at runtime. . . . trouble. . .

 

Let's look at translation (translationX or translationY) :

        //获取当前 View X方向偏量 如果在原始位置,XY轴的偏移量都是0
        val originX = tv_animator_view3.translationX
        //获取 View 的宽度,返回值单位 px像素
        val width = tv_animator_view3.width
        /**
         *          参数1     tv_animator_view3 目标View
         *          参数2     translationX X轴的平移 正为往右  负数为左,单位为像素
         *          参数3     这里可以传进多个参数,
         *                    运行结果:从原始位置 往左移动 当前View的宽度,然后再回到原始位置
         * */
        val translationAnimator =
            ObjectAnimator.ofFloat(tv_animator_view3, "translationX", originX, width.toFloat(), originX)
        translationAnimator.duration = 10000
        translationAnimator.start()

The result of the operation is what I wrote in the comment: move the width of the current View from the original position to the left, and then return to the original position

Note the units in the method:

translationX X-axis translation is positive to the right, negative is to the left, the unit is pixel

translationY Y-axis translation is downward, negative is upward, the unit is pixel

 

Let's look at scaled (scaleX or scaleY) :

        val translationAnimator =
            ObjectAnimator.ofFloat(tv_animator_view3, "scaleX", 0F, 1F)
        translationAnimator.duration = 10000
        translationAnimator.start()

Looking at the code, you can see that the smart ones are zooming in from 0 to 1 (original size) in the X axis direction.

If you change to  scaleY  , it will zoom in the vertical direction. . . . .

 

So far. The four most commonly used animations (alpha, rotation, translationX and scaleY) of attribute animation are shown.

But just like the question at the top, the second parameter in the method, what else can be filled in, I will answer for you: any character.

what??? are you kidding me? No, I'm not joking.

When designing Android, there is no limit to only View, which can be any subclass of Object.

Therefore, if you pass abd in ObjectAnimator.ofFloat(tv_animator_view3, " abd ", 0F, 1F), there will be no animation effect on this View.

Of course, it's not going to be carsh...

You can understand it   this way. After a "scaleX" is passed in, the animation will continue to look for the target while the animation is in progress. Does the target currently passed in have the setScaleX() method.

Knock on the blackboard! note! ! Is the corresponding method 

If there is, it will call this method according to the progress. If not, it will not be processed.

And what is passed in at the beginning is TextView, which has a method setScaleX() , and the animation will change it.

But if what I pass in is " abd ", TextView has no method setAbd(), so it won't change anything. . . . No error

The method setScaleX() of TextView isactually in its parent class View. The source code is as follows:

 

So it calls this setScaleX() of  View

By analogy, you who are smart will also think that in the View there will definitely be:

The methods setRotation(), getRotation(), setTranslationX(), getTranslationX(), setScaleY(), getScaleY(). . . . .

 

As for how the ObjectAnimator mechanism finds the corresponding set() method. . . In fact, this is the case, ObjectAnimator is dependent on the current thread Looper, if the current thread does not have Looper, an error will be reported. From ObjectAnimator.start() tracking to the final ValueAnimator.star() method can be seen.

As for how to create the current Looper, it is not the content of this chapter. Readers go to tuition by themselves. . . . .

In fact, the source code has been followed to the final animateValue (float fraction),

There is a piece of code called: mValues[i].calculateValue(fraction);  this code is used to calculate the attribute value of each frame

 

Okay, let's take a look at how the property animation obtains the get() and set() methods. This is what we care about most. . . .

The get() method is obtained when we are not initialized. In  PropertyValuesHolder

Obtained by reflection

 

So, what about set()  ? In fact, it is also in  PropertyValuesHolder  . Because we created the second parameter passed in by ObjectAnimator,

It is stored in this class, so  get and set are obtained through  PropertyValuesHolder  .

The above source code tracking is only an interception of the key part, the reader will find the specific tracking path by himself,,, haha

 

 

============[ Animation Intermediate Agency ]==================

Okay, let's take a Demo to verify the method of property animation  - property

 

We first create an AnimationTemp class

class AnimationTemp {

    private val TAG = "AnimationTemp"


    fun setAbcint(abcInt: Int) {
        Log.d(TAG, "调用 setAbcInt() abcInt :  $abcInt ")
    }

    fun getAbcint(): Int {
        Log.d(TAG, "调用 getAbcInt() ")
        return 100
    }

}

 

Then according to the principle of attribute animation, the animation object can be any object, good. We use this AnimationTemp as the animation object

        /**
         *          参数1   以 AnimationTemp 作为 动画对象
         *          参数2   改变 abcint 这个属性,
         *                  对应会在目标对象里找:setAbcint(abcInt: Int) 这个方法,注意,方法名大小写
         *          参数3   起始值
         *          参数4   结束值
         * */
        val ofInt = ObjectAnimator.ofInt(AnimationTemp(), "abcint", 0, 10)
        ofInt.duration = 100
        ofInt.start()

 

Look at the running results:

Okay, it's obvious. . . . . Keep calling  setAbcint(abcInt: Int)   to print from 0 to 10

 

In summary, analyze the principle:

1> ObjectAnimator.onInt() creates an animation with an Int gradient. The animation object is an AnimationTemp instance. The property that needs to be changed is called: abcint

2> When the animation is in progress, the system tries to change the abcint of the  animation object  , then the corresponding

        The system will look for a method called: setAbcint(abcInt: Int) in the animation object , pay attention to the type of input  parameters of the method and the name case of the method name

3> So throughout the animation cycle, call  setAbcint(abcInt: Int)   to change the property value of the target animation

 

At this time, you may see that our getAbcint() is not called. In fact, the source code above has been analyzed, that is, it will be called when we have no initialization value.

Okay, let's change the code:

 When creating an animation, pass in the end value, (if you only pass an Int, the system defaults to this value as the end value)

        /**
         *          参数1   以 AnimationTemp 作为 动画对象
         *          参数2   改变 abcint 这个属性,
         *                  对应会在目标对象里找:setAbcint(abcInt: Int) 这个方法,注意,方法名大小写
         *          参数3   结束值
         * */
        val ofInt = ObjectAnimator.ofInt(AnimationTemp(), "abcint", 10)
        ofInt.duration = 100
        ofInt.start()

Then AnimationTemp remains unchanged

class AnimationTemp {

    private val TAG = "AnimationTemp"


    fun setAbcint(abcInt: Int) {
        Log.d(TAG, "调用 setAbcInt() abcInt :  $abcInt ")
    }

    fun getAbcint(): Int {
        Log.d(TAG, "调用 getAbcInt() ")
        return 100
    }
}

 

Look at the results:

Okay, it's obvious. Verify the source code analysis.

When the initial value of the animation is not initialized, the get() method of the corresponding property will be found in the animation target as the starting value.

Here we return 100 as the start value, and then the end value is 10

So print from 100 to 10

So, at this point, everyone basically knows the principle of attribute animation. All understand why the target of attribute animation can be any target! ! ! !

============[ Combination Animation ]==================

Finish talking about the principle. Finally, let's talk about multiple animations together. . Combination animation. . .

AnimatorSet    uses this class. You can put one or more of ValueAnimator and ObjectAnimator to play

Main method:

play()                            Play a single

playSequentially()        plays sequentially, one after another

playTogether()              play all animations together

 

after(Animator animIn)             insert the existing animation into the incoming animation (ie: animIn ), execute
after(long delay)                      to delay  the existing animation for specified milliseconds and execute
before(Animator animIn)         to insert the existing animation into the incoming animation (Ie: animIn ) The previous execution of
with(Animator anim)                 willexecutethe existing animation and the animation passed in (ie: animIn ) at the same time

Demo1----after

Okay, let's run the code:

        val alphaAnimator = ObjectAnimator.ofFloat(tv_animator_view, "alpha", 0F, 1F, 0F, 1F)
        alphaAnimator.duration = 4000

        val rotationAnimator = ObjectAnimator.ofFloat(tv_animator_view, "rotation", 0F, -90F, 90F, 0F)
        rotationAnimator.duration = 4000

        val animatorSet = AnimatorSet()
        animatorSet.play(alphaAnimator).after(rotationAnimator)
        animatorSet.start()

 

The running effect is: rotationAnimator is executed first, alphaAnimator is executed

 【Demo2----with

        val alphaAnimator = ObjectAnimator.ofFloat(tv_animator_view, "alpha", 0F, 1F, 0F, 1F)
        alphaAnimator.duration = 4000

        val rotationAnimator = ObjectAnimator.ofFloat(tv_animator_view, "rotation", 0F, -90F, 90F, 0F)
        rotationAnimator.duration = 4000

        val animatorSet = AnimatorSet()
        animatorSet.play(alphaAnimator).with(rotationAnimator)
        animatorSet.start()

 

The running effect is: rotationAnimator and alphaAnimator are executing at the same time

 

 【Demo3----before

        val alphaAnimator = ObjectAnimator.ofFloat(tv_animator_view, "alpha", 0F, 1F, 0F, 1F)
        alphaAnimator.duration = 4000

        val rotationAnimator = ObjectAnimator.ofFloat(tv_animator_view, "rotation", 0F, -90F, 90F, 0F)
        rotationAnimator.duration = 4000

        val animatorSet = AnimatorSet()
        animatorSet.play(alphaAnimator).before(rotationAnimator)
        animatorSet.start()

The running effect is: alphaAnimator is executed first, and rotationAnimator is executed later

 

Demo4 -- after(long)

        val alphaAnimator = ObjectAnimator.ofFloat(tv_animator_view, "alpha", 0F, 1F, 0F, 1F)
        alphaAnimator.duration = 4000

        val rotationAnimator = ObjectAnimator.ofFloat(tv_animator_view, "rotation", 0F, -90F, 90F, 0F)
        rotationAnimator.duration = 4000

        val animatorSet = AnimatorSet()
        animatorSet.play(alphaAnimator).before(rotationAnimator).after(3000)
        animatorSet.start()

The running effect is: delay 3 seconds, alphaAnimator executes first, rotationAnimator executes later

 

Demo5 ---- playTogether

        val alphaAnimator = ObjectAnimator.ofFloat(tv_animator_view, "alpha", 0F, 1F, 0F, 1F)
        alphaAnimator.duration = 4000

        val rotationAnimator = ObjectAnimator.ofFloat(tv_animator_view, "rotation", 0F, -90F, 90F, 0F)
        rotationAnimator.duration = 4000

        val animatorSet = AnimatorSet()
        animatorSet.playTogether(alphaAnimator, rotationAnimator)
        animatorSet.start()

The running effect is: rotationAnimator and alphaAnimator are executing at the same time

 

Demo5 ---- playSequentially

        val alphaAnimator = ObjectAnimator.ofFloat(tv_animator_view, "alpha", 0F, 1F, 0F, 1F)
        alphaAnimator.duration = 4000

        val rotationAnimator = ObjectAnimator.ofFloat(tv_animator_view, "rotation", 0F, -90F, 90F, 0F)
        rotationAnimator.duration = 4000

        val animatorSet = AnimatorSet()
        animatorSet.playSequentially(alphaAnimator, rotationAnimator)
        animatorSet.start()

The running effect is: alphaAnimator is executed first, and rotationAnimator is executed later

 

==================[ Animation monitoring ]=================

Is actually a method

        val alphaAnimator = ObjectAnimator.ofFloat(tv_animator_view, "alpha", 0F, 1F, 0F, 1F)
        alphaAnimator.duration = 4000

        val rotationAnimator = ObjectAnimator.ofFloat(tv_animator_view, "rotation", 0F, -90F, 90F, 0F)
        rotationAnimator.duration = 4000

        val animatorSet = AnimatorSet()
        animatorSet.addListener(object : Animator.AnimatorListener{
            override fun onAnimationRepeat(animation: Animator?) {
                //重复
            }

            override fun onAnimationEnd(animation: Animator?) {
                //结束
            }

            override fun onAnimationCancel(animation: Animator?) {
                //取消
            }

            override fun onAnimationStart(animation: Animator?) {
                //开始
            }

        })
        animatorSet.playSequentially(alphaAnimator, rotationAnimator)
        animatorSet.start()

 

In fact, you don’t know if you are confident, have you found out that the attribute animation has not found setFillAfter() to set whether the animation is restored to the initial state.

Well, we can monitor the end of the animation, and then set the target state when it ends

public class ResetAnimatorListenerAdapter extends AnimatorListenerAdapter {

    View animatorView;

    public ResetAnimatorListenerAdapter(View animatorView) {
        this.animatorView = animatorView;
    }

    @Override
    public void onAnimationEnd(Animator animation) {
        super.onAnimationEnd(animation);
        //重置 animatorView 的状态
        animatorView.setTranslationY(0);
        animatorView.setAlpha(0.0f);
    }

    @Override
    public void onAnimationCancel(Animator animation) {
        super.onAnimationCancel(animation);
        
    }
    
}

 

 

The above mentioned that Chase code dynamically sets the animation, in fact, it can also be set through xml, not to mention the specifics. Because it is not recommended to use xml, it is relatively dead and cannot be changed.

 

Finally, the caveats of animation

1: Prevent OOM and avoid using frame animation with more pictures. . .

2: To prevent memory leaks, stop the animation when the View exits invisible

3: The attribute animation is used above Android3.0, lower than this version, you can use the compatible library

4: View animation, when the View is moved, the View.GONE setting will sometimes become invalid. At this time, just call View.clearAnimation() to clear the animation.

5: When the animation is in progress, you can turn on hardware acceleration to improve fluency.

 

There is no problem with the above code, please leave a message to correct any problems, thank you

Guess you like

Origin blog.csdn.net/Leo_Liang_jie/article/details/90812632