モジュール 2 タスク 3: キーボードナビゲーションの追加
- 概要
-
- キーのアクションを追加する
- 壁をスクロールする関数を作成する
- 選択した画像のボーダーを作成する
はじめに
このタスクはキーボードナビゲーション機能を導入します。キーボード機能では、矢印キーを使用して行と列を縦横にスクロールしますが、その際にサムネイルの壁にある列および行の数を考慮します。ステージに表示される最後のサムネイルまでナビゲートすると、行または列がスクロールして、ステージ境界内にさらに多くのサムネイルが現れます。列の終わりに達すると、フォーカスは自動的に次の列内の最初の画像に移動します。アクティブな画像が拡大されている間は、次の画像にスクロールすることが可能です。次の画像にフォーカスが置かれると自動的に拡大されます。Enter キーを押すと選択した画像が拡大され、Esc キーを押すと画像が閉じます。
プロジェクトの実行
- モジュール 2 タスク 3 NetBeans プロジェクトをダウンロードして、NetBeans で開きます。
- プロジェクトを実行します。
- 左上のサムネイルにフォーカスが置かれます。矢印キーを使用して、サムネイルの壁の中をナビゲートします。
図 1 に、このタスクのアーキテクチャーを示します。
図 1
キーボードナビゲーションの構築
主要なキーボードナビゲーション機能は、Wall.fx クラスで構築されます。これは Thumbnail.fx および Main.fx で参照されます。
キーアクションの追加
キーアクションは、javafx.scene.input.KeyCode クラスと javafx.scene.input.KeyEvent クラスを使用して追加されます。次のコードは、キーボードナビゲーションを処理する onKeyPressed 関数を示しています。Wall がキーボード入力を受け取れるように、Wall は requestFocus() を呼び出す必要があります (これは create() 関数で行われます)。壁は入力フォーカスがないとキーボードイベントを受け取りません。
onKeyPressed: function(evt: KeyEvent): Void {
scrollCtl.dragging = true;
if (evt.code == KeyCode.VK_ENTER) {
if ( isMediaShowing ) {
hideMedia();
} else {
thumbnails[thumbWithFocus].showFullView();
}
} else if (evt.code == KeyCode.VK_ESCAPE){
hideMedia();
} else {
var newThumbIndex = thumbWithFocus;
if (evt.code == KeyCode.VK_LEFT) {
newThumbIndex -= Constants.THUMB_ROWS;
} else if (evt.code == KeyCode.VK_RIGHT) {
newThumbIndex += Constants.THUMB_ROWS;
} else if (evt.code == KeyCode.VK_UP) {
newThumbIndex -= 1;
} else if (evt.code == KeyCode.VK_DOWN) {
newThumbIndex += 1;
}
ScrollControl.dragging は、センタリングアニメーションがキーイベントのたびに実行されるのを防ぐために、ScrollControl によって使用されます。これにより、ユーザーはナビゲーションキーを押したまま、サムネイルをすばやくスクロールできます。ドラッグのフラグは、コードの後方で onKeyReleased 関数で false にリセットされます。
thumbWithFocus 変数は、フォーカスが置かれているサムネイルに対応するサムネイルシーケンスへのインデックスです。この変数は最初のサムネイルに初期化されます。onKeyPressed イベントを処理するコードブロックは、単に thumbWithFocus の新しい値を計算してから、壁をスクロールしてサムネイルが表示されるようにします。
Wall のスクロール
選択したサムネイル画像がステージの最後の列から 2 番目にある場合は、壁をスクロールさせて、より多くの列が表示されるようにする必要があります。次の画像は、ステージの左側から 2 番目の列にあるアクティブな画像を示しています。右スクロールを続行する場合は (図 2)、アプリケーションを左から右にスクロールさせて、より多くの画像が表示されるようにする必要があります。
図 2
次のコードサンプルは、フォーカスが置かれたサムネイルが表示されるようにするコードです。サムネイルが表示されるかどうかを決めるには getBoundsInScene() を使用することに注意してください。getBoundsInScene() 関数は、Nodeが表示されるシーンに関連します。getBoundsInScene().maxX がゼロよりも小さい場合、サムネイルはステージの左端から離れます。getBoundsInScene().maxX がシーンの幅よりも大きい場合、サムネイルはステージの右端から離れます。boundsInLocal 変数は、その値が Wall の挿入先のシーンではなく Wall を基準にしているため、ここではそれほど使用されません。
サムネイルが表示されない場合、このコードブロックはサムネイルを表示させるために必要な scrollPosition を計算します。詳細については、NetBeans プロジェクト内のコードを参照してください。このプロジェクト内のコードには多数のコメントが付けられています。
if ( newThumbWithFocus.getBoundsInScene().minX !=
oldThumbWithFocus.getBoundsInScene().minX ) {
if (newThumbWithFocus.getBoundsInScene().maxX < 2 * columnWidth) {
var offset = maxVisibleWidth - columnWidth -
newThumbWithFocus.getBoundsInScene().maxX;
var newScrollPosition = (-scrollOffset - offset)/(thumbsGroupWidth - maxVisibleWidth);
if (newScrollPosition < 0.0) {
newScrollPosition = 0.0;
}
scrollPosition = newScrollPosition;
} else if (newThumbWithFocus.getBoundsInScene().minX > maxVisibleWidth - 2 * columnWidth) {
var offset = newThumbWithFocus.getBoundsInScene().minX - columnWidth;
var newScrollPosition = (-scrollOffset + offset)/(thumbsGroupWidth - maxVisibleWidth);
if (newScrollPosition > 1.0) {
newScrollPosition = 1.0;
}
scrollPosition = newScrollPosition;
}
}
選択済み画像のボーダーの追加
アクティブな画像は白いボーダーによって強調表示され、ほかの画像と区別されます。単純な矩形であるボーダー用のコードは、Thumbnail.fx にあります。thumbnail.hasFocus = true を設定することにより、ボーダーが描かれます。これが行われている Wall.fx 内の場所を探してください。
def focusOutline : Rectangle = Rectangle {
x: bind imageView.boundsInParent.minX - 1
y: bind imageView.boundsInParent.minY - 1
width: bind imageView.boundsInParent.width + 2
height: bind imageView.boundsInParent.height + 2
strokeWidth: 2
stroke: Color.WHITE
fill: null
visible: bind hasFocus
};
ナビゲーション時の選択された画像の変更
thumbWithFocus が変わるとともに、選択された画像 (表示されている場合) も同じように変わります。Main で showMedia 関数をリコールすることにより、選択されたサムネイルのメディアが示されます。選択された画像を変更するには、単に showMedia を呼び出すだけです。ただし、この関数は Thumbnail から呼び出されます。また、Thumbnail は、Thumbnail 以外では利用できないデータを渡します。そのため次のフックが Thumbnail に追加され、showMedia が呼び出されます。
package function showFullView() : Void {
fullView(metaData, image);
この関数が呼び出されるかどうかは、現在表示されているメディアが存在するかどうかに基づきます。今までは、Main だけがこれを把握していました。ここでは、この情報を Main の外部で利用できるようにする必要があります。変数 isMediaShowing は Wall に追加されていました。その値は Main で次のように初期化されます。
isMediaShowing: bind (media != null )
これは単に、isMediaShowing の値はメディアが null であるかどうかによって決まることを示しています。これが機能するのは、メディアが Main.hide で null に設定されているためです。
試してみましょう
thumbWithFocus が最初または最後の列にあるときは、それぞれ左または右にナビゲートしても折り返されません。このロジックを修正して、thumbWithFocus が列の最後から次の行の先頭に折り返したり、その逆に折り返すようにします。
VK_ENTER と VK_ESCAPE の処理は、Wall では実際に必要ではありません。VK_ENTER は Thumbnail で処理し、VK_ESCAPE は Media で処理するように試してみます。
また、サムネイルがフォーカスを得ると、Thumbnail は Main の replaceMedia 関数を呼び出すことができます。Main.replaceMedia は、メディアが null である場合にアクションを起こさないように調整する必要があります。その後は、isMediaShowing 変数を Wall から削除できます。
