kotlinでフェードイン、フェードアウトを実装した話

qiitaが「いいね」からLGTM（Look Good To Me）に変わりましたね。
今回はフェードイン、フェードアウトを実装しました。

できることとしては
チュートリアルなんかでよくある、ゆったりふわーと真っ白い画面からチュートリアルにうつって、ゆったりふわーっと真っ白い画面から元いた画面に戻るあれです（伝われ）

Animationってライブラリがあって数秒かけて透過させる的な処理はできるのですが、連打したら透過させる処理を繰り返してしまったりそのままでは使い物にならなかったので。

①fade outを時間を指定してできる（3秒経過後に関数を実行したかったができなかったからその機能を追加した。例えばfade out後にdialogFragmentをdismiss()したいなど）
②fade in, fade out中に他のanimationの処理が割り込まないようにisAnimagingというフラグを用いた（連打時のバグ防止）

ではまずはdialogFragmentを表示するactivityから

```kotlin:MainActivity.kt

class MainActivity : AppCompatActivity(), BaseFullScreenDialogFragment.Callback {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        button.setOnClickListener {
            showFullScreenDialog()
        }
    }

    override fun onClickScreen() {
        comeBack()
    }

    private fun showFullScreenDialog() {
        // 付け足したければ
        val bundle = Bundle()
        val dialog = FullScreenDialogFragment.newInstance(bundle)
        dialog.setOnCallBack(this)
        dialog.show(supportFragmentManager, "dialog")
    }
    private fun comeBack() {
　　　　　// ダイアログを閉じたあとはここを通ります
        val a = 334
    }
}
```

buttonってのはfade inのトリガーになるボタンです。


dialogでフルスクリーンのチュートリアルを表示しています。
setOnCallBackでは画面のタップを受けたらdismiss()するっていう処理を書いています。

で、FullScreenDialogFragmentについてのソースコードを


```kotlin:FullScreenDialogFragment

class FullScreenDialogFragment : BaseFullScreenDialogFragment() {
    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        val dialog = super.onCreateDialog(savedInstanceState)
        dialog.setContentView(R.layout.dialog_full_screen)
        dialog.background_img.setOnClickListener {
            dialog.background_img.fadeOut(3000) {
                super.callback!!.onClickScreen()
                this.dismiss()
            }
        }
        return dialog
    }

    // showの後に呼ばれる
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        dialog.background_img.fadeIn(3000)
        return super.onCreateView(inflater, container, savedInstanceState)
    }


    companion object {
        private var dialogFragment: FullScreenDialogFragment? = null

        fun newInstance(bundle: Bundle): FullScreenDialogFragment {
            if (dialogFragment == null) {
                dialogFragment = FullScreenDialogFragment()
            }
            dialogFragment!!.arguments = bundle
            dialogFragment!!.isCancelable = false
            return dialogFragment!!
        }
    }
}
```
onCreateView()のオーバーライドにてfadeIn()をやっています。
これはdialog().show()のあとに呼ばれるメソッドです。



このdialog fragmentが継承してるクラスはBaseFullScreenDialogFragmentです。↓


```kotlin:BaseFullScreenDialogFragment

abstract class BaseFullScreenDialogFragment : DialogFragment() {

    var callback: Callback? = null
    private val ARG_LISTENER_TYPE = "listenerType"

    // リスナーのタイプを保持するためのenum
    private enum class ListenerType {
        ACTIVITY, FRAGMENT, OTHER
    }

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        val dialog = super.onCreateDialog(savedInstanceState)

        dialog.requestWindowFeature(Window.FEATURE_NO_TITLE)

        return dialog
    }

    override fun onAttach(context: Context) {
        super.onAttach(context)
        val listenerType = arguments!!.getSerializable(ARG_LISTENER_TYPE) as ListenerType?
        if (listenerType == ListenerType.ACTIVITY) {
            this.callback = activity as Callback
        } else if (listenerType == ListenerType.FRAGMENT) {
            callback = targetFragment as Callback?
        }
    }

    override fun onStart() {
        super.onStart()
        // ダイアログを全画面にする
        dialog?.window?.apply {
            setFlags(
                FLAG_FULLSCREEN,
                FLAG_LAYOUT_IN_SCREEN
            )
            setBackgroundDrawable(ColorDrawable(Color.WHITE))
            setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
        }
    }

    override fun show(manager: FragmentManager?, tag: String) {
        // 連打などで二度表示する処理をしてしまった時のクラッシュ防止
        try {
            val fragment = manager!!.findFragmentByTag(tag)
            if (fragment == null) {
                super.show(manager, tag)
            }
        } catch (e: IllegalStateException) {
            manager!!.beginTransaction().add(this, tag).commitAllowingStateLoss()
        }
    }

    interface Callback {
        fun onClickScreen()
    }

    fun setOnCallBack(_cbj: Callback) {
        callback = _cbj
        // リスナーのタイプを保持
        val listenerType: ListenerType?
        if (this.callback == null) {
            listenerType = null
            setTargetFragment(null, 0)
        } else if (this.callback is Activity) {
            listenerType = ListenerType.ACTIVITY
            setTargetFragment(null, 0)
        } else if (this.callback is Fragment) {
            listenerType = ListenerType.FRAGMENT
            setTargetFragment(this.callback as Fragment?, 0)
        } else { //その他の場合
            listenerType = ListenerType.OTHER
            setTargetFragment(null, 0)
        }

        arguments!!.putSerializable(ARG_LISTENER_TYPE, listenerType)
    }
}
```
画面回転するとdialogを呼び元との接続がきれてしまうので呼び出し元を保持する、という方針です。
画面のタップを検知するためにcallbackを設けました。（このタップを検知してfade in, fade outの処理をします）
また、onStart()にて全画面表示にしています。

ちなみにxmlは

```xml:dialog_full_screen.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

    <com.free.myapplication.extension.AnimationImageView
        android:id="@+id/background_img"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@drawable/miri" />

</FrameLayout>

```





とまあこんな感じでやれば実装できます



追記：　画面回転でメンバが消えると指摘があったので直しました（家帰ったら点検します）

追記2:　家で確認したところ、バックグラウンドにいったら落ちました。
serializableをCallbackインターフェースでimplementして無理やりbundleに保持したのですが、それがなにやらまずかったようで、調べたら参考になる記事がありました（方針は全く違う）
https://qiita.com/KazaKago/items/999ac7f7392de4657f30

追記3: 連打したときにfragmentmanagerに同じdialog fragmentが二度addされてクラッシュする事象が発生したので対応しました。