GlassPaneを作る(その2)
GlassPaneの要件
配下(子供)のGUIを覆う透明又は半透明な領域(パネル)として描画 ・・(済)
表示されている間は一部の入力以外は受け付けない(ブロックする)
自身の上に予め登録されたGUIを描画でき、そのGUIだけは入力を受け付ける
昨日は半透明な領域の描画を実装したので、残る部分に関しても実装していく。(実装しながら書いている)
- タップ、キーボード(トラックボール)による操作を無効にする
重要な機能として表示されている間は配下のビューが操作できないように一切の操作をブロック(阻止)する必要がある。
GlassPaneのスーパクラス、ひいては全てのレイアウトのスーパクラスであるViewGroup抽象クラスにはdispatch〜(イベント名)という名前のメソッドが定義されており、Androidのビュー上で発生したイベントはこのメソッドを辿ることで伝搬していく。
この階層はAndroid SDKに添付されてきた "Hierarchy Viwer"で見ると解りやすい。
(一部階層を省略しているが)このビュー階層の上から下にイベントは伝搬される。
なのでdispatch〜メソッドをオーバライドして、内部でイベントの伝搬を止めることで入力をブロックする。
-
- GlassPane.java
/* キーボード */ @Override public boolean dispatchKeyEvent(KeyEvent event) { return true; //return super.dispatchKeyEvent(event); } /* トラックボール */ @Override public boolean dispatchTrackballEvent(MotionEvent event) { return true; //return super.dispatchTrackballEvent(event); } /* タッチ */ @Override public boolean dispatchTouchEvent(MotionEvent event) { return true; //return super.dispatchTouchEvent(event); }
戻り値にtrueを戻すことでイベントは「消費された」と見なされ、以降の子孫階層にイベントは伝搬しない。
この状態でテストしてみたが、GlassPaneの背後(実際には子供)のビューがキーボード、タッチ(有ればトラックボール)に反応しなくなることが確認できる。(エミュレータにはトラックボールは無いが、F6を押下するとトラックボールモード(左上にボールが表示される)に切り替わる(DELキーを押下している間だけトラックボールのモードにすることも可能)
さて、入力をブロックできたのは良いが困ったことが見つかった。
1. システムにとって必ず動作が必要なキー操作までブロックされてしまう
2. ブロック状態から抜ける手段が無い
1.に関して、Homeキーは元々アプリケーション側では無効にできないので良いが、その他の重要なBackキーやMenuキー、ハードウェアを直接制御するキー(フックup/downや音量up/down等)等、機器の最低限の操作を行うために一部の操作だけは有効にしておかなければならない。
これらの制御の方法としてはイベントのキーコードやイベント状態を直接判定すれば良いのだが、KeyEventクラスには便利な状態検査のためのメソッドがあるので、それを使うことでコードをシンプルにできる。
-
- 一部のキー操作を有効にしたdispatchKeyEventメソッド
@Override public boolean dispatchKeyEvent(KeyEvent event) { /* isSystem()では以下のキーコードが有効と見なされる (Android 2.2)、 KEYCODE_MENU KEYCODE_SOFT_RIGHT KEYCODE_HOME KEYCODE_BACK KEYCODE_CALL KEYCODE_ENDCALL KEYCODE_VOLUME_UP KEYCODE_VOLUME_DOWN KEYCODE_MUTE KEYCODE_POWER KEYCODE_HEADSETHOOK KEYCODE_MEDIA_PLAY_PAUSE KEYCODE_MEDIA_STOP KEYCODE_MEDIA_NEXT KEYCODE_MEDIA_PREVIOUS KEYCODE_MEDIA_REWIND KEYCODE_MEDIA_FAST_FORWARD KEYCODE_CAMERA KEYCODE_FOCUS KEYCODE_SEARCH */ if ( event.isSystem() ) { return super.dispatchKeyEvent(event); } return true; //return super.dispatchKeyEvent(event); }
2.上記の方法で他のアプリケーションに制御を移すことはできるようになったが、依然としてアプリケーション側からブロック/ブロック解除を制御することができない。これではコンポーネントとしては使えないので、GlassPaneの状態を制御するためのメソッドを追加することにする。
-
- lock/unclockメソッドを追加
//マルチスレッドは考慮していない protected boolean lock; public void lock() { this.lock = true; this.innerPaint.setAlpha(225); this.invalidate(); } public void unlock() { this.lock = false; this.innerPaint.setAlpha(0); this.invalidate(); } public boolean isLock() { return this.lock; } public void setLock(boolean lock) { if ( lock ) { this.lock(); } else { this.unlock(); } }
lockとunlockでは描画オブジェクトを切替えるのではなく描画オブジェクトのアルファ値を変更するだけで見た目を変えている。(アンロック時はアルファ値=0で透過つまり見えなくなる)このお陰でdispatchDrawは書き直す必要は無く、必要なのは状態変数lockで現在ロック中か否かを判定して、先ほど無条件でブロックしていた各イベントを通すようにすることだ。
-
- dispatch〜メソッドを更に変更
/* キーボード */ @Override public boolean dispatchKeyEvent(KeyEvent event) { if ( event.isSystem() ) { return super.dispatchKeyEvent(event); } if ( this.islock() ) { return true; } return super.dispatchKeyEvent(event); } /* トラックボール */ @Override public boolean dispatchTrackballEvent(MotionEvent event) { if ( this.islock() ) { return true; } return super.dispatchTrackballEvent(event); } /* タッチ */ @Override public boolean dispatchTouchEvent(MotionEvent event) { if ( this.islock() ) { return true; } return super.dispatchTouchEvent(event); }
これでlock中のみ入力をブロックするようにできる。
ではサンプルを使って実際に動かしてみよう。
ロック中はGlassPaneではイベントを拾えないので、面倒だが(どんだけ面倒くさがりだよ)オプションメニューで状態をトグルするようにしてみた。
-
- GlassPaneTest.java (追加分のみ)
public class GlassPaneTest extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); final GlassPane gp = (GlassPane)this.findViewById(R.id.GlassPane01); gp.lock(); //ロック開始 } @Override public boolean onCreateOptionsMenu(Menu menu) { menu.add(Menu.NONE, Menu.FIRST + 1, Menu.NONE, "アンロック"); return super.onCreateOptionsMenu(menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { if ( item.getItemId() == Menu.FIRST + 1 ) { final GlassPane gp = (GlassPane)this.findViewById(R.id.GlassPane01); gp.setLock(!gp.isLock()); if ( gp.isLock() ) { item.setTitle("アンロック"); } else { item.setTitle("ロック"); } } return true; } }
では実行してみよう。
-
- 実行結果
このように、アプリケーション側でロック/アンロックを制御できるようになった。
ガラス区画上に任意のビューを配置できるようにする
さて、あと一つ残っているが、またもや長くなったので続きはまた今度。