LoginSignup
1
1

More than 1 year has passed since last update.

FlutterでAndroid向けのSplashScreenを自前で実装する

Last updated at Posted at 2021-08-07

概要

この記事は「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も追加します.

build.gradle
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.xmlSplashScreenに設定されていることがわかります.

launch_background.xml
<?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の実装を行っていきたいので

  1. activityタグのandroid:theme="@style/LaunchTheme"android:theme="@style/NormalTheme"に置き換える
  2. io.flutter.embedding.android.SplashScreenDrawable という名前のmeta-dataタグを削除

をしていきます. (次に示すAndroidManifest.xmlは一部記述を省略しています)

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/NormalThemeres>values>styles.xmlに定義されていないことがある

というものがあります.

@style/NormalThemeが定義されていなかった場合, res>values>styles.xmlに次のように定義してください.

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を次のように変更してください.

styles.xml
<?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という名前で次のようにレイアウトファイルを用意します.

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さんのイルカのアニメーションdolphin.jsonという名前で保存し利用します.

ダウンロードしたjsonファイルはLottie for Androidの慣例に従い, resディレクトリ直下にrawディレクトリを作成しrawディレクトリに保存します.

そして, splash_view.xmlLottieAnimationViewを用いて次のように変更します.

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="#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を作成します.

CustomSplashScreen.kt
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()の呼び出しのタイミングを調整することでフェードアウトなどのアニメーションを付け加えることができます.

最後に, MainActivityprovideSplashScreen()をオーバライドしてSplashScreenの実装は終わりです.

MainActivity.kt
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クラスを作成します.

CustomSplashScreenView.kt
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も以下のように変更します.

CustomSplashScreen.kt
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編についても余力があればまとめてみたいなと思います.

参考

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1