このサイトでは、分析、カスタマイズされたコンテンツ、および広告に Cookie を使用します。このサイトを引き続き閲覧すると、Cookie の使用に同意するものと見なされます。
Hi, Developers,
straightapps.com ロゴ
作成日 December 26, 2017、全面更新 April 9, 2021
トップページ > Android 開発トップ > タッチ入力の取得(基本編)

ここでは、タッチイベントの検出と、タッチ位置の特定について検討しています。

line
Android 開発
line

Android ネイティブアプリでタッチイベントを取得するのは、簡単です。

開発環境は、Windows 10 + Visual Studio 2019 です。 すでに、 「VS2019 の Android 開発環境を整える」から始まり、 「VC++ 2019/2015 で新規プロジェクトを作成する」など、 自動的に作成されるコードが、実機でテストできていることが前提です。

Windows プログラムでは、マウスやタッチの入力があると、 WM_LBUTTONDOWN ( マウスの左ボタンが押されたときの通知メッセージ。 ) 等のメッセージが送信され、そのパラメータに座標が付けられています。

Android の場合は、タッチイベントの発生で専用のメッセージが通知されるのではなく、 入力イベントがあると呼び出される engine_handle_input 関数で、 どんな入力イベントが発生したかを受領してその種別の判定し、 AMotionEvent_getX 関数などを呼び出して、 そのイベントの発生座標を取得することになるようです。

ここでは、タッチイベントの判別や返される座標と画面の対応などを検証しています。

▼ セクション一覧

タッチ処理用の独自変数の追加 - April 9, 2021
用意した変数の初期化 - April 9, 2021
タッチ入力の取得(基本編)
画面に線を描画して確認 - April 9, 2021

なお、本サイトのご利用に際しては、必ずプライバシーポリシー(免責事項等)をご参照ください。
また、本サイトが初めての方は、まずこのページの注意事項をご覧ください。

タッチ処理用の独自変数の追加

投稿 April 9, 2021

コードを確認する前に、どういう形にするかを明確にしておきます。

タッチの状況を詳しく調べるには、タッチイベントを検出できたらログを出力するなどが必要です。 「C/C++ によるログ出力」で、 標準の logcat を使用しない、ファイルへのログ出力を検討・実装していますが、それなりに面倒です。

ここでは簡単に、タッチされたところから、タッチを離されたところまで、 画面にラインを引いてみる、という動作としています。

タッチされた座標を x1, y1、タッチを離された座標を x2, y2 として、 これを OpenGL ES の関数で画面に線を引いています。 OpenGL ES については、「OpenGL ES での色指定」から始まる記事に書いています。

自分用の変数の追加位置は、 自動生成されたコードにならって、main.cpp の最初のほうで定義されている、 saved_state 構造体にします。

/**
* 保存状態のデータです。
*/
struct saved_state {
	float angle;
	int32_t x;
	int32_t y;
};

このようなコードがすでにありますので、 この部分はそのままにして、以下の変数を追加しておきます。

	int32_t x1, y1;
	int32_t x2, y2;
	bool bInit;

Windows デスクトッププログラムを開発していない方は、bool 型の変数の名前が気持ち悪いかも知れません。 変数名の前の部分に型名の省略形を付けているもので、 bool なので b で始めて、その意味を英語で書く、となっています。

x1, y1 から x2, y2 まで線を引いて、タッチされた状況を確認します。 bInit は座標を初期化済みかどうかの判定です。

▲ページ先頭へ

用意した変数の初期化

投稿 April 9, 2021

用意した変数を初期化するため、 ここでは深くは考えず、描画前に呼び出される engine_init_display 関数にコードを追加します。

追加する場所は、 engine->display = display; のような初期化コードがある後です。

	engine->state.x1 = 0;
	engine->state.y1 = 0;
	engine->state.x2 = 0;
	engine->state.y2 = 0;
	engine->state.bInit = false;

座標はすべて 0、初期化されていな状態です。

▲ページ先頭へ

タッチ入力の取得(基本編)

投稿 December 26, 2017, 最終更新 July 30, 2018(様式変更)

ユーザーからの入力、タッチとキーダウンは、main.cpp で扱います。

エントリー関数 android_main の最初の部分で、
state->onInputEvent = engine_handle_input;
と設定しているので、何かの入力イベントが発生すると、 同じ main.cpp で定義されている engine_handle_input 関数 ( static int32_t engine_handle_input(struct android_app* app, AInputEvent* event) と定義されています。 ) が呼び出されます。

