Android includes many options to add animations to your app. In this tutorial, we will learn how to add a type of animation called frame-by-frame animation into our Android app.
At the end of the tutorial, you would have learned:
- How to add frame-by-frame animation to an Android app.
- Android Studio. The version used in this tutorial is Android Studio Dolphin | 2021.3.1.
- Intermediate Android.
To follow along with the tutorial, perform the steps below:
-
Create a new Android project with the default Empty Activity.
-
Download the free Santa sprites from Game Art 2d.
-
Unpack the archive and import all files in the png directory to the project (keep the same names that Android Studio generated for you).
-
Copy and paste all the
<string>
resources below into your projects strings.xml file.<string name="idle">Idle</string> <string name="walk">Walk</string> <string name="run">Run</string> <string name="jump">Jump</string> <string name="die">Die</string> <string name="slide">Slide</string>
-
Replace all the content of activity_main.xml with the code below.
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <ImageView android:id="@+id/image_santa" android:layout_width="0dp" android:layout_height="300dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" android:src="@drawable/idle__1_" /> <Button android:id="@+id/idle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:text="@string/idle" app:layout_constraintBottom_toTopOf="@id/walk" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/image_santa" /> <Button android:id="@+id/walk" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/walk" app:layout_constraintBottom_toTopOf="@id/run" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/idle" /> <Button android:id="@+id/run" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/run" app:layout_constraintBottom_toTopOf="@id/jump" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/walk" /> <Button android:id="@+id/jump" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/jump" app:layout_constraintBottom_toTopOf="@id/slide" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/run" /> <Button android:id="@+id/slide" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/slide" app:layout_constraintBottom_toTopOf="@id/die" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/jump" /> <Button android:id="@+id/die" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/die" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/slide" /> </androidx.constraintlayout.widget.ConstraintLayout>
Our starter application contains a single screen with 6 buttons, allowing the user to start animations of the following actions
- Idle.
- Walk.
- Run.
- Jump.
- Slide.
- Die.
There are two steps that we need to do next to allow the animation to work:
- Set up the animation resources that describe the frame-by-frame image.
- Set up button listeners to activate the animation.
The type of animation that we are aiming for can be achieved by loading images in a sequence. We can either define this animation in code using the AnimationDrawable class or using a pre-defined Frame Animation Resource. The frame animation resource starts with an <animation-list>
tag, and then each image is defined in an <item>
tag.
To create the frame animation resource for the idle action, create a new file called idle.xml and copy and paste the code below into it.
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/idle__1_" android:duration="75" />
<item android:drawable="@drawable/idle__2_" android:duration="75" />
<item android:drawable="@drawable/idle__3_" android:duration="75" />
<item android:drawable="@drawable/idle__4_" android:duration="75" />
<item android:drawable="@drawable/idle__5_" android:duration="75" />
<item android:drawable="@drawable/idle__6_" android:duration="75" />
<item android:drawable="@drawable/idle__7_" android:duration="75" />
<item android:drawable="@drawable/idle__8_" android:duration="75" />
<item android:drawable="@drawable/idle__9_" android:duration="75" />
<item android:drawable="@drawable/idle__10_" android:duration="75" />
<item android:drawable="@drawable/idle__11_" android:duration="75" />
<item android:drawable="@drawable/idle__12_" android:duration="75" />
<item android:drawable="@drawable/idle__13_" android:duration="75" />
<item android:drawable="@drawable/idle__14_" android:duration="75" />
<item android:drawable="@drawable/idle__15_" android:duration="75" />
<item android:drawable="@drawable/idle__16_" android:duration="75" />
</animation-list>
Each <item>
must contain the source to the actual drawable resource (the images that we imported earlier). The duration
attribute dictates how long to show a frame, in milliseconds.
If you switch to Split/Design view, you can also preview the animation.
Now that we already know how to define the idle animation resource, create the rest of the animations with the names below.
-
walk.xml.
<?xml version="1.0" encoding="utf-8"?> <animation-list xmlns:android="http://schemas.android.com/apk/res/android"> <item android:drawable="@drawable/walk__1_" android:duration="75" /> <item android:drawable="@drawable/walk__2_" android:duration="75" /> <item android:drawable="@drawable/walk__3_" android:duration="75" /> <item android:drawable="@drawable/walk__4_" android:duration="75" /> <item android:drawable="@drawable/walk__5_" android:duration="75" /> <item android:drawable="@drawable/walk__6_" android:duration="75" /> <item android:drawable="@drawable/walk__7_" android:duration="75" /> <item android:drawable="@drawable/walk__8_" android:duration="75" /> <item android:drawable="@drawable/walk__9_" android:duration="75" /> <item android:drawable="@drawable/walk__10_" android:duration="75" /> <item android:drawable="@drawable/walk__11_" android:duration="75" /> <item android:drawable="@drawable/walk__12_" android:duration="75" /> <item android:drawable="@drawable/walk__13_" android:duration="75" /> </animation-list>
-
run.xml.
<?xml version="1.0" encoding="utf-8"?> <animation-list xmlns:android="http://schemas.android.com/apk/res/android"> <item android:drawable="@drawable/run__1_" android:duration="75" /> <item android:drawable="@drawable/run__2_" android:duration="75" /> <item android:drawable="@drawable/run__3_" android:duration="75" /> <item android:drawable="@drawable/run__4_" android:duration="75" /> <item android:drawable="@drawable/run__5_" android:duration="75" /> <item android:drawable="@drawable/run__6_" android:duration="75" /> <item android:drawable="@drawable/run__7_" android:duration="75" /> <item android:drawable="@drawable/run__8_" android:duration="75" /> <item android:drawable="@drawable/run__9_" android:duration="75" /> <item android:drawable="@drawable/run__10_" android:duration="75" /> <item android:drawable="@drawable/run__11_" android:duration="75" /> </animation-list>
-
jump.xml.
<?xml version="1.0" encoding="utf-8"?> <animation-list xmlns:android="http://schemas.android.com/apk/res/android"> <item android:drawable="@drawable/jump__1_" android:duration="75" /> <item android:drawable="@drawable/jump__2_" android:duration="75" /> <item android:drawable="@drawable/jump__3_" android:duration="75" /> <item android:drawable="@drawable/jump__4_" android:duration="75" /> <item android:drawable="@drawable/jump__5_" android:duration="75" /> <item android:drawable="@drawable/jump__6_" android:duration="75" /> <item android:drawable="@drawable/jump__7_" android:duration="75" /> <item android:drawable="@drawable/jump__8_" android:duration="75" /> <item android:drawable="@drawable/jump__9_" android:duration="75" /> <item android:drawable="@drawable/jump__10_" android:duration="75" /> <item android:drawable="@drawable/jump__11_" android:duration="75" /> <item android:drawable="@drawable/jump__12_" android:duration="75" /> <item android:drawable="@drawable/jump__13_" android:duration="75" /> <item android:drawable="@drawable/jump__14_" android:duration="75" /> <item android:drawable="@drawable/jump__15_" android:duration="75" /> <item android:drawable="@drawable/jump__16_" android:duration="75" /> </animation-list>
-
slide.xml
<?xml version="1.0" encoding="utf-8"?> <animation-list xmlns:android="http://schemas.android.com/apk/res/android"> <item android:drawable="@drawable/slide__1_" android:duration="75" /> <item android:drawable="@drawable/slide__2_" android:duration="75" /> <item android:drawable="@drawable/slide__3_" android:duration="75" /> <item android:drawable="@drawable/slide__4_" android:duration="75" /> <item android:drawable="@drawable/slide__5_" android:duration="75" /> <item android:drawable="@drawable/slide__6_" android:duration="75" /> <item android:drawable="@drawable/slide__7_" android:duration="75" /> <item android:drawable="@drawable/slide__8_" android:duration="75" /> <item android:drawable="@drawable/slide__9_" android:duration="75" /> <item android:drawable="@drawable/slide__10_" android:duration="75" /> <item android:drawable="@drawable/slide__11_" android:duration="75" /> </animation-list>
-
die.xml.
<?xml version="1.0" encoding="utf-8"?> <animation-list xmlns:android="http://schemas.android.com/apk/res/android"> <item android:drawable="@drawable/dead__1_" android:duration="75" /> <item android:drawable="@drawable/dead__2_" android:duration="75" /> <item android:drawable="@drawable/dead__3_" android:duration="75" /> <item android:drawable="@drawable/dead__4_" android:duration="75" /> <item android:drawable="@drawable/dead__5_" android:duration="75" /> <item android:drawable="@drawable/dead__6_" android:duration="75" /> <item android:drawable="@drawable/dead__7_" android:duration="75" /> <item android:drawable="@drawable/dead__8_" android:duration="75" /> <item android:drawable="@drawable/dead__9_" android:duration="75" /> <item android:drawable="@drawable/dead__10_" android:duration="75" /> <item android:drawable="@drawable/dead__11_" android:duration="75" /> <item android:drawable="@drawable/dead__12_" android:duration="75" /> <item android:drawable="@drawable/dead__12_" android:duration="75" /> <item android:drawable="@drawable/dead__13_" android:duration="75" /> <item android:drawable="@drawable/dead__14_" android:duration="75" /> <item android:drawable="@drawable/dead__15_" android:duration="75" /> <item android:drawable="@drawable/dead__16_" android:duration="75" /> <item android:drawable="@drawable/dead__17_" android:duration="75" /> </animation-list>
To use the animations that we have defined, first, in onCreate()
, get the reference to the ImageView that displays Santa.
val santaImage = findViewById<ImageView>(R.id.image_santa)
We have six buttons in total, calling findViewById()
6 different times would be a chore, so let us use a Map to reduce repetitive code and improve readability.
// Add Button IDs and Animation IDs into map for easy looping.
val animationMap = mapOf(
Pair(R.id.idle, R.drawable.idle),
Pair(R.id.walk, R.drawable.walk),
Pair(R.id.run, R.drawable.run),
Pair(R.id.jump, R.drawable.jump),
Pair(R.id.slide, R.drawable.slide),
Pair(R.id.die, R.drawable.die)
)
for ((buttonId, animationId) in animationMap){
findViewById<Button>(buttonId).setOnClickListener {
santaImage.setImageResource(animationId)
(santaImage.drawable as AnimationDrawable).start()
}
}
The two most important lines of code above are
santaImage.setImageResource(animationId)
(santaImage.drawable as AnimationDrawable).start()
- Every time we need to play a different animation, we need to replace the image resource with the corresponding animation resource.
- We then cast the
drawable
to AnimationDrawable and then callstart()
to start the animation.
We are now ready to run the app, it should behave similarly to the animation below.
We have learned how to animate sprites in this tutorial. The full project code can be found at https://github.com/dmitrilc/DaniwebAnimateSpritesAnimationDrawable.