AndroidでSurfaceViewを使ってみた

SurfaceViewをKotlinで実装してみました。

#目的
 * SurfaceViewを使ってみる。
 * Kotlinを使ってみる。

#SurfaceViewのポイント
 * SurfaceHolderを介して操作する。
 * メインスレッド以外から描画可能。
 * SurfaceHolder.CallbackのsurfaceCreated()からsurfaceDestroyed()のみ描画可能

#実装
今回は、画面全体をSurfaceViewにしてみます。

```xml:activity_surface_view.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout 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=".SurfaceViewActivity">

    <SurfaceView
        android:id="@+id/surfaceView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</android.support.design.widget.CoordinatorLayout>
```

Kotlinで空のActivityを作成し、レイアウトを読み込みます。

```kotlin:SurfaceViewActivity.kt
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.view.SurfaceView

class SurfaceViewActivity : AppCompatActivity() {

    private var mainSurfaceView : SampleSurfaceView? = null

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

    override fun onResume() {
        super.onResume()

        val surfaceView = findViewById<SurfaceView>(R.id.surfaceView)
        mainSurfaceView = SampleSurfaceView(surfaceView)
    }
}
```

SurfaceHolder用のクラスを作成し、SurfaceViewに描画する。
今回は、ランナーが右から左に通過するサンプルを作ってみました。

```kotlin:SampleSurfaceHolder.kt
import android.view.SurfaceHolder
import android.view.SurfaceView
import android.graphics.*

class SampleSurfaceHolder : SurfaceHolder.Callback, Runnable {
    private val _migrationLength : Int = 50
    private val _holder: SurfaceHolder
    private val _surface: SurfaceView

    private var _thread: Thread? = null
    private var _isCancel = false

    constructor(surface: SurfaceView) {
        _holder = surface.holder
        _holder.addCallback(this)
        _surface = surface
    }

    override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
    }

    override fun surfaceDestroyed(holder: SurfaceHolder?) {
        _isCancel = true
        _thread = null
    }

    override fun surfaceCreated(holder: SurfaceHolder?) {
        _thread = Thread(this)
        _thread?.start()
    }

    override fun run() {
        var cnt = 0
        val runner = Runner()

        while (!_isCancel) {
            val canvas = _holder.lockCanvas()

            val bitmap = runner.createBitmap()
            var x = - bitmap.width
            if (_surface.width < (x + cnt * _migrationLength )) {
                cnt = 0
            } else {
                x += cnt * _migrationLength
            }

            val paint = Paint()
            canvas.drawColor(0, PorterDuff.Mode.CLEAR)
            canvas.drawBitmap(bitmap, x.toFloat(), (_surface.height - bitmap.height).toFloat(), paint)

            _holder.unlockCanvasAndPost(canvas)
            Thread.sleep(150L)
            cnt++
        }
    }

    private class Runner {
        private val _magnification : Float = 10F
        private val _color = arrayOf(
                0xff000000, // 0
                0xff777777  // 1
        )

        private val _position: Array<IntArray> = arrayOf(intArrayOf(
                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0,
                0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
                0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
                0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
                0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0,
                0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0,
                0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
                0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
                0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
                0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
                0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
                0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
                0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
                0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
                0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
                0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
                0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
                0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
                0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
                0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0
            ),
            intArrayOf(
                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0,
                0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
                0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
                0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
                0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
                0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0,
                0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0,
                0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
                0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
                0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
                0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
                0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
                0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
                0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
                0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
                0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
                0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
                0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0,
                0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
            )
        )
        private var _index = 0

        fun createBitmap(): Bitmap {
            val pixels = Array(16 * 22) { Color.BLACK }
            for (y in 0..(22 - 1)) {
                for (x in 0..(16 - 1)) {
                    pixels[x + y * 16] = _color[_position[_index % 2][x + y * 16]].toInt()
                }
            }
            _index = if (++_index >= _position.size) 0 else _index

            val bitmap = Bitmap.createBitmap(16, 22, Bitmap.Config.ARGB_8888)
            bitmap.setPixels(pixels.toIntArray(), 0, bitmap.width, 0, 0, bitmap.width, bitmap.height)
            val matrix = Matrix()
            matrix.setScale(_magnification, _magnification)
            return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix,true)
        }
    }
}
```