【Android爬坑周记】用SplashScreen做一个会动的开屏!

7,213 阅读4分钟

Android 12以上加入了SplashScreen,并且支持开屏动画了!因此我在【小鹅事务所】项目中加入了一个开屏动画,如下(为方便动图展示,我故意延长了几秒钟):

开屏.gif

SplashScreen

简单介绍一下SplashScreen,仅在冷启动或者温启动的时候会展示SplashScreen,支持AVD动画、帧动画。我就先使用帧动画实现这个开屏动画,后面会考虑换成AVD动画。关于SplashScreen具体就不细讲啦,我讲这些讲不明白,没有官方文档讲得好,直接进入实战!!

注意裁切

image_bbWXUd5R2b.png

ICON在设计的时候只能够占用三分之二大小的圆,超出这部分的会被裁切掉,所以这点需要注意!

设计

首先打开UI设计软件,我此处用Figma,新建一个方形的框框,方形的框框里面整一个三分二大小的圆圈,像这样。

image_zN3wn3Qj0_.png

然后呢,就把设计好的Icon放进去

image_nPXCuEjgOv.png

这个时候一张静态图就做好啦,但是帧动画需要让图片动起来的话,就需要多张静态图。怎么设计它动起来呢?我的思路是让它扭头!像这样。

image_bKPGvPkiso.png

然后再把框框的颜色隐藏掉,我们只需要透明背景的Icon

image_9vcZawLYyF.png

注意,为了展示外边需要留空间,我给它们的框框加上描边,实际不需要!这个时候就可以导出图片啦,我这边选择导出矢量图,也就是SVG格式。

image_tg-XgUBqeW.png

导入动画

打开Android Studio,右键点击res → new → Vector Asset,再导入图片,将静态图都导进去就可以做动画啦。

image_7DgCf5ExTe.png

新建anim_little_goose.xml,根标签是animation-list,并在里面放4个item。

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="false">
    <item
        android:drawable="@drawable/ic_little_goose"
        android:duration="50" />
    <item
        android:drawable="@drawable/ic_little_goose_back"
        android:duration="150" />
    <item
        android:drawable="@drawable/ic_little_goose"
        android:duration="50" />
    <item
        android:drawable="@drawable/ic_little_goose_fore"
        android:duration="150" />
</animation-list>

根据命名可以看出

  • 第一帧为正常的小鹅,展示50毫秒

  • 第二帧为向后扭头的小鹅,展示150毫秒

  • 第三帧为正常的小鹅,展示50毫秒

  • 第四帧为向前扭头的小鹅,展示150毫秒

一次循环就是400毫秒,点开Split界面,就能在右边预览动画了,这个时候,动画就简简单单做好了。

As.gif

SplashScreen

引入依赖

由于SplashScreen是Android12以上才有的,而Android12以下需要适配,但是!Jetpack提供了同名适配库,去gradle引用就好了。

//SplashScreen
implementation 'androidx.core:core-splashscreen:1.0.0'

设置开屏主题

然后在res/values/themes中新建一个style标签,并将其父标签设为Theme.SplashScreen,需要注意的是,如果适配了黑夜模式的话,也可以在values-night/themes文件下单独配置。

<style name="Theme.AppSplashScreen" parent="Theme.SplashScreen">
    <item name="windowSplashScreenBackground">@color/primary_color</item>
    <item name="windowSplashScreenAnimatedIcon">@drawable/anim_little_goose</item>
    <item name="windowSplashScreenAnimationDuration">3000</item>
    <item name="postSplashScreenTheme">@style/Theme.Account</item>
</style>

我这边配置一个无ICON背景的动画,因此不用windowSplashScreenIconBackgroundColor标签设置ICON背景。

简单介绍一下我设置的4个标签

  • windowSplashScreenBackground设置整个开屏动画的背景颜色。

  • windowSplashScreenAnimatedIcon设置的是开屏动画播放的动画文件,也就是上面写的动画文件。

  • windowSplashScreenAnimationDuration设置的是动画的播放时长,也就是说小鹅抖三秒钟头就会停止播放。

  • postSplashScreenTheme这个设置的是开屏动画播放完需要回到的主题,此处设置了我的主题。

    <style name="Theme.Account" parent="Theme.MaterialComponents.DayNight.NoActionBar">
    ...
    </style>
    

在Manifest注册

    <application
        android:label="@string/app_name"
        ...
        android:theme="@style/Theme.Account">
        
        ...
        <activity
            android:name=".ui.MainActivity"
            android:theme="@style/Theme.AppSplashScreen"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        
    </application>

可以看到在打开应用打开的第一个Activity,即MainActivity中设置了开屏主题,而在Application中设置了自己的主题。在Application设置主题的话,这个Application中的除了特殊设置Theme的Activity,其它都默认使用Application主题。

去MainActivity吧!

class MainActivity : BaseActivity() {

    private val binding by viewBinding(ActivityMainBinding::inflate)

    override fun onCreate(savedInstanceState: Bundle?) {
        val splashScreen = installSplashScreen()
        splashScreen.setKeepOnScreenCondition { !isAppInit }
        super.onCreate(savedInstanceState)
        initView()
    }   
    ...

}

重写onCreate函数,并在调用super.onCreate之前加载SplashScreen,即调用installSplashScreen,获得一个splashScreen实例,理论上来说调用installSplashScreen函数已经可以实现开屏动画了,可是我想等到一部分数据加载完再进入APP怎么办?

可以看到我调用了setKeepOnScreenCondition 函数,传入一个接口,这个接口返回一个Boolean值,如果返回true则继续展示开屏,如果返回false则进入APP。而此函数在每次绘制之前都会调用,是主线程调用的,因此不能在这里处理太多东西阻塞主线程!

我这边就设置了一个顶层变量,每次都去看看这个顶层变量的值,不会阻塞主线程。

class AccountApplication : Application() {

    val supervisorScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
    
    //初始化数据 防止第一次打开还要加载
    private fun initData() {
        supervisorScope.launch {
            val initIconDataDeferred = async { TransactionIconHelper.initIconData() }
            val initTransactionDeferred = async { TransactionHelper.initTransaction() }
            val initScheduleDeferred = async { ScheduleHelper.initSchedule() }
            val initNoteDeferred = async { NoteHelper.initNote() }
            val initMemorialsDeferred = async { MemorialHelper.initMemorials() }
            val initTopMemorialDeferred = async { MemorialHelper.initTopMemorial() }
            val initDataStoreDeferred = async { DataStoreHelper.INSTANCE.initDataStore() }
            initIconDataDeferred.await()
            initTransactionDeferred.await()
            initScheduleDeferred.await()
            initNoteDeferred.await()
            initMemorialsDeferred.await()
            initTopMemorialDeferred.await()
            initDataStoreDeferred.await()
            isAppInit = true
        }
    }
}

var isAppInit = false

我在Application中对所有需要初始化的东西先初始化一遍,初始化完之后再将isAppInit设置为true,此时在闪屏那边获取的为false,也就是说就会进入APP了。

到这里就结束了,去运行一下吧!

开屏.gif

总结

说实话,在我看来,SplashScreen其实用处不大,因为我们的闪屏一般是用来放advertisement的,而不是放有趣的动画的!

参考

SplashScreen: developer.android.google.cn/develop/ui/…