• Brief introduction:

Recently, a bubble effect needs to be displayed in the project. When the number in the interface changes, there will be a bubble animation with the number +1, as shown in the picture below:

Dynamic effect description: First, the starting point of displacement is in the middle position with the number, and the transparency is 0% at the beginning, and then the upward displacement begins, and the transparency changes gradually to 100%, and stays for two seconds, then the next displacement begins, and the next displacement continues upward, and the transparency changes gradually from 100% to 0%, and then the bubble disappears.

  • Ideas:

Let’s take it step by step

First of all, you have to have a translation effect, and the translation effect has to be divided into two parts, first of all, you have to move one part up, and then you have to move one part up based on the first displacement, and this is the translation part.

Then there is the gradient, which goes from 0% to 100% opacity when the first section is panned, followed by 100% to 0% opacity when the second section is panned.

Ok, it’s pretty easy to break it down, so let’s start with the translation.

  • Translation of animation
// Build properties animation - Pan animation
val translate1Anim = TranslateAnimation(0.0f, 0.0f, 0.0f, -100.0f)
// Whether to stay in the moved position after the animation ends, true: stay
animation1Set.fillAfter = true
// Animation execution time (milliseconds)
animation1Set.duration = 1000

Copy the code

The above code is easy to understand. First we create a panned property animation object. The four parameters in the constructor are:

  • The difference between the point at which the float fromXDelta animation starts and the current View’s X coordinate
  • The difference between the point at which the float toXDelta animation ends and the current View’s X coordinate
  • Float fromYDelta The difference between the point at which the animation starts and the Y coordinate of the current View
  • Float toYDelta The difference between the point at which the animation starts and the Y coordinate of the current View

I need to move the control up here, so I pass -100.0f in the last argument

So once you’ve created your animation and you’re ready to try it out, I’m going to paste a layout here, and I’m going to use my CardView to do the circular layout, and I’m going to have a TextView nested inside it

<androidx.cardview.widget.CardView
    android:id="@+id/animation_card"
    android:layout_width="30dp"
    android:layout_height="30dp"
    android:layout_marginTop="120dp"
    app:cardBackgroundColor="#06C584"
    app:cardCornerRadius="15dp"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent">

    <TextView
        android:id="@+id/animation_tv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:src="@mipmap/ic_launcher_round"
        android:text="+ 12"
        android:textColor="@color/white"
        android:textSize="10sp" />

</androidx.cardview.widget.CardView>
Copy the code

Ok, so what we’re going to do is we’re going to have CardView call startAnimation() to do the animation, and we’re done, so we’re not going to post the animation, we’re going to try it out;

binding.animationCard.startAnimation(translate1Anim)
Copy the code

The second move is based on the first move, stay for 2 seconds and then continue to move, stay for 2 seconds, in fact, still need to create a translation property of the object:

// Move the second step of the animation up by 100 based on the position of the first step
val translate2Anim = TranslateAnimation(0.0f, 0.0f, -100.0f, -200.0f)
// Return to the original position after animation
animation1Set.fillAfter = false
// Animation execution time (milliseconds)
animation1Set.duration = 1000
Copy the code

Still remember the first fill in the last period of displacement parameters, the first grade to move on the Y axis – 100 – f, and we set it as not back, so now controls the position of the is – 100 – f on the Y axis, translation and then we need it to continue to upgrade, so the distance to x2, namely – 200 – f, don’t understand it doesn’t matter. I’ll run it in a second and see how it works;

Then the second paragraph a moving picture also write, how to link up the two animations, should have a callback to tell me Animation to finish, after processing, and then to do these attributes Animation has already help us think, provides an Animation. The property Animation AnimationListener interface, Implementing this interface requires overloading three callback methods, namely the animation start, end, and repeat lifecycle methods, so we can make it perform a second displacement in the end-of-lifecycle method:

translate1Anim.setAnimationListener(object : Animation.AnimationListener {
    override fun onAnimationStart(animation: Animation?) {
        // Display the layout before animation starts
        binding.animationCard.visibility = View.VISIBLE
    }

    override fun onAnimationEnd(animation: Animation?) {
        // After the first animation, pause for 2 seconds, then start the second animation
        GlobalScope.launch(Dispatchers.Main) {
            delay(2000)
            binding.animationCard.startAnimation(translate2Anim)
        }
    }

    override fun onAnimationRepeat(animation: Animation?){}})Copy the code

As you can see in the code, I’m going to start with the layout, and then the animation is going to execute, and then I’m going to go to the onAnimationEnd method, and in this method I’m going to execute the second bit-move drawing, and I’m going to do a two-second wait using the Kotlin coroutine, Globalscope.launch (dispatchers.main){} enable the coroutine. If you are not familiar with Kotlin or coroutine, you can search for it on Baidu. It is a very powerful feature and worth learning

Using Kotlin coroutines requires the introduction of the following dependencies:

implementation "Org. Jetbrains. Kotlinx: kotlinx coroutines - android: 1.4.2." "
implementation 'androidx. Core: the core - KTX: 1.3.2'
implementation "Org. Jetbrains. Kotlin: kotlin stdlib - jdk8:1.4.32"
Copy the code

