Star Shower in Android using Android Property Animation
Last Updated :
05 Aug, 2021
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() {
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()
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)
newStar.scaleX = Math.random().toFloat() * 1 .5f + .1f
newStar.scaleY = newStar.scaleX
starW *= newStar.scaleX
starH *= newStar.scaleY
newStar.translationX = Math.random().toFloat() * containerW - starW / 2
val mover = ObjectAnimator.ofFloat(newStar, View.TRANSLATION_Y, -starH, containerH + starH)
mover.interpolator = AccelerateInterpolator(1f)
val rotator = ObjectAnimator.ofFloat(
newStar, View.ROTATION,
(Math.random() * 1080 ).toFloat()
)
rotator.interpolator = LinearInterpolator()
val set = AnimatorSet()
set.playTogether(mover, rotator)
set.duration = (Math.random() * 1500 + 500 ).toLong()
set.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
container.removeView(newStar)
}
})
set.start()
}
private fun ObjectAnimator.disableViewDuringAnimation(view: View) {
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
Share your thoughts in the comments
Please Login to comment...