Android Compose — Making Splash Screen with SplashScreen API and AnimatedVector Resource

Samed Harman
5 min readDec 25, 2023

--

Hi, In this article we are going to make this splash screen example in Android Jetpack Compose using SplashScreen API.

First of all we need to add Android Splash Screen dependency in build.gradle app module file.

implementation("androidx.core:core-splashscreen:1.0.1")

Now we can create a simple viewmodel for managing splash screen logic.

@HiltViewModel
class SplashViewModel @Inject constructor() : ViewModel(){
private val splashShowFlow = MutableStateFlow(true)
val isSplashShow = splashShowFlow.asStateFlow()

init {
viewModelScope.launch {
delay(3000)
splashShowFlow.value = false
}
}
}

You can see “@HiltViewModel” in here because I was using dependency injection with Android Hilt library but you can ignore this stage. Because it is not within the scope of the subject. But if you want to be informed about this, you can check it out here.

Now we are going to create splash screen api configurations. For that, we are create value resource file in this path res → values → splash.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.App.Starting" parent="Theme.SplashScreen">
<item name="windowSplashScreenAnimatedIcon">@drawable/splash_animated_vector</item>
<item name="windowSplashScreenBackground">@color/white</item>
<item name="postSplashScreenTheme">@style/Theme.Subject_segmentation_compose</item>
</style>
</resources>

In this resource file you can see windowSplashScreenAnimatedIcon property. We are going to give it our animated vector file. That file looks like,

<animated-vector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:drawable">
<vector
android:name="vector"
android:width="1024dp"
android:height="1024dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<group
android:name="group"
android:pivotX="512"
android:pivotY="512"
android:scaleX="0.3"
android:scaleY="0.3">
<path
android:name="path"
android:pathData="M 401.1 616.7 C 401.1 652.194 412.342 686.792 433.204 715.507 C 454.067 744.222 483.498 765.604 517.254 776.573 C 551.011 787.541 587.389 787.541 621.146 776.573 C 654.902 765.604 684.333 744.222 705.196 715.507 C 726.058 686.792 737.3 652.194 737.3 616.7 C 737.3 572.134 719.577 529.348 688.065 497.835 C 656.552 466.323 613.766 448.6 569.2 448.6 C 524.634 448.6 481.848 466.323 450.335 497.835 C 418.823 529.348 401.1 572.134 401.1 616.7 Z"
android:fillColor="#ffb89a"
android:strokeWidth="1"/>
<path
android:name="path_1"
android:pathData="M 522.7 765.1 C 410.1 765.1 318.5 673.5 318.5 560.9 C 318.5 448.3 410.1 356.7 522.7 356.7 C 562 356.7 600.2 367.9 633.2 389.1 C 665.2 409.7 690.8 438.8 707.2 473.1 C 714.3 488.1 708 506 693 513.1 C 678 520.2 660.1 513.9 653 498.9 C 641.4 474.6 623.3 454.1 600.7 439.5 C 577.5 424.5 550.5 416.6 522.7 416.6 C 443.2 416.6 378.5 481.3 378.5 560.8 C 378.5 640.3 443.2 705 522.7 705 C 542.3 705 561.3 701.2 579.1 693.6 C 594.3 687.1 612 694.2 618.4 709.5 C 624.9 724.7 617.8 742.4 602.5 748.8 C 577.3 759.6 550.4 765.1 522.7 765.1 Z"
android:fillColor="#33cc99"
android:strokeWidth="1"/>
<path
android:name="path_2"
android:pathData="M 686.7 659.6 C 683.3 659.6 679.9 659 676.6 657.8 C 661 652.2 652.9 635 658.5 619.4 C 662.4 608.6 664.9 597.3 666.1 585.9 C 667.8 569.4 682.6 557.4 699 559.2 C 715.5 560.9 727.4 575.7 725.7 592.1 C 724 608.3 720.4 624.3 714.9 639.6 C 710.6 651.9 699.1 659.6 686.7 659.6 Z M 770 386 C 770 394.298 773.3 402.265 779.168 408.132 C 785.035 414 793.002 417.3 801.3 417.3 C 809.598 417.3 817.565 414 823.432 408.132 C 829.3 402.265 832.6 394.298 832.6 386 C 832.6 377.702 829.3 369.735 823.432 363.868 C 817.565 358 809.598 354.7 801.3 354.7 C 793.002 354.7 785.035 358 779.168 363.868 C 773.3 369.735 770 377.702 770 386 Z"
android:fillColor="#33cc99"
android:strokeWidth="1"/>
<path
android:name="path_3"
android:pathData="M 821.1 240.6 L 760.3 240.6 L 760.3 240.2 C 758.6 240.5 756.8 240.6 755.1 240.6 C 732 240.6 713.2 216 713.2 185.6 C 713.2 182.1 713.5 178.6 713.9 175.3 L 713.9 169.4 C 713.9 129.8 681.5 97.4 641.9 97.4 L 386.3 97.4 C 346.7 97.4 314.3 129.8 314.3 169.4 L 314.3 177.4 C 314.6 180.1 314.8 182.8 314.8 185.6 C 314.8 216 292 240.6 268.9 240.6 C 268.2 240.6 267.4 240.6 266.7 240.5 L 266.7 240.6 L 254.3 240.6 L 254.3 184.8 C 254.3 168.2 240.9 154.8 224.3 154.8 C 207.7 154.8 194.3 168.2 194.3 184.8 L 194.3 241 C 122 246.5 64.6 307.3 64.6 381 L 64.6 784.2 C 64.6 861.4 127.8 924.6 205 924.6 L 821.3 924.6 C 898.5 924.6 961.7 861.4 961.7 784.2 L 961.7 381 C 961.5 303.8 898.3 240.6 821.1 240.6 Z M 901.5 784.1 C 901.5 805.4 893.1 825.6 877.8 840.8 C 862.5 856.1 842.4 864.5 821.1 864.5 L 204.9 864.5 C 183.6 864.5 163.4 856.1 148.2 840.8 C 132.9 825.5 124.5 805.4 124.5 784.1 L 124.5 381 C 124.5 359.7 132.9 339.5 148.2 324.3 C 163.5 309 183.6 300.6 204.9 300.6 C 204.9 300.6 241.9 300.9 268.8 300.6 C 295.7 300.3 330.6 280.6 330.6 280.6 C 337.8 275.1 344.4 268.5 350.1 260.9 C 365.9 240.1 374.7 213.4 374.7 185.7 C 374.7 182 374.5 178.3 374.2 174.6 L 374.2 169.5 C 374.2 163 379.7 157.5 386.2 157.5 L 641.8 157.5 C 648.3 157.5 653.8 163 653.8 169.5 L 653.8 171.8 C 653.3 176.4 653.1 181.1 653.1 185.7 C 653.1 213.4 661.8 240.2 677.7 260.9 C 684.3 269.5 691.9 276.9 700.3 282.9 C 700.3 282.9 727 300.7 760.3 300.7 L 821.1 300.7 C 842.4 300.7 862.6 309.1 877.8 324.4 C 893.1 339.7 901.5 359.8 901.5 381.1 L 901.5 784.1 Z"
android:fillColor="#45484c"
android:strokeWidth="1"/>
</group>
</vector>
</aapt:attr>
<target android:name="group">
<aapt:attr name="android:animation">
<set>
<objectAnimator
android:propertyName="scaleX"
android:duration="700"
android:valueFrom="0.3"
android:repeatCount="infinite"
android:repeatMode="reverse"
android:valueTo="0.5"
android:valueType="floatType"
android:interpolator="@android:anim/overshoot_interpolator"/>
<objectAnimator
android:propertyName="scaleY"
android:duration="700"
android:valueFrom="0.3"
android:valueTo="0.5"
android:repeatMode="reverse"
android:repeatCount="infinite"
android:valueType="floatType"
android:interpolator="@android:anim/overshoot_interpolator"/>
<objectAnimator
android:propertyName="rotation"
android:startOffset="125"
android:duration="900"
android:valueFrom="0"
android:valueTo="360"
android:repeatCount="infinite"
android:valueType="floatType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
</set>
</aapt:attr>
</target>
</animated-vector>

