|
○ はじめに
ユーザと対話するアプリケーションにとって、ユーザ入力を受け付けることは非常に重要な機能です。というか、入力を受け付けられなかったら対話アプリケーションは作れません。
ここではユーザ入力のうち、キー入力について検出して対応する方法について説明します。
○ キーイベントとキーコード
Android アプリケーションでは、キー入力は android.view.KeyEvent として表現されます。この KeyEvent は、android.view.View クラスの onKeyDown() および onKeyUp() メソッドを通してアプリケーションに通知されます。
public boolean View#onKeyDown(int keyCode, KeyEvent event); public boolean View#onKeyup(int keyCode, KeyEvent event);
これらのメソッドには、前述の KeyEvent のインスタンスと同時に、押されたキーを表すキーコードが引数として渡されます。キーコードは KeyEvent#getKeyCode() を使って取り出すことも可能です。API ドキュメントで用意されているキーコード定数(KEYCODE_で始まるメンバ)を確認出来ます。
また、ALT キーが押されているかどうかは
public final boolean KeyEvent#isAltPressed();
を使って調べることもできます。その他にも色々なメソッドが android.view.KeyEvent クラスには用意されていますので、一度 API ドキュメントを眺めてみて下さい。
これらの KeyEvent は、ソフトウェアキーボードの場合は通常は生成されない様です(API ドキュメントによる)。ソフトウェアキーボードの場合にはこれらのイベントに依存してはならないとあります。
○ サンプルプログラムと説明
キー入力を検出するサンプルプログラムについて説明します。006keyapp.zip からダウンロードできます。はじめに のページから、全てのサンプルプログラムを 固めた zip ファイルをダウンロードすることもできます。
なお、ソースコードは SDK Tools 22.0.4 / プラットフォーム Android 4.2 でコンパイル可能です。local.properties 内に sdk.dir 変数が定義されていますが、ここは環境依存になりますので、お使いの環境に合わせて変更してください。
ソースコード配布は修正 BSD ライセンスに従います(はじめに のページの免責事項を参照してください)。
配布内容のファイルツリーは↓の様になります。
- 006/
- AndroidManifest.xml
- ant.properties
- bin/
- build.xml
- libs/
- local.properties
- proguard-project.txt
- project.properties
- res/
- drawable-ldpi/ic_launcher.png
- drawable-mdpi/
- ic_launcher.png
- ball.png
- drawable-hdpi/ic_launcher.png
- drawable-xhdpi/ic_launcher.png
- layout/main.xml (未使用)
- values/strings.xml
- src/riolab/sample/
- KeyApp.java
- KeyView.java
今回は res/drawable-mdpi/ball.png を表示します。キー入力によって、この画像の表示位置を変えてみます。
↓ball.png (24x24)
QVGA(320x480:mdpi) のエミュレータで実行すると、↓の様に表示されました。
JKHL(上下左右)のキーを押す/離すごとに、ボールが上下左右に移動します。DPADキーにしたかったのですが、自分の PC はテンキーが無くてうまく認識してくれなかったので^^; 特に画面外かどうかの判定は行っていないため、画面外のどこまでも移動しちゃいます。
ここでは
- KeyApp.java
- KeyView.java
について説明します。
- KeyApp.java
1 /* 2 * Copyright (c) 2013, Ryo Ichinose 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * * Neither the name of the auther nor the 14 * names of its contributors may be used to endorse or promote products 15 * derived from this software without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 19 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 20 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 21 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 23 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 24 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 25 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 26 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 27 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 */ 29 30 package riolab.sample; 31 32 import android.app.Activity; 33 import android.os.Bundle; 34 import android.view.Window; 35 36 public class KeyApp extends Activity 37 { 38 /** Called when the activity is first created. */ 39 @Override 40 public void onCreate(Bundle savedInstanceState) 41 { 42 super.onCreate(savedInstanceState); 43 requestWindowFeature(Window.FEATURE_NO_TITLE); 44 setContentView(new KeyView(this)); 45 } 46 }
これまでのサンプルプログラムの Activity クラスと、特に変わりはありません。
- KeyView.java
1 /* 2 * Copyright (c) 2013, Ryo Ichinose 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * * Neither the name of the auther nor the 14 * names of its contributors may be used to endorse or promote products 15 * derived from this software without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 19 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 20 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 21 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 23 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 24 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 25 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 26 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 27 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 */ 29 30 package riolab.sample; 31 32 import android.content.Context; 33 import android.content.res.Resources; 34 import android.graphics.Bitmap; 35 import android.graphics.Canvas; 36 import android.graphics.Color; 37 import android.graphics.Paint; 38 import android.graphics.drawable.Drawable; 39 import android.view.KeyEvent; 40 import android.view.View; 41 42 public class KeyView extends View 43 { 44 private final Paint paint = new Paint(); 45 private Bitmap bitmap; 46 private int x; 47 private int y; 48 49 public KeyView(Context context) 50 { 51 super(context); 52 setFocusable(true); 53 54 Resources res = context.getResources(); 55 Drawable drawable = res.getDrawable(R.drawable.ball); 56 57 int w = drawable.getIntrinsicWidth(); 58 int h = drawable.getIntrinsicHeight(); 59 60 bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); 61 Canvas canvas = new Canvas(bitmap); 62 63 drawable.setBounds(0, 0, w, h); 64 drawable.draw(canvas); 65 } 66 67 @Override 68 protected void onSizeChanged(int w, int h, int oldw, int oldh) 69 { 70 x = w / 2; 71 y = h / 2; 72 } 73 74 @Override 75 public void onDraw(Canvas canvas) 76 { 77 super.onDraw(canvas); 78 canvas.drawColor(Color.BLUE); 79 canvas.drawBitmap(bitmap, x, y, paint); 80 } 81 82 @Override 83 public boolean onKeyDown(int keyCode, KeyEvent event) 84 { 85 if (processKeyCode(keyCode)) { 86 invalidate(); 87 return true; 88 } 89 90 return super.onKeyDown(keyCode, event); 91 } 92 93 @Override 94 public boolean onKeyUp(int keyCode, KeyEvent event) 95 { 96 if (processKeyCode(keyCode)) { 97 invalidate(); 98 return true; 99 } 100 101 return super.onKeyUp(keyCode, event); 102 } 103 104 private boolean processKeyCode(int keyCode) 105 { 106 if (keyCode == KeyEvent.KEYCODE_K) { 107 y -= 10; 108 return true; 109 } 110 111 if (keyCode == KeyEvent.KEYCODE_L) { 112 x += 10; 113 return true; 114 } 115 116 if (keyCode == KeyEvent.KEYCODE_J) { 117 y += 10; 118 return true; 119 } 120 121 if (keyCode == KeyEvent.KEYCODE_H) { 122 x -= 10; 123 return true; 124 } 125 126 return false; 127 } 128 }
キー入力に応じて画像の表示位置を変える View クラスです。画像の表示自体については、LECTURE 5 画像を表示する を参照してください。
- 46 〜 47 行目
画像の表示位置を保存する変数です。画像の左上の角を表します。
- 67 〜 72 行目 (onSizeChanged())
View のサイズが変わった時に実行されるメソッドです。本プログラムでは、View のほぼ真ん中(x、y は画像左上の座標のため、正確には真ん中ではありませんが)に画像を表示するために、本メソッドをオーバライドしています。
- 74 〜 80 行目 (onDraw())
79 行目の canvas.drawBitmap(bitmap, x, y, paint); により、保存してある x/y の位置に画像を表示します。
- 82 〜 91 行目 (onKeyDown())
キーが押された際に実行されるメソッドです。KeyEvent を処理した場合は true、処理しなかった場合は false を返します。
自作の processKeyCode() によってキーに応じて画像の表示位置を更新し、View#invalidate() にて画面の再描画を促します。View#invalidate() には領域指定有り/無しバージョンがありますが、ここでは領域指定無しバージョンを使うことで画面全体の再描画を指示しています。
View#invalidate() は、UI スレッド(UI 関連の処理を行うスレッド。コールバックメソッドは UI スレッドから実行される)以外のスレッドから実行してはいけません。UI スレッド以外から画面の再描画を指示するには、View#postinvalidate() を使用してください。
- 93 〜 102 行目 (onKeyUp())
キーが離された際に実行されるメソッドです。onKeyDown() の説明を参照してください(本プログラムでは、キーを押したのか離したのかでは処理を区別していません)。
- 104 〜 127 行目 (processKeyCode())
JKHL(上下左右)が操作された場合に、画像の表示位置(x/y) を更新するメソッドです。本メソッド実行後に View#invalidate() を実行することで、新しい表示位置に画像が表示されます。
以上で解説は終わりです。次回は、複数 View を同時に画面に表示するために必要となる、レイアウト管理用クラスについて説明する予定です。
○ OnKeyListener を使ってキー入力処理を切り出す
サンプルプログラムでは、View クラスの onKeyDown()/onKeyUp() をオーバライドしてキー入力を処理しました。この方法は簡単ですが、プログラムがある程度大きくなってくると、キー入力処理は別のクラスに任せてコードをすっきりさせたいという要求が出てきたりします。
この様な場合に使えるのが、android.view.View.OnKeyListener インタフェースです。OnKeyListener インタフェースは、キー入力を処理するクラスが実装すべきインタフェースで、実装クラスを View#setOnKeyListener() に指定するとキーイベントが転送されます。
public void View#setOnKeyListener(View#OnKeyListener l);
OnKeyListener インタフェースで定義されているメソッドは、onKey() メソッドのみです。実装クラスは、本メソッドを実装します。
public boolean onKey(View v, int keyCode, KeyEvent event);
KeyEvent が発生した View と、keyCode および KeyEvent のインスタンスが引数によって伝えられます。View#onKeyDown()/onKeyUp() 同様、KeyEvent を処理した場合は true、処理しなかった場合は false を返します。キーが押されたのか離されたのかは、KeyEvent#getAction() にて取得します。
public final int KeyEvent#getAction();
KeyEvent#getAction() メソッドは、以下のいずれかを返します。
- KeyEvent#ACTION_DOWN
- KeyEvent#ACTION_UP
- KeyEvent#ACTION_MULTIPLE