※javaのお絵描きアプリ解説はこちら


kotlinで、簡単なペイントアプリを作ってみたいと思います。

以前、第6回目のところでタッチイベントを紹介しましたが今回も同じような仕組みを使っていくので、予めこちらの動画をご覧いただくことをオススメします。

↓↓↓

【はじめてのKotlinプログラミング(6)】座標の取得(タッチするとボールがついてくる!?)

●タッチしたところから描画が開始され、
●動かしていると、線がひかれる、
●離すと、何も作業はされません、ということで

結果的にお絵描きができる、というものになります。

動画

 

コード

▼activity_main.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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">


    <com.example.paintktapp.MyView
        android:id="@+id/myView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:layout_editor_absoluteX="132dp"
        tools:layout_editor_absoluteY="155dp" />

    <Button
        android:id="@+id/btnClear"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="clear"
        app:layout_constraintBottom_toBottomOf="@+id/myView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

 

▼MainActivity.kt

package com.example.paintktapp

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button

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

        //5)Viewの取得⇒クリア処理
        val myView :MyView =findViewById(R.id.myView)
        val btnClear:Button =findViewById(R.id.btnClear)

        btnClear.setOnClickListener {
            myView.clearCanvas()
        }
    }
}

 

▼MyView.kt

package com.example.paintktapp

import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Path
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View

//1)Viewを継承したクラス
class MyView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {

    private var path : Path =Path()//線を引く、図形を描く、グラフィック描画
    private var paint : Paint =Paint()//色とか太さ
    private var drawX:Float= 0F
    private var drawY:Float= 0F

    //2)onDraw(描画の準備)
    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        paint.color =Color.BLUE//色
        paint.style=Paint.Style.STROKE//描画のスタイルを線にする
        paint.strokeWidth = 20F//幅
        canvas?.drawPath(path,paint)
    }

    //3)実際の描画(押した時、動かした時)
    override fun onTouchEvent(event: MotionEvent?): Boolean {
        //タッチポジション(x座標、y座標)
        drawX = event!!.x
        drawY = event.y

        when(event.action){
            MotionEvent.ACTION_DOWN -> path.moveTo(drawX,drawY)
            MotionEvent.ACTION_MOVE -> path.lineTo(drawX,drawY)
        }
        //再描画を実行させる呪文
        invalidate()

        //return super.onTouchEvent(event)
        return true
    }

    //4)クリア処理(の関数を作る)
    fun clearCanvas(){
        path.reset()
        invalidate()
    }
}

 

テキスト

今回の仕組みについてざっくり解説します。
こちらがちょっと前、第6回でやった「タッチしたところにボールが動く」というやつの画面です。

今回はその応用です。
第6回の動画は概要欄に貼っておきますので、まだの方はそちらをご覧ください。

前回は、main.ktの中に、もう1つ描画用のクラスを作って
それを表示させる、というものでした。

onDrawっていう描画メソッドを使って
そこにcanvasを表示させる。
canvasの画面をタッチした座標を取得して、そこにボールを配置する、というものでした。

 

onDrawがViewグループに属しているので
Viewを継承した子供のクラスを作った、っていうことでした。

 

今回は、この応用です。今回は、これ自体を独立させます。

つまり、もう1個別にkotlinクラスを作って、

それをxmlに重ねます。↓↓↓

 

そうすることで、xmlにカンバスが表示されますし、
クリアボタンを追加して、クリア処理が実行できる、という風なことができるようになります。

まあ、こまかい理屈はともかく、こんな感じでふんわりとイメージができたところで
さっそく始めていきましょう。

AndroidStudioを起動します。名前は適当にPaintKtAppとかにしておきます。

 

1.サブクラス

MainActivity.ktと同列にしたいので、その1つ上、パッケージを右クリック
⇒new ⇒kotlin class
名前を適当にMyViewとかにしておきます。

で、今回はViewクラスのメソッドonDrawを使いたいので
自分の名前の後に、親の名前、つまり
: View
と、このように書いてあげれば、
Viewという親を継承した子供ですよ、という親子関係ができました。
で、viewはインポートが必要です。

で、何やらワーニングが出ているのでクリックしてください。

そうすると、コンストラクターってのを用意してください、と言っています。
4つほど用意してありまして
普通は上から順に選んでいけばいいんですが
結論からいうと、今回は2番目でいきます。

View(context , attributset)ってやつですね。
一番上は、前回のように
このクラスだけで完結する場合には大丈夫なんですが
今回のように、このクラスをxmlに重ねるという
ちょっと複雑になってくると一番上のやつはしょぼくて使えません。

まあ、下に行けばいくほど色々文字が増えて豪華になるんですが
でもまあ、今回は2番目でいきたいと思います。

はい、そうすると、こんな感じで画面が出てきたかと思います。

それではViewを継承した子クラスが出来たので、
onDrawで描画の準備をしていきましょう。

 

2.onDraw

それでは
onDrawメソッドでカンバスをしいて、描画ができるように準備していきましょう。

onDって入れるとonDrawが出てきますので選択。
そうすると、決まり文句がドーンと出てきます。

で、描画をするにはパスとペイントを用意しなければいけないので
定義してあげましょう。

private var path: Path =Path() //線を引く、図形を描く、グラフィック描画
private var paint :Paint = Paint() //色とか太さ

