Fragment内のCanvasで図形をアニメーションさせる

# はじめに
他の画面には全く使わないけど1画面だけパーティクルとか出したいみたいな需要が発生してそこのためにUnityとかを使ったらいけないかなぁ…と思ったのでこれを下地に作っていけないかなというやつです

テンプレートはEmptyActivityを選択した状態から開発していきます

# Fragment用のレイアウトファイルを作る

Fragmentで使うレイアウトファイルを用意します。下記のようにシンプルなものを使います

```xml:fragment_anim.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/anim_fragment_body"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
</LinearLayout>
```

# Fragmentを作成する

一旦Activityで読み込むようにしたいのでシンプルなFragmentを作成します

```kotlin:AnimFragment.kt
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment

class AnimFragment : Fragment() {
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_anim, container, false)
    }
}
```

# MainActivity用のレイアウトファイルにFragmentを配置

表示確認したいのでFragmentを配置します。
activity_main.xmlを下記のように書き換えます。

```xml:activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:baselineAligned="false">

    <fragment
        android:id="@+id/fragment"
        android:name="io.github.tamfoi.fragmentcanvas.AnimFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1" />
</LinearLayout>
```

# CustomView(Canvas)を作成してFragmentに表示

下記のようにFragmentを修正します。

やった作業

- CustomViewの作成
- CustomViewをFragmentのViewに追加
- CustomViewに四角を表示する

```kotlin:AnimFragment.kt
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import android.os.Bundle
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import androidx.fragment.app.Fragment

class AnimFragment : Fragment() {
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {

        val view = inflater.inflate(R.layout.fragment_anim, container, false)
        val layout = view.findViewById<LinearLayout>(R.id.anim_fragment_body)
        layout.addView(AnimView(this.activity))

        return view
    }

    class AnimView : View {
        constructor(context: Context?) : super(context)
        constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
        constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

        override fun onDraw(canvas: Canvas?) {
            super.onDraw(canvas)

            val paint = Paint()
            paint.color = Color.BLACK
            val rect = Rect(100, 200, 300, 400)
            canvas!!.drawRect(rect, paint)
        
        }
    }
}
```

下記のようにCanvas内に四角が出れば成功です。
![fragmentcancas1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150472/d36f27fe-0e11-674d-9f98-e23c20f7c6ed.png)

# Animationするためのループを作る

下記のように編集してHandlerを使ってループ処理を作ります

```kotlin:AnimFragment.kt
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import android.os.Bundle
import android.os.Handler
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import androidx.fragment.app.Fragment

class AnimFragment : Fragment() {
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {

        val view = inflater.inflate(R.layout.fragment_anim, container, false)
        val layout = view.findViewById<LinearLayout>(R.id.anim_fragment_body)
        val animView = AnimView(this.activity)
        layout.addView(animView)

        val handler = Handler()
        handler.post(object : Runnable{
            override fun run() {
                animView.invalidate();
                handler.postDelayed(this, 16);
            }
        })

        return view
    }

    class AnimView : View {
        constructor(context: Context?) : super(context)
        constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
        constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

        override fun onDraw(canvas: Canvas?) {
            super.onDraw(canvas)

            val paint = Paint()
            paint.color = Color.BLACK
            val rect = Rect(100, 200, 300, 400)
            canvas!!.drawRect(rect, paint)

        }
    }
}
```

# Animationさせてみる

onDrawを下記のように編集してみます

```kotlin:AnimFragment.kt
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import android.os.Bundle
import android.os.Handler
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import androidx.fragment.app.Fragment
import kotlin.math.roundToInt
import kotlin.math.sin

class AnimFragment : Fragment() {
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {

        val view = inflater.inflate(R.layout.fragment_anim, container, false)
        val layout = view.findViewById<LinearLayout>(R.id.anim_fragment_body)
        val animView = AnimView(this.activity)
        layout.addView(animView)

        val handler = Handler()
        handler.post(object : Runnable{
            override fun run() {
                animView.invalidate();
                handler.postDelayed(this, 16);
            }
        })

        return view
    }

    class AnimView : View {

        constructor(context: Context?) : super(context)
        constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
        constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

        override fun onDraw(canvas: Canvas?) {
            super.onDraw(canvas)

            val paint = Paint()
            paint.color = Color.BLACK
            val rect = Rect(400, 600, 700, 900)

            val offsetY = sin(System.currentTimeMillis().toDouble() * 0.005) * 500
            rect.offset(0, offsetY.roundToInt())

            canvas!!.drawRect(rect, paint)

        }
    }
}
```

下記のようになりました(sinの動き楽しいですね)

![fragmentcanvas2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150472/5f3eedbe-9ad6-ed02-9e15-8f06312f0c5a.gif)

# おわりに
実際のところパフォーマンス的にどうなのか分からないのでこうしたほうが良いよみたいなのあったら知りたいです

# 参考
http://muumuutech.hatenablog.com/entry/2017/10/29/163705
https://stackoverflow.com/questions/23280685/drawing-in-a-fragment-in-android/23282354#23282354
http://blog.livedoor.jp/stock_club/archives/52105545.html
https://tech.recruit-mp.co.jp/mobile/remember_canvas1/
https://sites.google.com/a/asabon.net/asabon_site/android_programming/coding/ondraw
https://qiita.com/aftercider/items/81edf35993c2df3de353