Open In App

Star Shower in Android using Android Property Animation

Last Updated : 05 Aug, 2021
Improve
Improve
Like Article
Like
Save
Share
Report

In this article, we are going to create a star shower using android property animation. We will create a slightly more involved animation, animating multiple properties on multiple objects. For this effect, a button click will result in the creation of a star with a random size, which will be added to the background container, just out of view of the top of that container. The star will proceed to fall down to the bottom of the screen by accelerating as it goes. the star will also rotate when it falls. A sample GIF is given below to get an idea about what we are going to do in this article. Note that we are going to implement this project using the Kotlin language. 

Star Shower in Android using Android Property Animation

Step by Step Implementation

Step 1: Create a New Project

To create a new project in Android Studio please refer to How to Create/Start a New Project in Android Studio. Note that select Kotlin as the programming language. 

Step 2: We will need some local variables to hold the state.

  • a reference to the starfield ViewGroup which is just the parent of the current star view.
  • the width and height of that container which will be used to calculate the end translation values for the falling stars.
  • the default width and height of the star which will be changed later with a scale factor to get different-sized stars.

val container = star.parent as ViewGroup

val containerW = container.width

val containerH = container.height

var starW: Float = star.width.toFloat()

Create a new View to hold the star as it is a VectorDrawable asset, make use of an AppCompatImageView, which has the ability to host that kind of resource. Create the star and add it to the background container.

val newStar = AppCompatImageView(this)

newStar.setImageResource(R.drawable.ic_star)

newStar.layoutParams = FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT,

                    FrameLayout.LayoutParams.WRAP_CONTENT)

Step 3: Sizing and positioning the star

We haven’t defined the position of the image in the container, so it’s positioned at (0, 0) by default. We will fix it in this step.

(i) Set the size of the star. Modify the star to have a random size, from .1x to 1.6x of its default size. Use this scale factor to change the cached width/height values.

newStar.scaleX = Math.random().toFloat() * 1.5f + .1f

newStar.scaleY = newStar.scaleX

starW *= newStar.scaleX

starH *= newStar.scaleY

You have now cached the star’s pixel H/W stored in starW and starH:

