Jetpack Compose实现首页splash动画

本文讨论下如何在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
        }
    }
}


最终效果:


demo.gif






参考:

How to Build an Animated Splash Screen on Android - The Full Guide

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容