7
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Kotlinで、マルチタッチ時に各指の位置/状態を取得

Last updated at Posted at 2018-12-07

2018/12/18追記
Down時とUp時のコードに誤りがあったので、修正しました。

やりたいこと

「マルチタッチで、各指のダウン、移動、アップをそれぞれで取りたい!」

…だったのですが、中々そのものズバリなサンプルが無かったので、色々組み合わせて作ってみました。

コード

最終的には以下のようなコードになりました。

package touchtest

import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.view.MotionEvent
import kotlinx.coroutines.experimental.GlobalScope
import kotlinx.coroutines.experimental.async
import kotlinx.coroutines.experimental.launch

class MainActivity : AppCompatActivity() {

    private var touchIDs: MutableList<Int> = mutableListOf();

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

    override fun onTouchEvent(event: MotionEvent?): Boolean {

        var pid = event!!.getPointerId(event!!.actionIndex)
        var type = ""
        var x = 0.0f
        var y = 0.0f
        when(event!!.actionMasked){
            MotionEvent.ACTION_POINTER_DOWN , MotionEvent.ACTION_DOWN -> {
                type="down"
                touchIDs.add(pid)
                val idx = event!!.findPointerIndex(pid)
                x = event!!.getX(idx)
                y = event!!.getY(idx)
                output(pid, type, x, y)
            }
            MotionEvent.ACTION_MOVE -> {
                type="move"
                for (id in touchIDs){
                    var idx = event!!.findPointerIndex(id)
                    pid = event!!.getPointerId(idx)
                    x = event!!.getX(idx)
                    y = event!!.getY(idx)
                    output(pid, type, x, y)
                }
                return false
            }
            MotionEvent.ACTION_POINTER_UP, MotionEvent.ACTION_UP -> {
                type="up"
                touchIDs.remove(pid)
                val idx = event!!.findPointerIndex(pid)
                x = event!!.getX(idx)
                y = event!!.getY(idx)
                output(pid, type, x, y)
            }
            else -> return false
        }

        return super.onTouchEvent(event)
    }

    override fun onPause() {
        touchIDs.clear()
        super.onPause()
    }

    fun output(id: Int, type: String, x: Float, y: Float) = launch {
        Log.d("TAG", type + "," + id + "," + x + "," + y)
    }

}

ポイント

シングルタッチとマルチタッチで通り道が違う

一本目の指がダウンした時はMotionEvent.ACTION_DOWNが、二本目以降はMotionEvent.ACTION_POINTER_DOWNが呼ばれます。アップ時も同様です。

なのでコード中では

            MotionEvent.ACTION_POINTER_DOWN , MotionEvent.ACTION_DOWN -> {
                // ...略
            }

というようにして、どちらも同じ処理を通るようにしています。

MotionEvent.ACTION_POINTER_DOWNはAPI Level 5からの実装のようなので、互換性のためにこのような仕様になっているのでしょうか…

event.x/yは常に最初の指の座標が格納されている

例えid3番の指がDown、Upした時であっても、event.xevent.yの値は常に最初の指の情報が入っているようです。
javaだとgetX(int id)でidを指定して座標を取って来るのですが、オーバロードとしてgetX()で最初の座標を取って来れるようです。そう考えると納得の挙動なのですが、kotlinではgetX()のショートカットとしてxがあるので、見え方がややこしいですね…

IDは必ずしもイベント毎に発行される訳ではない

僕自身がFlashやUnityの出身なので、マルチタッチ時には各タッチがオブジェクト化されていて、ID的なものからそのオブジェクトを拾ってきて…みたいな構造が頭にあったのですが、Androidのマルチタッチは少し考え方が違いました。

IDが重要である事には変わらないのですが、

            MotionEvent.ACTION_POINTER_DOWN , MotionEvent.ACTION_DOWN -> {
                // ...
                touchIDs.add(pid)
                // ...
            }

            // ...略

            MotionEvent.ACTION_POINTER_UP, MotionEvent.ACTION_UP -> {
                // ...
                touchIDs.remove(event!!.getPointerId(event!!.actionIndex))
                // ...
            }

とあるように、IDはダウン時とアップ時に自分で取得して、自分で管理しなくてはいけません。FlashやUnityではこの辺りがまとまってオブジェクトになっている感じなので、混乱しました…

その上で、

            MotionEvent.ACTION_MOVE -> {
                type="move"
                for (id in touchIDs){
                    var idx = event!!.findPointerIndex(id)
                    pid = event!!.getPointerId(idx)
                    x = event!!.getX(idx)
                    y = event!!.getY(idx)
                    output(pid, type, x, y)
                }
                return false
            }

というように、改めてIDからポイントを取得する必要があるようです。

途中でpause状態になった時

デバッグ中に意図せずホームボタンを押して、ホームに戻ってしまった時があり、再度アプリを立ち上げてタッチを続けたらすぐに落ちてしまった…という事がありました。

原因としては、恐らくマルチタッチで触っているうちの1つの指がホームボタンに触れてしまった事で配列に格納しているIDの情報がそのまま残っていまい、次にタッチした時に存在しないIDを参照した事によって例外で落ちてしまったのではないか…と考えています。

その対策として

    override fun onPause() {
        touchIDs.clear()
        super.onPause()
    }

pause時にIDを格納している配列をクリアしています。

ちなみに、findPointerIndex(id)はネイティブコードを呼んでいるようで、ダイアログ等何も出ずにいきなりアプリが落ちました。

参考

マルチタッチイベントを取得する
Androidでマルチタッチを使う

7
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?