本文讨论下如何在Jetpack Compose中实现一个首页的splash动画效果,涉及的知识点包括:1 google提供的androidx.core:core-splashscreen;2 VectorDrawable;3 ObjectAnimator属性动画。其中第二点VectorDrawable相关知识点可以参考这2篇文章:Android矢量图(一)--VectorDrawable基础、Android矢量图(二)--VectorDrawable所有属性全解析。
1 google splash sdk
app/build.gradle.kts添加依赖:
implementation("androidx.core:core-splashscreen:1.0.1")
。
然后在res/values/themes.xml里写2个style,splash动画主要是通过style来实现:
// res/values/themes.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.Starkdemo" parent="android:Theme.Material.Light.NoActionBar" />
<style name="Theme.App.Starting" parent="Theme.SplashScreen">
<item name="windowSplashScreenBackground">@color/black</item>
<item name="postSplashScreenTheme">@style/Theme.Starkdemo</item>
<item name="android:windowSplashScreenAnimatedIcon">@drawable/animated_logo</item>
</style>
</resources>
可以看到,themes.xml里定义两个style,第一个style是splash动画结束后的采用的默认的style,第二个style是用来展示splash动画。第二个style里我们定义了三个属性,第一个属性是动画的背景颜色,这里是黑色,第二个属性是动画结束后的采用的style,第三个属性表示具体的动画,动画值是res/drawable/animated_logo.xml,这里xml在下面的第二小节里具体介绍。
最后AndroidManifest.xml里的application和activity标签下的android:theme属性都设置成这个Theme.App.Starting。
2 编辑 res/drawable/animated_logo.xml
animated_logo.xml里需要指定两个东西,第一个是动画的目标,这里是VectorDrawabele,通过android:drawable属性指定;第二个是动画类型,这里是属性动画,通过<target/>元素指定,源码如下:
// res/drawable/animated_logo.xml
<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/baseline_diamond_24">
<target
android:animation="@animator/logo_animator"
android:name="logoGroup"/>
</animated-vector>
baseline_diamond_24.xml和logo_animator.xml源码如下:
// res/drawable/baseline_diamond_24.xml
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="50dp"
android:height="50dp"
android:viewportWidth="24"
android:viewportHeight="24">
<group
android:name="logoGroup"
android:pivotX="12"
android:pivotY="12"
>
<path
android:fillColor="@android:color/holo_green_dark"
android:pathData="M12.16,3l-0.32,0l-2.63,5.25l5.58,0z"
android:strokeColor="@android:color/holo_red_dark" />
<path
android:fillColor="@android:color/holo_green_dark"
android:pathData="M16.46,8.25l5.16,0l-2.62,-5.25l-5.16,0z"
android:strokeColor="@android:color/holo_red_dark" />
<path
android:fillColor="@android:color/holo_green_dark"
android:pathData="M21.38,9.75l-8.63,0l0,10.35z"
android:strokeColor="@android:color/holo_red_dark" />
<path
android:fillColor="@android:color/holo_green_dark"
android:pathData="M11.25,20.1l0,-10.35l-8.63,0z"
android:strokeColor="@android:color/holo_red_dark" />
<path
android:fillColor="@android:color/holo_green_dark"
android:pathData="M7.54,8.25l2.62,-5.25l-5.16,0l-2.62,5.25z"
android:strokeColor="@android:color/holo_red_dark" />
</group>
</vector>
// res/animator/logo_animator.xml
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:interpolator="@anim/overshoot_interpolator">
<propertyValuesHolder
android:propertyName="rotation"
android:valueFrom="0.0"
android:valueTo="360.0"
android:valueType="floatType" />
<propertyValuesHolder
android:propertyName="scaleX"
android:valueFrom="0.0"
android:valueTo="0.4"
android:valueType="floatType" />
<propertyValuesHolder
android:propertyName="scaleY"
android:valueFrom="0.0"
android:valueTo="0.4"
android:valueType="floatType" />
</objectAnimator>
// res/anim/overshoot_interpolator.xml
<?xml version="1.0" encoding="utf-8"?>
<overshootInterpolator xmlns:android="http://schemas.android.com/apk/res/android">
</overshootInterpolator>
VectorDrawable这里就不介绍了,可以参考之前提到的两篇文章。这里对res/animator/logo_animator.xml坐下简单说明,对三个属性rotation、scaleX、scaleY做了持续1000ms的动画,起始属性值和终止属性值分别通过android:valueFrom和android:valueTo指定。
3 installSplashScreen
最后,还需要写点代码,在MainActivity的onCreate里,需要调用installSplashScreen(),而且时机需要在setContent之前。installSplashScreen()函数返回的是一个SplashScreen对象,这个对象提供了setKeepOnScreenCondition和setOnExitAnimationListener等接口。
class MainActivity : ComponentActivity() {
private val TAG = "MainActivity"
private val mLoginViewModel by viewModels<LoginViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
installSplashScreen().apply {
setKeepOnScreenCondition {
!mLoginViewModel.login.value
}
setOnExitAnimationListener { screen ->
val zoomX = ObjectAnimator.ofFloat(screen.iconView, View.SCALE_X, 0.4f, 0.0f).apply {
interpolator = OvershootInterpolator()
duration = 500
doOnEnd { screen.remove(); StarkLog.w(TAG, "testsplash exit anim end x") }
}
val zoomY = ObjectAnimator.ofFloat(screen.iconView, View.SCALE_Y, 0.4f, 0.0f).apply {
interpolator = OvershootInterpolator()
duration = 500
doOnEnd { screen.remove(); StarkLog.w(TAG, "testsplash exit anim end y") }
}
zoomX.start()
zoomY.start()
}
}
setContent {
StarkdemoTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Greeting("Android")
}
}
}
}
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Text(
text = "Hello $name!",
modifier = modifier
)
}
}
// LoginViewModel.kt
package com.zzh.eden.stark.demo
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
class LoginViewModel() : ViewModel() {
private val _login: MutableStateFlow<Boolean> = MutableStateFlow(false)
val login: StateFlow<Boolean> = _login.asStateFlow()
fun onLoginChanged(l: Boolean) {
_login.update { l }
}
init {
// test
viewModelScope.launch {
delay(3000)
onLoginChanged(true)
// _login.value = true
}
}
}
最终效果:
参考:
How to Build an Animated Splash Screen on Android - The Full Guide