今回やること
surfaceViewを使って簡単お絵描きアプリを作る。
リセットボタンと色を変えられる機能をつける。
こんな感じ ↓
作るファイルは3個(MainActivity.kt, CustomSurfaceView.kt, activity_main.xml)だけ
※以下少し長くなります。
レイアウトファイルにsurfaceViewをのせる
android studio のパレットからsurfaceView(widgetsとかにある)をドラッグする。
色変更ボタンとリセットボタンを配置する
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/blackBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="くろ"
app:layout_constraintEnd_toStartOf="@+id/redBtn"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/redBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="あか"
app:layout_constraintEnd_toStartOf="@+id/greenBtn"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/blackBtn"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/greenBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="みどり"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/redBtn"
app:layout_constraintTop_toTopOf="parent" />
<SurfaceView
android:id="@+id/surfaceView"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/resetBtn"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/redBtn" />
<Button
android:id="@+id/resetBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="リセット"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

CustomSurfaceViewを作る
コンストラクタ(初期値など)
とりあえずコピペでok
class CustomSurfaceView: SurfaceView, SurfaceHolder.Callback{
private var surfaceHolder: SurfaceHolder? = null
private var paint: Paint? = null
private var path: Path? = null
var color: Int? = null
var prevBitmap: Bitmap? = null
private var prevCanvas: Canvas? = null
private var canvas: Canvas? = null
var width: Int? = null
var height: Int? = null
constructor(context: Context, surfaceView: SurfaceView) : super(context) {
surfaceHolder = surfaceView.holder
/// display の情報(高さ 横)を取得
val size = Point().also {
(context.getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay.apply {
getSize(
it
)
}
}
/// surfaceViewのサイズ
width = size.x
height = size.y
/// 背景を透過させ、一番上に表示
surfaceHolder!!.setFormat(PixelFormat.TRANSPARENT)
surfaceView.setZOrderOnTop(true)
/// コールバック
surfaceHolder!!.addCallback(this)
/// ペイント関連の設定
paint = Paint()
color = Color.BLACK
paint!!.color = color as Int
paint!!.style = Paint.Style.STROKE
paint!!.strokeCap = Paint.Cap.ROUND
paint!!.isAntiAlias = true
paint!!.strokeWidth = 15F
}
}
surfaceView
とSurfaceHolder.Callback
を継承する。
widthとheightに関しては本当はViewTreeObserver
とか使ってsurfaceViewのサイズに合わせた方がいいが今回はこっちでも問題ないので簡単なスクリーンサイズを使用。
setZOrderTop(true)
とsetFormat
がないと線とか何も表示されないので注意。(自分はこれに気付かず2日くらいハマった)
データクラス
描画する際のpathと色を保存するデータクラスを作る。
新しくファイル作ってもCustomSurfaceViewに書いてもok
//// pathクラスの情報とそのpathの色情報を保存する
data class pathInfo(
var path: Path,
var color: Int
)
Implement
Implementする。
canvasとbitmapを初期化するメソッドを作る
/// surfaceViewが作られたとき
override fun surfaceCreated(holder: SurfaceHolder?) {
/// bitmap,canvas初期化
initializeBitmap()
}
override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
}
override fun surfaceDestroyed(holder: SurfaceHolder?) {
/// bitmapをリサイクル
prevBitmap!!.recycle()
}
/// bitmapとcanvasの初期化
private fun initializeBitmap() {
if (prevBitmap == null) {
prevBitmap = Bitmap.createBitmap(width!!, height!!, Bitmap.Config.ARGB_8888)
}
if (prevCanvas == null) {
prevCanvas = Canvas(prevBitmap!!)
}
prevCanvas!!.drawColor(0, PorterDuff.Mode.CLEAR)
}
今回BitmapはsurfaceViewがdestroyされたときにリサイクルする。bitmapはそのままにしておくとメモリーリークが発生する危険があるので使わなくなったらリサイクルしておく。
描画メソッド
onTouchリスナーで感知して線を書くメソッドをつくる
private fun draw(pathInfo: pathInfo) {
canvas = Canvas()
/// ロックしてキャンバスを取得
canvas = surfaceHolder!!.lockCanvas()
//// キャンバスのクリア
canvas!!.drawColor(0, PorterDuff.Mode.CLEAR)
/// 前回のビットマップをキャンバスに描画
canvas!!.drawBitmap(prevBitmap!!, 0F, 0F, null)
//// pathを描画
paint!!.color = pathInfo.color
canvas!!.drawPath(pathInfo.path, paint!!)
/// ロックを解除
surfaceHolder!!.unlockCanvasAndPost(canvas)
}
/// 画面をタッチしたときにアクションごとに関数を呼び出す
fun onTouch(event: MotionEvent) : Boolean{
when (event.action) {
MotionEvent.ACTION_DOWN -> touchDown(event.x, event.y)
MotionEvent.ACTION_MOVE -> touchMove(event.x, event.y)
MotionEvent.ACTION_UP -> touchUp(event.x, event.y)
}
return true
}
///// path クラスで描画するポイントを保持
/// ACTION_DOWN 時の処理
private fun touchDown(x: Float, y: Float) {
path = Path()
path!!.moveTo(x, y)
}
/// ACTION_MOVE 時の処理
private fun touchMove(x: Float, y: Float) {
/// pathクラスとdrawメソッドで線を書く
path!!.lineTo(x, y)
draw(pathInfo(path!!, color!!))
}
/// ACTION_UP 時の処理
private fun touchUp(x: Float, y: Float) {
/// pathクラスとdrawメソッドで線を書く
path!!.lineTo(x, y)
draw(pathInfo(path!!, color!!))
/// 前回のキャンバスを描画
prevCanvas!!.drawPath(path!!, paint!!)
}
surfaceViewをセット
上記で作ったCustomSurfaceViewをレイアウトのsurfaceViewにセットする
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
/// CustomSurfaceViewのインスタンスを生成しonTouchリスナーをセット
val customSurfaceView = CustomSurfaceView(this, surfaceView)
surfaceView.setOnTouchListener { v, event ->
customSurfaceView.onTouch(event)
}
}
とりあえずここまで来たら黒の線で絵を描けるようになってるはず。
最後にカラーチェンジとリセットを実装する
色とリセットを追加
CustomSurfaceViewにメソッドを書き加える。
/// resetメソッド
fun reset() {
///初期化とキャンバスクリア
initializeBitmap()
canvas = surfaceHolder!!.lockCanvas()
canvas?.drawColor(0, PorterDuff.Mode.CLEAR)
surfaceHolder!!.unlockCanvasAndPost(canvas)
}
/// color チェンジメソッド
fun changeColor(colorSelected: String) {
when (colorSelected) {
"black" -> color = Color.BLACK
"red" -> color = Color.RED
"green" -> color = Color.GREEN
}
paint!!.color = color as Int
}
最後にMainActivityにリスナーをセットして完成
/// カラーチェンジボタンにリスナーをセット
/// CustomSurfaceViewのchangeColorメソッドを呼び出す
blackBtn.setOnClickListener {
customSurfaceView.changeColor("black")
}
redBtn.setOnClickListener {
customSurfaceView.changeColor("red")
}
greenBtn.setOnClickListener {
customSurfaceView.changeColor("green")
}
/// リセットボタン
resetBtn.setOnClickListener {
customSurfaceView.reset()
}
完成!!
おわり
少し長くなったが最後までついてきていただいた方はお絵描きアプリができてるはず。
最初はsurfaceViewの使い方がよくわからず戸惑ったが使ってみるとおもしろいことできそうだなって感じた。
自分のスマホで動くのは楽しくていいね! Android最高。