概要
この記事は「FlutterでAndroid向けのSplashScreenを自前で実装する」という内容の記事です.
ライブラリを用いてSplashScreen
を実装する情報は散見されるのですが, Android側のlayout
ファイルを呼び出すSplashScreen
の実装に関する情報は少ないという印象です.
そこで今回はSplashScreen
のライブラリを用いず, 自前で実装を行うことでカスタマイズ性の高いSplashScreenを実装する方法について紹介していきます.
最終的には, 次の動画のような「アニメーション」と「フェードアウト」を組み合わせたSplashScreen
を実装します.
FlutterにおけるSplashScreenの実装の概要
Flutterはアプリ起動時にAndroid/iOSで実装されている画面を表示した後にFlutterで実装されている画面を呼び出します.
そのため, Flutter側でSplashScreen
を実装したとしても次の動画のようにアプリを起動するとデフォルトでネイティブ側に設定されている真っ白な画面が表示されてしまいます.
そこで, FlutterでSplashScreen
を実装するためにはAndroid/iOS, それぞれネイティブで実装を行っていく必要があります.
具体的なSplashScreen
を実装する手段としては, flutter_native_splashといったライブラリを利用するものがあります.
ライブラリを用いると実装が簡単である一方, 中心にロゴを表示させるだけといった風にカスタマイズ性が乏しいという欠点があります.
AndroidにおけるSplashScreenの実装
そこで, Androidのlayout
ファイル画面にてレイアウトを作成し, それをSplashScreen
として設定していきます.
また, サンプルリポジトリは
となります.
build.gradle の設定
最初に, android>app
直下にあるbuild.gradle
にAndroid側で利用するライブラリの設定を行っていきます.
dependencies
にアニメーションのライブラリであるLottieに加えて, constraint layout
も追加します.
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.airbnb.android:lottie:4.0.0' // 追加
implementation "androidx.constraintlayout:constraintlayout:2.1.0" // 追加
}
AndroidManifest.xml の設定
次に, AndroidManifest.xml
の設定を行っていきます.
AndroidManifest.xml
を見ると, デフォルトではres>drawable
にあるlaunch_background.xml
がSplashScreen
に設定されていることがわかります.
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
アプリ起動時に表示される真っ白な画面の実態は, launch_background.xml
であり簡単なSplashScreen
の実装であればこのファイルを編集するだけでも行うことができます.
今回は, constraint layout
を用いたSplashScreen
の実装を行っていきたいので
-
activity
タグのandroid:theme="@style/LaunchTheme"
をandroid:theme="@style/NormalTheme"
に置き換える -
io.flutter.embedding.android.SplashScreenDrawable
という名前のmeta-data
タグを削除
をしていきます. (次に示すAndroidManifest.xml
は一部記述を省略しています)
<manifest>
<application>
<!-- 1. activityタグの
android:theme="@style/LaunchTheme" を
android:theme="@style/NormalTheme" に置き換える -->
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme">
<!-- Note:
次のio.flutter.embedding.android.NormalTheme という名前のmeta-dataタグが無ければ追加 -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<!-- 2. io.flutter.embedding.android.SplashScreenDrawable という名前のmeta-dataタグを削除 -->
<meta-data
android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:resource="@drawable/launch_background"
/>
</activity>
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>
注意点として,
-
io.flutter.embedding.android.NormalTheme
という名前のタグがAndroidManifest.xml
に無い場合がある -
@style/NormalTheme
がres>values>styles.xml
に定義されていないことがある
というものがあります.
@style/NormalTheme
が定義されていなかった場合, res>values>styles.xml
に次のように定義してください.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- 追加 -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Note: 場合に応じて変更を行う -->
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
また, Android側でSplashScreen
を実装したとしても真っ白な画面が挿入されてしまうことがあります.
これは, App startup time | Android Developersによるとアプリの起動時にシステムプロセスが描画する空白の初期画面
が原因であり, 有色のスプラッシュ画面であった場合特に顕著に目立ちます.
そのような場合はNormalTheme
を次のように変更してください.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowDisablePreview">true</item>
</style>
</resources>
レイアウトの作成
次にSplashScreen
に表示するレイアウトを作成していきます.
res
ディレクトリ直下にlayout
ディレクトリを作り, splash_view.xml
という名前で次のようにレイアウトファイルを用意します.
<?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"
android:background="#FFFFFF"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- TODO: 実装 -->
</androidx.constraintlayout.widget.ConstraintLayout>
このconstraint layout
にラップされているsplash_view.xml
を変更することでSplashScreen
のレイアウトを作成することができます.
Lottie の設定
Lottieは, jsonとして書き出されたAdobe After EffectsのアニメーションをAndroid, iOS, Web, Windows上でネイティブにレンダリングすることができるライブラリです.
LottieFilesというサイトで多くのクリエイターがアニメーションを公開してくれています.
今回は, LottieFilesでダウンロードできる[Christopher Ortiz](Christopher Ortiz)さんのイルカのアニメーションをdolphin.json
という名前で保存し利用します.
ダウンロードしたjsonファイルはLottie for Androidの慣例に従い, res
ディレクトリ直下にraw
ディレクトリを作成しraw
ディレクトリに保存します.
そして, splash_view.xml
をLottieAnimationView
を用いて次のように変更します.
<?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"
android:background="#FFFFF"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<com.airbnb.lottie.LottieAnimationView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:lottie_autoPlay="true"
app:lottie_rawRes="@raw/dolphin"
app:lottie_loop="true"
app:lottie_speed="1.00" />
</androidx.constraintlayout.widget.ConstraintLayout>
SplashScreenの実装
最後にSplashScreen
を実装していきます.
まず, SplashScreen
クラスを継承したCustomSplashScreen
を作成します.
package com.example.android_splash_screen
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import io.flutter.embedding.android.SplashScreen
class CustomSplashScreen : SplashScreen {
// 1. splash_view.xmlをInflate
override fun createSplashView(context: Context, savedInstanceState: Bundle?): View? =
LayoutInflater.from(context).inflate(R.layout.splash_view, null, false)
// 2. Flutterのフレームのレンダリングの準備ができたら呼び出される
override fun transitionToFlutter(onTransitionComplete: Runnable) {
// 3. CustomSplashScreen (SplashScreen)の削除
onTransitionComplete.run()
}
}
io.flutter.embedding.android.SplashScreen
を継承したCustomSplashScreen
クラスの役割は,
-
SplashScreen
のViewを生成 - Flutterのフレームのレンダリングの準備ができたら
SplashScreen
を非表示(削除)にする
の2つです.
transitionToFlutter()
はFlutterのフレームのレンダリングの準備ができた直後に呼び出されれるのですが, onTransitionComplete.run()
の呼び出しのタイミングを調整することでフェードアウトなどのアニメーションを付け加えることができます.
最後に, MainActivity
でprovideSplashScreen()
をオーバライドしてSplashScreen
の実装は終わりです.
package com.example.android_splash_screen
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.android.SplashScreen
class MainActivity: FlutterActivity() {
// provideSplashScreen()のオーバーライド
override fun provideSplashScreen(): SplashScreen? = CustomSplashScreen()
}
フェードアウトの実装
おまけとして, SplashScreen
にフェードアウトのアニメーションを付けていきます.
そこで, View
の保持とアニメーションを行うCustomSplashScreenView
クラスを作成します.
package com.example.android_splash_screen
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.content.Context
import android.graphics.Color
import android.view.Gravity
import android.view.ViewPropertyAnimator
import android.view.LayoutInflater
import android.widget.FrameLayout
class CustomSplashScreenView(context: Context) : FrameLayout(context) {
// 1. 変数
private val transitionTimeInMills = 5000L
private var onTransitionComplete: Runnable? = null
private var fadeAnimator: ViewPropertyAnimator? = null
init {
// 2. SplashScreenのViewの生成
val layoutInflater: LayoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
addView(layoutInflater.inflate(R.layout.splash_view, this, false),
LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, Gravity.CENTER))
}
// 3. フェードアウトアニメーションの定義
fun animatePlay(onTransitionComplete: Runnable?) {
this.onTransitionComplete = onTransitionComplete
fadeAnimator = animate()
.alpha(0.0f)
.setDuration(transitionTimeInMills)
.setListener(transitionAnimatorListener)
fadeAnimator?.start()
}
private val transitionAnimatorListener: Animator.AnimatorListener = object : AnimatorListenerAdapter() {
// 4. アニメーション終了時に onTransitionComplete?.run() を呼び出す
override fun onAnimationEnd(animation: Animator) {
animation.removeAllListeners()
if (onTransitionComplete != null) {
// 5. CustomSplashScreen (SplashScreen) の削除
onTransitionComplete?.run()
}
}
}
}
そして, CustomSplashScreen
も以下のように変更します.
package com.example.android_splash_screen
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import io.flutter.embedding.android.SplashScreen
class CustomSplashScreen : SplashScreen {
private var splashView: CustomSplashScreenView? = null
override fun createSplashView(context: Context, savedInstanceState: Bundle?): View? {
if (splashView == null) {
splashView = CustomSplashScreenView(context)
}
return splashView
}
override fun transitionToFlutter(onTransitionComplete: Runnable) {
if (splashView != null) {
// 1. Flutterのフレームのレンダリングの準備ができたらフェードアウト
splashView?.animatePlay(onTransitionComplete)
}
else {
onTransitionComplete.run()
}
}
}
上記のようにonTransitionComplete.run()
を呼び出すタイミングを操作することでフェードアニメーションであったり, SplashScreen
の表示時間の制御であったりを行うことができます.
おわりに
今回, FlutterでAndroid向けのSplashScreen
を実装する方法についてまとめてみました.
ライブラリを用いないことで実装のコストは大きくなるものの, 色んなカスタマイズをしていくことができるのは楽しいなと思います.
iPhone編についても余力があればまとめてみたいなと思います.