自動生成されたコードには、すでにヒントが記述されています。

	if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) {
		engine->state.x = AMotionEvent_getX(event, 0);
		engine->state.y = AMotionEvent_getY(event, 0);

タッチされたのか、離されたのか、どの位置で発生したのかなどの種別が、 引数で渡されている AInputEvent* event にセットされています。 上記のように、 >AInputEvent_getType 関数 が返す値によって、判定できます。 ここではタッチを表す AINPUT_EVENT_TYPE_MOTION しか処理しませんが、 このほかに、AINPUT_EVENT_TYPE_KEY、AINPUT_EVENT_TYPE_FOCUS、AINPUT_EVENT_TYPE_CAPTURE が返されることがあるようです。

今回追加するコードは、自動生成された、AInputEvent_getType 関数AINPUT_EVENT_TYPE_MOTION であることを判定した if 文の中となります。

自動生成されたコードは、if 文の中には、変数に座標を記録するものしかありません。 engineengine_handle_input 関数に渡された引数で、 main.cpp の最初のほうで定義されている engine 構造体です。 その中の saved_state 構造体である state の x と y に、タッチイベントが発生した座標がセットされます。

自動生成されたコードはそのままにして、以下のコードを追加してみます。

まずは、どんなタッチイベントが発生したかの判定です。

	int32_t action = AMotionEvent_getAction(event);

改めて、event は engine_handle_input 関数に渡された引数です。 これを AMotionEvent_getAction 関数 に渡すと、「モーションイベントとポインターインデックス」が合成された値が返される、とあります。 返された値 action を AMOTION_EVENT_ACTION_MASK で AND 演算すると、 モーションイベントだけを取り出して、アクションを判断することができます。

	switch (action & AMOTION_EVENT_ACTION_MASK){

これで判断できるアクションはたくさんありますが、 ここでは以下の 3 つのみ、処理することにしています。

まずは、Windows で言えば WM_LBUTTONDOWN メッセージ ( マウスの左ボタンが押下されたときに発生するメッセージです。 ) に当たる、AMOTION_EVENT_ACTION_DOWN です。 これは、今タッチされはじめたことを示すイベントです。

Windows のマウスでは、クリックには左、右、ホイールが押せる場合は中央などありますが、 タッチには(複数タッチはここでは扱いませんので)区別はありません。 次のように、座標を設定します。

	case AMOTION_EVENT_ACTION_DOWN:
		engine->state.x1 = engine->state.x2;
		engine->state.y1 = engine->state.y2;

新しくタッチされたとき、 前回画面に引いたラインの終点の座標 x2, y2 を、 新しく引くラインの始点 x1, y1 にコピーしています。 このあとの処理も行わせたいので、あえて break を書いていません。

続けて、Windows の WM_MOUSEMOVE メッセージ ( マウスのボタンを押したまま移動させたときに発生するメッセージです。ドラッグのような操作です。 ) 相当の処理、AMOTION_EVENT_ACTION_MOVE です。 指を画面に置いたら AMOTION_EVENT_ACTION_DOWN、 そのまま画面から離さずに移動させたら AMOTION_EVENT_ACTION_MOVE となります。

	case AMOTION_EVENT_ACTION_MOVE:
		engine->state.x2 = AMotionEvent_getX(event, 0);
		engine->state.y2 = AMotionEvent_getY(event, 0);

画面に引くラインの終点、x2, y2 にタッチされている座標を設定します。 この直前の case から連続していますので、 どちらの場合も、終点 x2, y2 を更新します。

座標が初期化されていない場合、座標として 0 が設定されてしまっていますので、 最初にタッチされた座標をラインの始点とします。 アプリ起動時にすでにタッチされていると、AMOTION_EVENT_ACTION_DOWN なく AMOTION_EVENT_ACTION_MOVE が来る可能性があるのではないかと思います。

		if (!engine->state.bInit) {
			engine->state.bInit = true;
			engine->state.x1 = engine->state.x2;
			engine->state.y1 = engine->state.y2;
		}
		break;

動作に直接的には関係ありませんが、if 文の条件としている !engine->state.bInit のような書き方が、 Android 系のネットの情報ではあまり使われていないような気がします。 if 条件式は真または偽で判断され、! 記号は否定の not ですので、 bInit が false のときは否定して true と評価されるので条件式が成立、 true のときは false となって不成立です。 変数の値が 0 かどうかの判定で、0 のときのみ成立させるには、 if (!value){ のように書きますが、 近年は非標準なのでしょうか?

最後にもう1つ、Windows の WM_LBUTTONUP メッセージ ( マウスのボタンを離したときに発生するメッセージです。 ) 相当の処理、AMOTION_EVENT_ACTION_UP です。

		case AMOTION_EVENT_ACTION_UP:
			engine->state.x2 = AMotionEvent_getX(event, 0);
			engine->state.y2 = AMotionEvent_getY(event, 0);
			break;

終点の座標 x2, y2 に、最後の座標を設定しています。 「タッチ操作の確定」に使うつもりで用意しているのですが、 今回の線を引くだけの処理なら、不要みたいです。


余談ですが、私はタッチ(ダウン)処理であれば、 OnButtonDown 関数 ( MFC 風に、OnButtonDown(float x, float y) のようにします。 ) を作成して呼び出すようにします。 基本的には main.cpp に深くコードを書くべきではないと思います。

同じように、タッチ(アップ)処理 AMOTION_EVENT_ACTION_UP なら、 OnButtonUp 関数を作成して呼び出すようにします。 ちなみに、ここで扱っている UP 処理は「決定(動作を実行する)アップ」であるそうで、 このほかに「キャンセルのアップ」という動作もあるようですが、 どういうことなのかは、今はわかりません。

AMOTION_EVENT_ACTION_MOVE でも、 OnMouseMove 関数を作成して処理することにします。


特にログを出力する場合に注意すべきことは、 AMOTION_EVENT_ACTION_MOVE のようなイベントは、 1タッチ(1移動)で1回通知されるのではなくタッチされている間じゅう通知さることです。 同じ座標をタッチし続けても、同じ座標のまま通知が来ますので、 ログ出力したりすると膨大になりますし、 何か処理を行うと、繰り返して処理が発生してしまいます。 機能の確定動作などは、 アップイベントで行うと良さそうです。

タッチイベントで取得できる値はデバイスのスクリーン座標ですので、 整数で、単位はピクセルです。 なのでその範囲は、デバイスの画面解像度によって異なります。

デバイスの向きは、特に何もしないでも、考慮されたものとなっていました。 縦長(ポートレート)のときは左上が (0,0) となり、 横長(ランドスケープ)のときもその向きでの左上が (0,0) でした。

▲ページ先頭へ

画面に線を描画して確認

投稿 April 9, 2021

タッチされた座標を正しく取得できているかを確認するため、 OpenGL ES で画面に線を描画して確認できるようにします。

しかしこれが Windows のように簡単にはいかず、 OpenGL ES の画面の座標系を知らないといけません。

Android デバイスには、さまざまな画面サイズがあり、画面解像度があり、 基本は全面使用のアプリになりますから、画面の座標は割合で表示されることになっています。

x 座標は、画面の左端が -1.0、画面の右端が 1.0 で、中央が 0.0 です。

y 座標は、画面の下端が -1.0、画面の上に向かうにつれ値が大きくなり、画面の上端が 1.0 で、中央が 0.0 です。 Windows メインの場合、この y 座標の反転がとても気になるポイントです。

デバイスの画面サイズ(ピクセル数)は、 engine_init_display 関数で、 engine->width = w; および engine->height = h; として、取得されています。

今回追加するコードは、 engine_draw_frame 関数の、 glClear( GL_COLOR_BUFFER_BIT ); 呼び出しと、 eglSwapBuffers( engine->display, engine->surface ); 呼び出しの間に追加します。

glClear 関数は、画面全体をクリアする関数で、自動生成されたコードに含まれているものです。 eglSwapBuffers 関数は、バッファに描画した画面イメージを、実際の画面に転送するための呼び出しです。

描画する線の始点と終点を指定するために、 画面の座標を設定する変数(配列)を用意します。

	GLfloat vtx_Line[] = {
		0.0f, 0.0f,		// 始点 x, y
		0.9f, 0.5f,		// 終点 x, y
	};

GLfloat は、OpenGL でいう float 型です。 要素数指定なしで、配列を作成しています。 設定している値は、このあと書き換えるので意味はありませんが、 このあとの座標指定をコメントアウトすれば、画面の座標のテスト(確認)に使えます。

	vtx_Line[0] = ((GLfloat)engine->state.x1 * 2.0f / (GLfloat)engine->width) - 1.0f;
	vtx_Line[1] = 1.0f - ((GLfloat)engine->state.y1 * 2.0f / (GLfloat)engine->height);

始点の座標を書き換えています。

x 座標の範囲は -1.0 から 1.0 まで、範囲は 2 です。 変数 x1 に設定された、単位がピクセルのスクリーン座標となっている始点の座標を、 engine_init_display 関数で設定された画面幅のピクセル数 width で割れば、 0.0 〜 1.0 の範囲で計算されます。 これだと範囲が 1 しかありませんので 2 倍して、0.0 〜 2.0 とします。 画面での指定は -1.0 〜 1.0 ですので、最後に -1.0 して、範囲を合わせています。 なお、2.0f や 1.0f のように、数値の最後に f を付けているのは、float 型であることを明示するためです。 2.0 や 1.0 と書くだけで int としては計算されないとは思いますが、間違いが起こりにくそうです。

y 座標の範囲は、画面下から上に向かって、-1.0 から 1.0 です。 注意が必要なのは、変数 y1 に設定された、単位がピクセルのスクリーン座標は、画面上端が 0 で、 下に向かって大きな値となることです。

まずは x 座標の計算と同じように、画面の縦のピクセル数 height を使って 0.0 〜 2.0 の値を得ます。 計算された値が 0.0 なら画面の一番上がタッチされたことになりますので、 描画のための座標指定は 1.0 になるべきです。 また、計算結果が 2.0 なら画面の一番下がタッチされたことになりますので、 描画のための座標指定は -1.0 になるべきです。 この上下反転を行うため、計算された結果を 1.0 からマイナスしています。

	vtx_Line[2] = ((GLfloat)engine->state.x2 * 2.0f / (GLfloat)engine->width) - 1.0f;
	vtx_Line[3] = 1.0f - ((GLfloat)engine->state.y2 * 2.0f / (GLfloat)engine->height);

終点についても、同様の計算を行います。

	glVertexPointer(2, GL_FLOAT, 0, vtx_Line);
	glEnableClientState(GL_VERTEX_ARRAY);

glVertexPointer 関数で、配列に入れた座標を使う設定にします。 glVertexPointer は、第1引数が頂点座標数なので 2 組、データ型は float で、第3引数の stride は今はわかりませんが、0 が良いようです。 最後に配列のポインタを渡します。

続く glEnableClientState 関数は、 引数に GL_VERTEX_ARRAY を渡すと、この頂点座標指定を有効にする、という意味になります。

	glColor4f(0.0f, 0.0f, 1.0f, 1.0f);
	glLineWidth(20.0f);
	glDrawArrays(GL_LINES, 0, 2);

この部分で、線を引いています。

glColor4f 関数は描画色指定で、R、G、B、A の順に、0.0 〜 1.0 の範囲で指定します。 最後の A は透過度で、0.0 なら透明、1.0 なら不透過です。 この場合は青指定です。

glLineWidth 関数はラインの太さ指定です。 大きな値を指定するほど、ラインが太くになります。

最後の glDrawArrays 関数が描画をしています。 GL_LINES を指定すると、線を引きます。 座標を 3 組以上指定すると、連続して線が描画されるようです。 第 3 引数で、頂点座標数を指定しています。

これだけでも確認できますが、画面としては面白くないので、 タッチした点を描画してみます。

	glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
	glPointSize(30.0f);
	glDrawArrays(GL_POINTS, 0, 2);

本当は円を描きたかったのですが、そう簡単には書けないようです。

glColor4f 関数で色指定です。 今度は不透明の赤にしています。

glPointSize 関数で、点のサイズを指定しています。

そして glDrawArrays 関数に、 今度は GL_POINTS を渡して、点を描画しています。 描画する点の数は、最後の引数で指定している 2 です。

ここでは座標を指定していませんが、線を描画するときにすでに始点と終点を指定していますので、 そのまま使っています。

	glDisableClientState(GL_VERTEX_ARRAY);

最後に、有効にした頂点座標指定を無効に戻して、描画が完了です。

画面の左上のほうをタッチし、そのまま指を動かすと、ラインが追従してきます。 これは AMOTION_EVENT_ACTION_MOVE 処理で、終点の x2, y2 を更新しているためです。

始点の右下で離すと、このような画面になります。 左上の赤四角が x1, y1 の位置で、右中央の赤四角が x2, y2 の位置となります。 背景の色は、自動生成された、タッチした場所で変わるグラデーション変化によるものですので、 今回の追加コードには関係ありません。

ラインを描画した様子

再びタッチすると、右中央の x2, y2 が新しく始点 x1, y1 となります。 ラインが追従してきて、左下に進むと、こんな具合に更新されます。

開始する

とりあえずこれで、タッチの情報は得られました。

が、本来 Windows プログラマの私としては、こんな -1.0 〜 1.0 による画面指定は難しいので、 画面の左上を 0, 0 とし、右あるいは下に向かって値が大きくなるような座標指定にしたいです。 また、デバイスによるタテヨコ比も考慮する必要があります。

▲ページ先頭へ
line
関連トピックス
line

OpenGL ES での色指定

Visual C++ で自動生成されたコードを参照し、OpenGL ES での背景色指定と描画について、書いています。

OpenGL ES で 2D テクスチャを作成する

OpenGL ES を使用して 2D テクスチャを作成する方法について、検討しています。

line
その他のおすすめ
line

Android 開発に関する記事をまとめた Android 開発トップ もご覧ください。

JavaScriptが無効です
▲ページ先頭へ


© 2017-2021 StraightApps.com 無断転載を禁じます。No reproduction without permission.