Okey, so many code here but most codes are svg codes of our animated icon. The important part here is the parts that have the objectAnimator tag. Because these determine which features in our icon will be animated and in what way. For example, our icon is constantly revolving around itself. For this, we need to focus on the “rotation” propertyName for objectAnimator. Notice that in the size animations on the icon, the values play between 0.3 and 0.5.

You don’t need to write each objectAnimator tag for this icon yourself. You can use ShapeShifter for this. But you may get incorrect images in some svg files, be careful with this.

After setting our icon, don’t forget to add android:theme=”@style/Theme.YourSplashScreenThemeName” to the AndroidManifest.xml file.

 <application
android:theme="@style/Theme.App.Starting" // HERE
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true"
android:theme="@style/Theme.App.Starting"> // HERE
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

Well, at the end of the animation, the icon leaves the screen by rotating and shrinking. How did we do it?

For that we used setOnExitAnimationListener provided by SplashScreen API.

val splashScreen = installSplashScreen().apply {
setOnExitAnimationListener { viewProvider ->
ObjectAnimator.ofFloat(
viewProvider.view,
"scaleX",
0.5f, 0f
).apply {
interpolator = OvershootInterpolator()
duration = 300
doOnEnd { viewProvider.remove() }
start()
}
ObjectAnimator.ofFloat(
viewProvider.view,
"scaleY",
0.5f, 0f
).apply {
interpolator = OvershootInterpolator()
duration = 300
doOnEnd { viewProvider.remove() }
start()
}
}
}

Note that if you call this method, the SplashScreen state is under your management. So when the animation is finished, we need to call viewProvider.remove(). Otherwise, the icon will not disappear from the screen.

Finally, we can use the logic of SplashScreen to stay on the screen according to the value we store in the viewmodel as follows.

splashScreen.setKeepOnScreenCondition{
splashViewModel.isSplashShow.value
}

As long as this splashViewModel.isSplashShow.value value is true, the SplashScreen will remain on the screen.

I leave the project link below. I hope it helped. May the new year bring you beauty 🎄.

--

--

Samed Harman
Samed Harman

Written by Samed Harman

Flutter/Android Mobile App Developer | Computer Engineer

No responses yet