(ii) Now position the new star. It should appear randomly somewhere in between the left edge to the right edge horizontally. The below code uses the width of the star to position it from halfway of the screen on the left (-starW / 2) to halfway off the screen on the right (with the star positioned at (containerW – starW / 2). 

newStar.translationX = Math.random().toFloat() * containerW – starW / 2

Step 4: Creating animators for star rotation and falling

It’s time to work on the animation. The star should rotate as it falls downwards. We can animate two properties together. The rotation will use a smooth linear motion (moving at a constant rate over the entire rotation animation), while the falling animation will use an accelerating motion (simulating gravity pulling the star downward at a constantly faster rate). So you’ll create two animators and add an interpolator to each.

(i) First, create two animators, along with their interpolators:

val mover = ObjectAnimator.ofFloat(newStar, View.TRANSLATION_Y,   -starH, containerH + starH)

mover.interpolator = AccelerateInterpolator(1f) // causes a gentle acceleration motion

val rotator = ObjectAnimator.ofFloat(newStar, View.ROTATION,

           (Math.random() * 1080).toFloat())  // star rotates a random amount between 0 and 1080 degrees 

rotator.interpolator = LinearInterpolator() //the rotation will proceed at a constant rate as the star falls

The mover animation is responsible for making the star “fall.” It animates the TRANSLATION_Y property but causing vertical instead of horizontal motion. The code animates from -starH to (containerH + starH), which effectively places it just off the container at the top and moves it until it’s just outside the container at the bottom, as shown here:

Step 5: Running the animations in parallel with AnimatorSet

Now it is time to put these two animators together into a single AnimatorSet. It is basically a group of animations, along with instructions on when to run those animations. It can play animations in parallel.

(i) Create the AnimatorSet and add the child animators to it. The default animation time of 300 milliseconds is too quick for the falling stars, so set the duration to a random number between 500 and 2000 milliseconds, so that stars can fall at different speeds.

val set = AnimatorSet()

set.playTogether(mover, rotator)

set.duration = (Math.random() * 1500 + 500).toLong()

(ii) Once newStar has fallen off the bottom of the screen, it should be removed from the container. Set a simple listener to wait for the end of the animation and remove it. Then start the animation.

set.addListener(object : AnimatorListenerAdapter() {

 override fun onAnimationEnd(animation: Animator?) {

container.removeView(newStar)

 }

})

set.start()

Navigate to the app > res > layout > activity_main.xml and add the below code to that file. Below is the complete code for the activity_main.xml file. 

XML




<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
  
    <Button
        android:id="@+id/showerButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="star shower"
        app:layout_constraintBottom_toTopOf="@+id/frameLayout"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
      
    <FrameLayout
        android:id="@+id/frameLayout"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:background="@color/black"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/showerButton"
        app:layout_constraintVertical_bias="0.0">
  
        <ImageView
            android:id="@+id/star"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:contentDescription="star image"
            android:visibility="invisible"
            app:srcCompat="@drawable/ic_star"
            tools:ignore="VectorDrawableCompat" />
  
    </FrameLayout>
  
</androidx.constraintlayout.widget.ConstraintLayout>


Go to the MainActivity.kt file and refer to the following code. Below is the complete code for the MainActivity.kt file. Comments are added inside the code to understand the code in more detail.

Kotlin




import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import android.view.animation.AccelerateInterpolator
import android.view.animation.LinearInterpolator
import android.widget.Button
import android.widget.FrameLayout
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.AppCompatImageView
  
class MainActivity : AppCompatActivity() {
      
    lateinit var showerButton: Button
    lateinit var star: ImageView
  
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        showerButton = findViewById<Button>(R.id.showerButton)
        star = findViewById(R.id.star)
  
        showerButton.setOnClickListener {
            shower()
        }
    }
  
    private fun shower() {
        // Create a new star view in a random X position above the container.
        // Make it rotateButton about its center as it falls to the bottom.
  
        // Local variables
        val container = star.parent as ViewGroup
        val containerW = container.width
        val containerH = container.height
        var starW: Float = star.width.toFloat()
        var starH: Float = star.height.toFloat()
  
        // Create the new star (an ImageView in layout holding drawable star image) 
        // and add it to the container
        val newStar = AppCompatImageView(this)
        newStar.setImageResource(R.drawable.ic_star)
        newStar.layoutParams = FrameLayout.LayoutParams(
            FrameLayout.LayoutParams.WRAP_CONTENT,
            FrameLayout.LayoutParams.WRAP_CONTENT
        )
        container.addView(newStar)
  
        // Scale the view randomly between 10-160% of its default size
        newStar.scaleX = Math.random().toFloat() * 1.5f + .1f
        newStar.scaleY = newStar.scaleX
        starW *= newStar.scaleX
        starH *= newStar.scaleY
  
        // Position the view at a random place between 
        // the left and right edges of the container
        newStar.translationX = Math.random().toFloat() * containerW - starW / 2
  
        // Create an animator that moves the view from a starting position right about the container
        // to an ending position right below the container. Set an accelerate interpolator to give
        // it a gravity/falling feel
        val mover = ObjectAnimator.ofFloat(newStar, View.TRANSLATION_Y, -starH, containerH + starH)
        mover.interpolator = AccelerateInterpolator(1f)
  
        // Create an animator to rotateButton the 
        // view around its center up to three times
        val rotator = ObjectAnimator.ofFloat(
            newStar, View.ROTATION,
            (Math.random() * 1080).toFloat()
        )
        rotator.interpolator = LinearInterpolator()
  
        // Use an AnimatorSet to play the falling and 
        // rotating animators in parallel for a duration
        // of a half-second to two seconds
        val set = AnimatorSet()
        set.playTogether(mover, rotator)
        set.duration = (Math.random() * 1500 + 500).toLong()
  
        // When the animation is done, remove the 
        // created view from the container
        set.addListener(object : AnimatorListenerAdapter() {
            override fun onAnimationEnd(animation: Animator?) {
                container.removeView(newStar)
            }
        })
  
        // Start the animation
        set.start()
    }
  
    private fun ObjectAnimator.disableViewDuringAnimation(view: View) {
  
        // This extension method listens for start/end 
        // events on an animation and disables
        // the given view for the entirety of that animation.
        addListener(object : AnimatorListenerAdapter() {
            override fun onAnimationStart(animation: Animator?) {
                view.isEnabled = false
            }
  
            override fun onAnimationEnd(animation: Animator?) {
                view.isEnabled = true
            }
        })
    }
}


Now, Run your application. You can click on the “STAR SHOWER” button multiple times, creating a new star and new animation each time.

Output:

Source code: Click Here



Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads