Star Shower in Android using Android Property Animation
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.
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 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
Please Login to comment...