神経衰弱アプリを作ってみる
9.カードをタップして表にしたり裏にしたり
前回まででようやくカードを並べたんで、AVDで表示してみたよね。
でもここで、一度カードを表にすると二度と裏に戻せないという悲劇的現状に気づいたことと思う。
あぁ、人生にやり直しはきかないのか!めくったカードは裏に戻すことはできないのか!と嘆くことなかれ。
時間を元には戻せないけれども、表にしたカードを裏に戻すことはプログラムを変更することによって可能なのだよ。
というわけで、以下がその変更を加えた結果のプログラムだ。
赤字が追加変更部分なので、ご注目。
package android.test;
import java.util.ArrayList;
import java.util.Collections;
import android.app.Activity;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.Display;
import android.view.Menu;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.TableLayout;
import android.widget.TableRow;
public class AndroidTestActivity extends Activity {
static final int ROW = 6; //セルの行数・・・(1)
static final int COL = 4; //セルの列数・・・(2)
//カードの配列
Card[] card = new Card[ROW*COL]; //・・・(11)
@Override
public void onCreate(Bundle savedInstanceState) {
//ステータスバーを消去
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); //・・・(3)
//タイトルバーを消去
requestWindowFeature(Window.FEATURE_NO_TITLE); //・・・(4)
super.onCreate(savedInstanceState);
setCards(); //・・・(12)
TableLayout tableLayout = getTableLayout(); //・・・(5)
setContentView(tableLayout); //・・・(6)
}
/**
* カードの配列にランダムに画像をセット
*/
private void setCards() {
//カードの表の画像(配列)
TypedArray images = getResources().obtainTypedArray(R.array.card_images);
//imageArrayを使って、すべての画像番号をシャッフルする
ArrayList<Integer> imageArray = new ArrayList<Integer>();
for ( int i = 0; i < images.length(); i++ ) {
imageArray.add(i);
}
Collections.shuffle(imageArray);
//cardArrayを使って、カードの数分の画像番号をペアで取り出しシャッフルする
ArrayList<Integer> cardArray = new ArrayList<Integer>();
for ( int i = 0; i < ROW*COL/2; i++ ) {
cardArray.add(imageArray.get(i));
cardArray.add(imageArray.get(i));
}
Collections.shuffle(cardArray);
//カードの配列に、シャッフルした画像番号と画像をセットする
for (int i=0; i < ROW*COL; i++) {
card[i] = new Card(
cardArray.get(i),
images.getDrawable(cardArray.get(i))
);
}
}
/**
* テーブルレイアウトを返却
* @return TableLayout
*/
private TableLayout getTableLayout() {
int cellNum = ROW * COL; //全体のセルの数
TableLayout tableLayout = new TableLayout(this);
TableRow tableRow = null;
for (int i = 0; i < cellNum; i++) {
if (i % COL == 0) {
tableRow = new TableRow(this);
tableLayout.addView(tableRow);
}
Button button = new Button(this);
button.setId(i); //・・・(7)
button.setHeight(getCellHeight()); //・・・(8)
button.setWidth(getCellWidth()); //・・・(9)
//ボタンに裏面の画像をセット
button.setBackgroundDrawable(
getResources().getDrawable(R.drawable.card_back)
);
setEvent(button); //・・・(10)
tableRow.addView(button);
}
return tableLayout;
}
/**
* 行数と画面の幅からセルの高さを算出
* @return セルの高さ
*/
private int getCellHeight() {
WindowManager windowManager = getWindowManager();
Display display = windowManager.getDefaultDisplay();
return display.getHeight() / ROW;
}
/**
* 列数と画面の幅からセルの幅を算出
* @return セルの幅
*/
private int getCellWidth() {
WindowManager windowManager = getWindowManager();
Display display = windowManager.getDefaultDisplay();
return display.getWidth() / COL;
}
/**
* 各ボタンビューにイベントをセット
* @param button
*/
private void setEvent(Button button) {
//タップ時の動作
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Button button = (Button)v;
int cardId = button.getId();
card[cardId].reverseCard(); //・・・(13)
dispCard(button, card[cardId]); //・・・(14)
}
});
}
/**
* カードの画像を表示する
*/
private void dispCard(Button button, Card card) {
if(card.isCardOpen()) {
//表面
button.setBackgroundDrawable(card.getCardImg());
} else {
//裏面
button.setBackgroundDrawable(
getResources().getDrawable(R.drawable.card_back)
);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_android_test, menu);
return true;
}
}
/**
* カードのクラス
*/
class Card {
private int imgNo; //画像番号
private Drawable cardImg; //画像
private boolean cardOpen; //カードが表になっているとき:true
Card(int imgNo, Drawable cardImg) {
this.imgNo = imgNo;
this.cardImg = cardImg;
this.cardOpen = false;
}
int getImgNo() {
return imgNo;
}
Drawable getCardImg() {
return cardImg;
}
boolean isCardOpen() {
return cardOpen;
}
void reverseCard() {
cardOpen = !cardOpen;
}
}
まずは、一番下の部分のcardクラスを見て欲しい。
ここに
private boolean cardOpen;
としてフィールドを一つ追加した。
これはそのカードが表になっているか裏になっているか、という状態を保持するためのフィールドだ。
「boolean」というのは「true(真)」と「false(偽)」の2つの値を持つ変数で、ここではカードが表のときに「true」と決めた。
というわけでその下のコンストラクタの中で、変数「cardOpen」は初期値として「false」をセットしてある。
つまり、最初はカードが裏の状態になってるってことだね。
続いてその下には、変数「cardOpen」の値を取得するためのメソッド「isCardOpen」を追加した。
これはつまり、変数「cardOpen」のゲッターだね。
そして最後に、「reverseCard」というメソッドが追加してある。
これは何かと言うと、カードの裏表の状態を切り替えるメソッドだ。
「!」とあるのは「NOT(否定)」を表す単項演算子で、つまり
cardOpen = !cardOpen;
というのは「cardOpen」の値が「true」のときは「false」に、逆に「false」のときは「true」というように値を逆にする処理を表している。
つまりカードの状態が「表」のときは「裏」に、「裏」のときは「表」にするのがこのメソッドの機能なわけ。
これを使って、カードの裏表の状態を変化させてやるんだね。
で、その「reverseCard」のメソッドをどこで使っているかというと、グィっと上のほうへ戻って各ボタンビューにイベントをセットしている「setEvent」のメソッドを見てもらいたい。
ここの(13)のところで、「reverseCard」を呼んでるね。
つまりカード(ボタン)をタップすると、この「reverseCard」が呼ばれて、タップされたカードの状態が「表」になったり「裏」になったりするってことだ。
そしてそのすぐ次の(14)のところでカードの状態を画面に表示しているのが、新しく追加したメソッド「dispCard」。
これは、今まで
button.setBackgroundDrawable(card[cardId].getCardImg());
としてカードの表の画像を表示していたところを新たに外に出してメソッドにしたってこと。
で、どこに出したかっていうと・・・そのすぐ下をご覧あれ。
private void dispCard(Button button, Card card)
として定義してあるね。
見てお分かりのように引数として、タップしたボタンとそれに対応するカードのインスタンスを渡している。
そして中身を見てみると、if文を使ってカードが表の場合と裏の場合のそれぞれの場合分けをして処理を行っているのが分かるね。
ここでカードが表か裏かを判断するために使っているのが、前述の「isCardOpen」メソッド。
if(card.isCardOpen())
としてこれが「true」の場合は「表」、「false」の場合は「裏」ってわけ。
そしてそれぞれの場合に応じて、「setBackgroundDrawable」メソッドを使ってカードの画像を表示してるってのがこのメソッドの仕組み。
表側の表示は前回カードの表の画像を表示した方法と同じだし、裏側の表示は初期状態でカードの表示をした部分と同じだから、特に難しくないね。
というわけで、タップするごとに(13)の処理でカードの裏表の状態を切り替える。
そしてそのカードの状態に応じて、(14)の処理で画像として表示するっていうのが今回追加した処理の概要だ。
では、早速これをAVDで実際に動かしてみよう。
クリック(タップ)するごとに、カードが表になったり裏になったりするのが確認できたでしょ。
というところで、サクッと今回は終了。
次回は、開いた2枚のカードが一致しているかどうか判定する仕組みについて考えてみよう。