privateっていうのは、このクラスの中だけで使う場合に書く、アクセス修飾子です。
主にprivateとパブリックの2種類があるんですが
何も書かない場合は、パブリック扱いになり、どこからでもアクセスができるということになります。

アクセスをコントロールすることで安全なプログラムが出来るようになるんですが
あとでもう1回出てくるので、その時に改めて説明します。

それではパスとペイントが用意できたので色とか幅とかを
ここに設定してあげます。

▼まずは色を青。
paint.color = Color.BLUE //色

▼描画のスタイルを線にする場合には、スタイルをストロークにしてあげます
paint.style = Paint.Style.STROKE //描画のスタイルを線にする

▼最後に幅も適当に決めておいてあげましょう
paint.strokeWidth = 20F //幅
とりあえず今回は20F。Fっていうのはfloatですね。
浮動小数点が使える数値のやつです。
描画の場合、だいたい型はfloatが使われるので覚えておいてください。

▼それでは、カンバスにdrawPathとしてあげて、path,paintを入れてあげれば準備完了です
canvas?.drawPath(path,paint)

それでは次に、実際の描画、
タッチイベントで押した時、動かした時、という
条件分岐をしてあげましょう。

 

3.実際の描画(押した時、動かした時)

それでは、実際のタッチイベントで、
画面を押した時はこう、
押したまま動かしているときはこう、という条件分岐をしてあげましょう。

onTと入れたら、オンタッチイベントが出てきますので選択。

で、まずはタッチしたときのx座標とy座標を取得してあげます。

//タッチポジション(x座標、y座標)
drawX = event!!.x
drawY = event.y

で、drawX 、drawYを今定義してなかったので定義してあげます。
private var drawX :Float = 0F
private var drawY :Float = 0F

で、今までは、普通にvalとかvarって書いていました。
何も書かない場合は
publicっていうのが省略されているんですが、
どこからでもアクセスできるという意味になります。
ただ、今回のように、このクラスの中だけで使う場合には
private と書いてあげるようにしてください。

こういうのを「アクセス修飾子」というんですが
アクセスが出来る、出来ないをコントロールすることで
安全なプログラムを書くことができます。

で、今回の変数も、このクラスでしか使わないので
AndroidStudioさんが「privateと書いた方がええですよ」とアドバイスしてくれています。
実際、おっしゃる通りなのでprivateと書いておきましょう

Floatっていうのは、浮動小数点を扱う時の型で、
とりあえず今は0を代入しています。
最初は0ですが、ここでタッチした座標を再代入しているので
valではなくvarを使っています。

floatの場合は、数字の後にFを付ける決まりなので忘れないようにしてください。
ちなみに、右側から型が自明の場合は、こっちの方は省略することも可能です。
慣れてきた方は省略してもOKです。

ということで、ここで初期値を宣言して
ここでタッチしたポジションを取得できましたので
whenを使って条件分岐をしてあげましょう。

で、javaの時は、押した時と、動かした時と、離したとき、
という3パターンで条件分岐をしたのですが
kotlinの場合は、押した時と、動かしている時の2種類でOKです。
つまり「離した時」というのは何もしないので、書かなくてもよい、ということで
だいぶシンプルになります。

when(event.action){
MotionEvent.ACTION_DOWN -> path.moveTo(drawX,drawY)
MotionEvent.ACTION_MOVE -> path.lineTo(drawX,drawY)
}

書き始めなさいよというのは「moveTo」
線を引きなさいというのは「lineTo」を使います。

これだけです。
javaに比べるとだいぶシンプルになっているのがわかります。

で、解説は後にして、
ここに
invalidate()
と書いてください。
無効化、という意味なんですが、再描画を実行させるには、この呪文を唱えます。

で、returnうんぬんかんぬんを消して
return true
に書き換えます。

こちらにreturn trueを書いたということは
こちらはreturn falseになります。

falseということは、この処理がまだ終わってませんという意味で、
次の処理に行くんですが、次の処理はないので、上手く動作してくれません。

ここで終わりましたよ、という意味でtrueを返してあげます
で、押し続けていると、またこの処理が実行されて、座標が更新、
でまた終わったらtrueが返されて、
押し続けている間、この中をぐるぐる処理が繰り返されるので
座標が更新し続けられるという風になっています。

ここはちょっと最後に改めて解説しますので
ひとまずこのまま書いておいてください。

それでは最後に、クリアボタンを押したら、画面がクリアされる処理を作って完成です。

 

4.クリア処理(の関数を作る)

さて、それでは、最後にクリアボタンを押した時に動作するような
関数を作ってあげましょう。

関数を作るには
fun、これfunctionの略なのですが文字通り関数って意味ですね。
で、名前を付けて()、{}、とすればOKです。

こっちの方のoverrideってのは親クラスのメソッドを引き継いでます、って意味なんです。
つまり親クラスに同じようなものが既に用意があるので
出だしを書いただけで、残りの分がドーンと入力されていた、っていうことです。

で、今回は、独自の関数、メソッドなので
overrideじゃありませんよと、こういうことですね。

はい、これでプログラミングは出来ましたので、
あとはこの画面をこっちに重ねて
クリアボタンとか、レイアウトを仕上げれば完成です。

 

 

 

    コメントを残す