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.x
とevent.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)
はネイティブコードを呼んでいるようで、ダイアログ等何も出ずにいきなりアプリが落ちました。