Now that we have the pan animation, let’s see what it looks like

(The picture is a bit too confusing, so you can see the effect ** – **)

  • The gradient of animation

Gradient animation is actually relatively simple, in fact, from 0-1 and then from 1-0 a gradient conversion, without further ado directly paste the code

// The first step of the gradient animation, from transparent to solid
val alpha1Anim = AlphaAnimation(0.0f, 1.0f)
// The second step of the gradient animation is from solid to transparent
val alpha2Anim = AlphaAnimation(1.0f, 0.1f)
Copy the code

Ok, this is two pieces of gradient animation code, which is actually instantiated two objects. The two parameters in the construction represent the transition of transparency, ranging from 0.0f to 1.0f (0% to 100%).

Gradient animation is done, so here’s the question, how do you make the two animations work together?

There is also a class called AnimationSet, which can add various animation effects and execute them together, so here we use AnimationSet to add pan and gradient animations:

// For the first round of animation set, pan up and gradient to solid color, and set the animation duration to 1 second and stay in the pan position after the animation ends
val animation1Set = AnimationSet(true)
animation1Set.fillAfter = true
animation1Set.duration = 1000
animation1Set.addAnimation(translate1Anim)
animation1Set.addAnimation(alpha1Anim)

// For the second round of animation set, pan it up and change it to transparent color, and set the animation duration to 1 second and return to original position after animation
val animation2Set = AnimationSet(true)
animation2Set.fillAfter = false
animation2Set.duration = 1000
animation2Set.addAnimation(translate2Anim)
animation2Set.addAnimation(alpha2Anim)

Copy the code

The control then calls startAnimation() and instead of panning the animation object, it passes in the animation set

// The first animation starts
binding.animationCard.startAnimation(animation1Set)
Copy the code

Likewise, the state callback interface of an animation

animation1Set.setAnimationListener(object : Animation.AnimationListener {
    override fun onAnimationStart(animation: Animation?) {
        // Display the layout before animation starts
        binding.animationCard.visibility = View.VISIBLE
    }

    override fun onAnimationEnd(animation: Animation?) {
        // After the first animation, pause for 2 seconds, then start the second animation
        GlobalScope.launch(Dispatchers.Main) {
            delay(2000)
            binding.animationCard.startAnimation(animation2Set)
        }
    }

    override fun onAnimationRepeat(animation: Animation?) {
    }
})

animation2Set.setAnimationListener(object : Animation.AnimationListener {
    override fun onAnimationStart(animation: Animation?) {}
    override fun onAnimationEnd(animation: Animation?) {
        // The layout is hidden after the end
        binding.animationCard.visibility = View.GONE
    }

    override fun onAnimationRepeat(animation: Animation?){}})Copy the code

Ok, so far our entire animation effect is basically complete, the following is the complete code:


private fun startTranslateAnimation() {
    // In the first step of the pan animation, move 100 up from the origin,
    val translate1Anim = TranslateAnimation(0.0f, 0.0f, 0.0f, -100.0f)
    
    // Move the second step of the animation, based on the position of the first step, and then move up 60
    val translate2Anim = TranslateAnimation(0.0f, 0.0f, -100.0f, -200.0f)

    // The first step of the gradient animation, from transparent to solid
    val alpha1Anim = AlphaAnimation(0.0f, 1.0f)
    // The second step of the gradient animation is from solid to transparent
    val alpha2Anim = AlphaAnimation(1.0f, 0.1f)

    // For the first round of animation set, pan up and gradient to solid color, and set the animation duration to 1 second and stay in the pan position after the animation ends
    val animation1Set = AnimationSet(true)
    animation1Set.fillAfter = true
    animation1Set.duration = 1000
    animation1Set.addAnimation(translate1Anim)
    animation1Set.addAnimation(alpha1Anim)

    // For the second round of animation set, pan it up and change it to transparent color, and set the animation duration to 1 second and return to original position after animation
    val animation2Set = AnimationSet(true)
    animation2Set.fillAfter = false
    animation2Set.duration = 1000
    animation2Set.addAnimation(translate2Anim)
    animation2Set.addAnimation(alpha2Anim)
    // The first animation starts
    binding.animationCard.startAnimation(animation1Set)

    animation1Set.setAnimationListener(object : Animation.AnimationListener {
        override fun onAnimationStart(animation: Animation?) {
            // Display the layout before animation starts
            binding.animationCard.visibility = View.VISIBLE
        }

        override fun onAnimationEnd(animation: Animation?) {
            // After the first animation, pause for 2 seconds, then start the second animation
            GlobalScope.launch(Dispatchers.Main) {
                delay(2000)
                binding.animationCard.startAnimation(animation2Set)
            }
        }

        override fun onAnimationRepeat(animation: Animation?) {
        }
    })

    animation2Set.setAnimationListener(object : Animation.AnimationListener {
        override fun onAnimationStart(animation: Animation?) {}
        override fun onAnimationEnd(animation: Animation?) {
            binding.animationCard.visibility = View.GONE
            binding.animationCard.startAnimation(animation1Set)
        }

        override fun onAnimationRepeat(animation: Animation?){}})}Copy the code