Schlechte Welt

2008-02-18

[]ブロック崩しをm5-rc14に移植 01:03

f:id:s_welt:20080219010040p:image


移植というほどたいした物ではないけど。


前回からの変更点は

  • AlertDialogの出し方が変わった

変更前は

AlertDialog.show(BlockTest.this, "Congratulations!", "OMEDETOU !!", "OK", true);

としてダイアログをだしてましたが、今回からは

new AlertDialog.Builder(BlockTest.this)

.setMessage("Congratulations!!")

.show();

}

とした方が良さそうです。


以前のやり方は引数も変わっており(iconidが必要になった)、

ただ単に文章を出したい場合はこの方が楽なはずです。

ボタン付きのやつなどはApiDemosのAlertDialogSamples.javaなんかを見ると良いと思います。


  • View.layout()の引数が変わった

変更前はレイアウトを変える(ボタンの位置など)ときは、

v.layout(0, 0, x, y, x+v.getWidth(), y+v.getHeight());

としていましたが今回からは、

v.layout(x, y, x+v.getWidth(), y+v.getHeight());

で良いそうです。


  • 標準のテーマが変わった

標準のテーマが変わったので変更前と同じテーマにするには、

初めにテーマをセットする必要がでてきました。

onCreate()の中で、

setTheme(android.R.style.Theme_Light);

とすると変更前のテーマと同じになります。

今の標準のテーマで作ろうとしたら、

サイズとかがうまくいかなかったので結局変更前と同じテーマを使いました。


  • AndroidManifest.xmlの記述が変わった

activityの辺りで若干変更がありました。

<activity class=".BlockTest" android:label="@string/app_name">

<intent-filter>

<action android:value="android.intent.action.MAIN" />

</intent-filter>

</activity>

これが、

<activity android:name=".BlockTest" android:label="@string/app_name">

<intent-filter>

<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />

</intent-filter>

</activity>

こうなります。


そんなわけで地味に変更する箇所が割とありました。一応動作画面、ソースも載せておきます。

動作画面

http://screencast.com/t/fP6gnshOn


ソース

package net.swelt.android.blocktest;

import android.app.Activity;
import android.app.AlertDialog;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.graphics.Rect;
import android.view.KeyEvent;
import android.view.View;
import android.widget.AbsoluteLayout;
import android.widget.AbsoluteLayout.LayoutParams;
import android.widget.Button;

public class BlockTest extends Activity {
	private final int FIELD_WIDTH = 320;
	private final int FIELD_HEIGHT = 200;
	
	private final int PADDLE_WIDTH = 100;
	private final int PADDLE_HEIGHT = 20;
	private final int PADDLE_VELOCITY = 10;
	
	private final int BALL_SIZE = 20;
	private final int BALL_VELOCITY = 5;
	
	private final int BLOCK_WIDTH = 50;
	private final int BLOCK_HEIGHT = 20;
	private final int BLOCK_NUM_X = 6;
	private final int BLOCK_NUM_Y = 3;
	
	private final int DIR_LB = 0;
	private final int DIR_LT = 1;
	private final int DIR_RT = 2;
	private final int DIR_RB = 3;
	
	private Button mPaddle; 
	private Button mBall;
	private Button[][] mBlocks;
	private int mBallDirection;	
	
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        setTheme(android.R.style.Theme_Light);
                
        final AbsoluteLayout al = new AbsoluteLayout(this);
        setContentView(al);
                
        mPaddle = new Button(this);
        mPaddle.setLayoutParams(new LayoutParams(
        		PADDLE_WIDTH, PADDLE_HEIGHT, (FIELD_WIDTH-PADDLE_WIDTH)/2, 160));
        al.addView(mPaddle);
        
        mPaddle.setOnClickListener(new View.OnClickListener() {
        	public void onClick(View v) {
        		restart();
        	}
        });
        
        mBall = new Button(this);
        mBall.setLayoutParams(new LayoutParams(
        		BALL_SIZE, BALL_SIZE, (FIELD_WIDTH-BALL_SIZE)/2, (FIELD_HEIGHT-BALL_SIZE)/2));
        al.addView(mBall);
        mBallDirection = DIR_LB;
        
        mBall.setOnClickListener(new View.OnClickListener() {
        	public void onClick(View v) {
        		new AlertDialog.Builder(BlockTest.this)
        			.setMessage("Congratulations!!")
        			.show();
        	}
        });
        
        mBlocks = new Button[BLOCK_NUM_X][BLOCK_NUM_Y];
        for( int i = 0; i < BLOCK_NUM_X; ++ i ) {
        	for( int j = 0; j < BLOCK_NUM_Y; ++ j ) {
        		final Button bt1 = new Button(this);
        		int x = 10 + i * BLOCK_WIDTH;
        		int y = 10 + j * BLOCK_HEIGHT;
        		bt1.setLayoutParams(new LayoutParams(BLOCK_WIDTH, BLOCK_HEIGHT, x, y));
        		al.addView(bt1);
        		bt1.setFocusable(false);
        		mBlocks[i][j] = bt1;
        	}
        }        
        
        update();
    }
    
    private void restart() {
    	moveWidget(mPaddle, (FIELD_WIDTH-PADDLE_WIDTH)/2, 160);
    	moveWidget(mBall, (FIELD_WIDTH-BALL_SIZE)/2, (FIELD_HEIGHT-BALL_SIZE)/2);
    	mBallDirection = DIR_LB;
    	
    	for( int i = 0; i < BLOCK_NUM_X; ++ i ) {
    		for( int j = 0; j < BLOCK_NUM_Y; ++ j ) {
    			mBlocks[i][j].setVisibility(View.VISIBLE);
    		}
    	}
    }
    
    private void moveWidget(View v, int x, int y) {
    	v.layout(x, y, x+v.getWidth(), y+v.getHeight());
    }       
    
    private void movePaddle(int vx) {
    	int x = mPaddle.getLeft() + vx;
    	if( isOverLeft(x) ) x = 0;
    	if( isOverRight(x+mPaddle.getWidth()) ) x = FIELD_WIDTH - mPaddle.getWidth();
    	moveWidget(mPaddle, x, mPaddle.getTop());    	
    }
    
    private void moveBall() {
    	int vx = BALL_VELOCITY, vy = BALL_VELOCITY;
    	switch( mBallDirection ) {
    	case DIR_LB: vx = -vx; break;
    	case DIR_LT: vx = -vx; vy = -vy; break;
    	case DIR_RT: vy = -vy; break;
    	case DIR_RB: break;
    	}    	
    	
    	int x = mBall.getLeft() + vx;
    	int y = mBall.getTop() + vy;
    	
    	if( isOverLeft(x) ) {
    		x = 0;
    		mBallDirection = vy < 0 ? DIR_RT : DIR_RB;
    	}
    	if( isOverRight(x+mBall.getWidth()) ) {
    		x = FIELD_WIDTH - mBall.getWidth();
    		mBallDirection = vy < 0 ? DIR_LT : DIR_LB;
    	}
    	if( isOverTop(y) ) {
    		y = 0;
    		mBallDirection = vx < 0 ? DIR_LB : DIR_RB;
    	}
    	if( isOverBottom(y+mBall.getHeight()) ) {
    		restart();
    		return;
    	}
    	
    	moveWidget(mBall, x, y);
    }
    
    private boolean isHit(View v1, View v2) {
    	Rect r1 = new Rect(v1.getLeft(), v1.getTop(), v1.getRight(), v1.getBottom());    	    	
    	return r1.intersect(v2.getLeft(), v2.getTop(), v2.getRight(), v2.getBottom());
    }
    
    private int getNextDirection(View v) {
    	int t = v.getTop();
    	int b = v.getBottom();
    	if( mBall.getTop() < t && mBall.getBottom() > t ) {    		
    		if( mBallDirection == DIR_LB ) return DIR_LT;
    		if( mBallDirection == DIR_RB ) return DIR_RT;
    	}
    	
    	if( mBall.getTop() < b && mBall.getBottom() > b ) {
    		if( mBallDirection == DIR_LT ) return DIR_LB;
    		if( mBallDirection == DIR_RT ) return DIR_RB;
    	}    	
    	
    	return DIR_LB;
    }
    
    private boolean isOverLeft(int x) { return x < 0; }
    private boolean isOverRight(int x) { return x > FIELD_WIDTH; }
    private boolean isOverTop(int y) { return y < 0; }
    private boolean isOverBottom(int y) { return y > FIELD_HEIGHT; }
        
  	@Override public boolean onKeyDown(int keyCode, KeyEvent event) {
		switch(keyCode) {
		case KeyEvent.KEYCODE_DPAD_LEFT:	
			movePaddle(-PADDLE_VELOCITY);			
			return true;
		case KeyEvent.KEYCODE_DPAD_RIGHT:
			movePaddle(PADDLE_VELOCITY);
			return true;
		}
		return super.onKeyDown(keyCode, event);
	}
  	
  	private void update() {
  		moveBall();
  		
  		if( isHit(mPaddle, mBall) ) {
  			mBallDirection = getNextDirection(mPaddle);
  		}
  		
  		for( int i = 0; i < BLOCK_NUM_X; ++ i ) {
  			for( int j = 0; j < BLOCK_NUM_Y; ++ j ) {
  				if( mBlocks[i][j].getVisibility() == View.INVISIBLE ) continue;
  				if( isHit(mBlocks[i][j], mBall) ) {
  					mBlocks[i][j].setVisibility(View.INVISIBLE);
  					mBallDirection = getNextDirection(mBlocks[i][j]);
  				}
  			}
  		}
  		
  		mRedrawHandler.sleep(100);
  	}
  	
	private RefreshHandler mRedrawHandler = new RefreshHandler();
	
	class RefreshHandler extends Handler {
		@Override public void handleMessage(Message msg) {
			update();
		}
		
		public void sleep(long delayMills) {
			this.removeMessages(0);
			sendMessageDelayed(obtainMessage(0), delayMills);
		}
	}
}

2008-02-17

[]Android SDK m5-rc14インストール完了! 01:42

WSTがないよ!とか、GEFがないよ!とか

色々怒られたけどなんとかインストール完了。


そして気づいた点がいくつか。

今までの小さい画面を想定して作ってたのでこれはつらい。

どのスキンにも対応できるよう作り直さないと。

ほんとはそこら辺考えながら作ってないとダメなんだろうけどねー。


うーん。前の方が良いと思う。


  • メソッドが増えたり減ったり!

今まで作ったアプリに軒並みエラーが出てます。

修正するだけでも一苦労。

なんというか、アプリコンテストギリギリにこれはつらいなあ。


と思ってたらコンテストの締め切り伸びてたっぽい。当初3月とかだったよね?

以下公式から。

Android Developer Challenge I: We will accept submissions from January 2 through April 14, 2008


なんとかまだ頑張る時間はありそう。


[]新しくなったAndroid SDK 00:28

いつの間にやらAndroid SDKがバージョンアップしたらしいてのを

chephesさんとこで知った。


うーん完全に遅れてるなあ。


やろうやろうと思っててもなかなかAndroidの方に手が付かず。

あと10日ぐらいでなんとか形にしないと!がんばる!!


そんなわけでまずはバージョンアップしよう。

結構変わってるみたいだ。

2007-12-27

[]Androidで透明ウィンドウつづき 18:12

f:id:s_welt:20071227180946p:image


昨日の透明ウィンドウはSurfaceViewを使ったやり方でなんともスマートでなかった。

そこで他のやり方を探してたらAPIDemosの中にまさに透明ウィンドウのサンプルを見つけた。

TranslucentとTranslucent Fancyてやつ。透明というか半透明だけど気にしない。


そこで早速ソースを見てみると普通の文字を表示させるソースとなんら変わりない。

試しにコピペしてみたけどやっぱりウィンドウは透明にならない。


そこで周辺のファイルを調べてみるとstyles.xmlとAndroidManifest.xmlに秘密があることがわかった。


styles.xml内でポイントとなるのは次の二行。

<drawable name="translucent_background">#44ff0000</drawable>

<item name="android:windowBackground">@drawable/translucent_background</item>

一行目で背景となるdrawableを作っている。色の指定は#aarrggbbで行っているので初め二つの値で透明度を指定している。

二行目では一行目で作ったdrawableを背景として設定している。

ここで直接色の指定をしたがダメだった。drawableで指定しないといけないみたい。

逆に言えばdrawableならなんでも登録できる。画像を背景に使いたい場合は便利だ。


次にAndroidManifest.xmlの話。

これは中を見てもらえばわかるけどアプリケーションの色々を設定するファイル。

透明ウィンドウを作るには次のがポイント。

<activity class=".TranslucentTest" android:label="@string/app_name" android:theme="@style/Theme.Transparent">

ここのandroid:themeにstyles.xml内で作った透明ウィンドウを指定すればよい。


ちなみにstyles.xmlって名前はただサンプルと合わせて

この名前にしただけなので違うファイル名でも平気なはずです。


そんなわけで昨日のやつを今回のやり方で作ったのが次のデモ。

半透明にして色を赤にしてみました。

SurfaceViewを使わないとソースがすごいスッキリするので透明にしたい場合はこっちの方がいいっぽい。


以下動作画面とソース。

動作画面

http://screencast.com/t/gqfzEnOi


ソース

TranslucentTest.java

package net.swelt.android.translucenttest;

import android.app.Activity;
import android.os.Bundle;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.view.MotionEvent;
import android.widget.AbsoluteLayout;

public class TranslucentTest extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        setContentView(new SampleView(this));
    }
    
    public class SampleView extends AbsoluteLayout {
    	private DisplayObject mKirby;
    	
    	public SampleView(Context context) {
    		super(context);
    		
    		mKirby = new DisplayObject(context);
    		this.addView(mKirby);
    		
    		Bitmap bmp = BitmapFactory.decodeResource(this.getContext().getResources(), R.drawable.kirby_t);
    		mKirby.setBitmap(bmp);
    		mKirby.move((320 - mKirby.getImageWidth())/2, (240 - mKirby.getImageHeight())/2);
    	}
    	    	
    	@Override
    	public void onDraw(Canvas canvas) {
    		mKirby.draw(canvas);
    	}
    	
    	@Override
    	public boolean onMotionEvent(MotionEvent event) {    		
    		switch( event.getAction() ) {
    		case MotionEvent.ACTION_DOWN:
    		case MotionEvent.ACTION_MOVE:
    			int x = (int)(event.getX() - mKirby.getImageWidth()/2);
    			int y = (int)(event.getY() - mKirby.getImageHeight()/2);
    			mKirby.move(x, y);    			
    			return true;
    		}
    		return true;
    	}
    }
}

styles.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
	
	<drawable name="translucent_background">#44ff0000</drawable>
	
	
	<style name="Theme" parent="android:Theme.Dark">
	</style>
		
	<style name="Theme.Transparent">
        <item name="android:windowBackground">@drawable/translucent_background</item>
        <item name="android:windowNoTitle">true</item>
	</style>
</resources>

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="net.swelt.android.translucenttest">
    <application android:icon="@drawable/icon">
        <activity class=".TranslucentTest"
        	android:label="@string/app_name"
        	android:theme="@style/Theme.Transparent">
        	
            <intent-filter>
                <action android:value="android.intent.action.MAIN" />
                <category android:value="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest> 

2007-12-26

[]Androidで透明ウィンドウ 01:28

f:id:s_welt:20071227012700p:image

kazhik.techさんのところで、

getWindow().setFormat(PixelFormat.TRANSLUCENT);

とすればウィンドウが半透明になるということを見た。


半透明な上に画像が表示できたら楽しいよね、

と思って試してみたらうまくいかない。

この設定を有効にするにはSurfaceViewを使わなくちゃいけないらしい。


そこでいろいろいじってたらやっと透明にできた。

これがSurfaceViewの正しい使い方かどうかはわからない(多分違う)けど、

なかなか面白いので良いです。


これで透明な部分はメイン画面を操作できたりしたら面白いんだけどな。

なんか出来る気がする。やりかたは見当つかないけども。


以下動作画面とソース。

動作画面

http://screencast.com/t/fOlko1zI3Tb


ソース

package net.swelt.android.surfaceviewtest;

import android.app.Activity;
import android.os.Bundle;
import android.content.Context;
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.view.SurfaceView;
import android.view.SurfaceHolder;
import android.view.Window;
import android.widget.AbsoluteLayout;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.PixelFormat;

public class SurfaceViewTest extends Activity {
	
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);        
        getWindow().setFormat(PixelFormat.TRANSPARENT);
        
        requestWindowFeature(Window.FEATURE_NO_TITLE);

        setContentView(new MySurfaceView(this));
        addContentView(new SampleView(this), new ViewGroup.LayoutParams(-1, -1));
    }
    
    public class SampleView extends AbsoluteLayout {
    	private DisplayObject mKirby;
    	
    	public SampleView(Context context) {
    		super(context);
    		
    		mKirby = new DisplayObject(context);
    		this.addView(mKirby);
    		
    		Bitmap bmp = BitmapFactory.decodeResource(this.getContext().getResources(), R.drawable.kirby_t);
    		mKirby.setBitmap(bmp);
    		mKirby.move((320 - mKirby.getImageWidth())/2, (240 - mKirby.getImageHeight())/2);
    	}
    	    	
    	@Override
    	public void onDraw(Canvas canvas) {
    		mKirby.draw(canvas);
    	}
    	
    	@Override
    	public boolean onMotionEvent(MotionEvent event) {    		
    		switch( event.getAction() ) {
    		case MotionEvent.ACTION_DOWN:
    		case MotionEvent.ACTION_MOVE:
    			int x = (int)(event.getX() - mKirby.getImageWidth()/2);
    			int y = (int)(event.getY() - mKirby.getImageHeight()/2);
    			mKirby.move(x, y);    			
    			return true;
    		}
    		return true;
    	}
    }
    
    public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback {
    	private SurfaceHolder mHolder;
    	
    	public MySurfaceView(Context context) {
    		super(context);
    		
    		mHolder = getHolder();
    		mHolder.setCallback(this);
    	}
    	
    	public boolean surfaceCreated(SurfaceHolder holder) {
    		return true;
    	}
    	
    	public void surfaceDestroyed(SurfaceHolder holder) {
    	}
    	
    	public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
    	}
    }
}

2007-12-17

[]AndroidとFlickrで神経衰弱 00:44

f:id:s_welt:20071217004229p:image

息抜きがてら、前にActionScriptで作った神経衰弱をAndroidで作ってみた。

神経衰弱と言っても普通のではなく、絵柄の代わりにFlickrのタグを使います。

つまり2つの画像のタグに共通するタグがあれば当たり、なければはずれです。


といってもよくわからないので先に動作画面見るのがいいかも。

HitTagのとこに表示されるのが共通のタグです。ない場合は"NONE"が表示される。


で、作るときに一番悩んだのはどのカードがクリックされたかを取得する方法。

ActionScriptでやってたときはイベントリスナーが使えたから楽だったけど、

Android、というかJavaにはそういうのがないみたいで苦労した。


結局下のソースのようにフォーカス移動時のメソッドを設定した。

onCardClickメソッドは本体側の関数。

card.setOnFocusChangeListener(new View.OnFocusChangeListener() {

public void onFocusChanged(View v, boolean hasFocus) {

onCardClick();

}

});

カードがクリックされたときはrequestFocusメソッドでそのカードにフォーカスを与え

本体側のonCardClickメソッドを呼び出してる。


なんともスマートでないやり方なのであんまり気に入ってないんですが。

こういう場合どうするのが良いんだろうか。


あと画像の読み込みに時間がかかる。

これはスレッドとかを使うべき?スレッドいまいち理解してないけども。

そのうちGridViewを使ったのとかも作るかも。


以下、動作画面とソース。

動作画面

http://screencast.com/t/57uKyW3l8IL

ソース

ShinkeiTest.java

package net.swelt.android.shinkeitest;

import android.app.Activity;
import android.os.Bundle;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.view.View;
import android.widget.AbsoluteLayout;
import android.widget.Button;
import android.widget.TextView;

import java.util.Vector;

import java.net.*;
import java.io.*;

public class ShinkeiTest extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        setContentView(new ShinkeiView(this));
    }
    
    public class ShinkeiView extends AbsoluteLayout {
    	private final int CARD_MAX = 6;
    	private final String DEFAULT_TAG = "cat";
    	
    	private Button mStartButton;
    	private TextView mStateText;
    	private TextView mInfoText;
    	private Vector<Card> mCards = new Vector<Card>();
    	private int[] mTestCards = new int[] {-1, -1};
    	
    	public ShinkeiView(Context context) {
    		super(context);
    		
    		mStartButton = new Button(context);
    		mStartButton.setLayoutParams(new AbsoluteLayout.LayoutParams(-2, -2, 0, 0));
    		this.addView(mStartButton);
    		mStartButton.setText("Start");
    		mStartButton.setOnClickListener(new View.OnClickListener() {
    			public void onClick(View v) {
    				loadImages(CARD_MAX);
    			}
    		});
    		
    		mStateText = new TextView(context);
    		mStateText.setLayoutParams(new AbsoluteLayout.LayoutParams(-2, -2, 60, 0));
    		this.addView(mStateText);
    		mStateText.setText("HitTag : NONE");
    		
    		mInfoText = new TextView(context);
    		mInfoText.setLayoutParams(new AbsoluteLayout.LayoutParams(-2, -2, 0, 0));
    		this.addView(mInfoText);
    	    		
    		invalidate();
    	}
    	    	
    	private void loadImages(int num) {
    		Card card;
    		ImageInfo info;
    		String url, nextTag = DEFAULT_TAG;
    		Bitmap bmp;
    	
    		mCards.clear();
    		mTestCards[0] = -1;
    		mTestCards[1] = -1;
    		for( int i = 0; i < num; ++ i ) {    			
    			info = loadImageInfo(nextTag);
    			if( info == null ) {
    				continue;
    			}

    			bmp = loadImage(info.getImageURL());
    			if( bmp == null ) {
    				continue;
    			}
    			
    			card = new Card(this.getContext());
    			mCards.add(card);
    			this.addView(card);
    			card.setImageInfo(info);
    			card.setBitmap(bmp);
    			card.move((i%3)*100 + 20, (i/3)*80 + 30);
    			card.setOnFocusChangeListener(new View.OnFocusChangeListener() {
    				public void onFocusChanged(View v, boolean hasFocus) {
    					onCardClick();
    				}
    			});
    			
    			nextTag = info.getRandomTag();
    			if( nextTag.equals("") ) nextTag = DEFAULT_TAG;
    		}
    		invalidate();
    	}

    	private Bitmap loadImage(String str) {
    		Bitmap bmp = null;
    		try {
    			URL url = new URL(str);
    			HttpURLConnection http = (HttpURLConnection) url.openConnection();
    			http.setRequestMethod("GET");
    			http.connect();
    			InputStream is = http.getInputStream();
    			bmp = BitmapFactory.decodeStream(is);
    			if( bmp.width() == 0 || bmp.height() == 0 ) bmp = null;
    			is.close();
    		} catch(Exception e) {
    		}
    		return bmp;
    	}
    	
    	private ImageInfo loadImageInfo(String tags) {
    		ImageInfo info = null;
    		try {
    			URL url = new URL("http://(プライベートIPアドレス)/loadimageinfo.php?tags="+tags);
    			HttpURLConnection http = (HttpURLConnection) url.openConnection();
    			http.setRequestMethod("GET");
    			http.connect();
    			InputStream is = http.getInputStream();
    			String str = "";
    			int c;
    			while( (c = is.read()) != -1 ) str = str.concat(""+(char)c);
    			info = new ImageInfo();
    			info.convertToInfo(str);
    			is.close();
    		} catch(Exception e) {
    		}
    		return info;
    	}
    	
    	private void onCardClick() {
    		for( int i = 0; i < mCards.size(); ++ i ) {
    			
    			if( mCards.elementAt(i).isClick() ) {		
    				int index = mTestCards[0] == -1 ? 0 : 1;
    				mTestCards[index] = i;
    				mCards.elementAt(i).setScale(0.6f);
    				
    				if( index == 1 ) {
    					if( mTestCards[0] == mTestCards[1] ) {
    						mTestCards[1] = -1;
    						continue;
    					}
    					
    					String tag = tagTest(mTestCards[0], mTestCards[1]);
    					mStateText.setText("HitTag : " + (tag.equals("") ? "NONE" : tag));
    					mTestCards[0] = -1;
    					mTestCards[1] = -1;
    				} else {
    					for( int j = 0; j < mCards.size(); ++ j ) {
    						if( i == j ) continue;
    						mCards.elementAt(j).setScale(1.0f);
    					}
    				}
    			}
    		}
    		invalidate();
    	}
    	
    	private String tagTest(int index1, int index2) {
    		Object[] tags1 = mCards.elementAt(index1).getTags();
    		Object[] tags2 = mCards.elementAt(index2).getTags();
    		if( tags1 == null || tags2 == null ) return "";
    		
    		for( int i = 0; i < tags1.length; ++ i ) {
    			String tag1 = (String) tags1[i];
    			for( int j = 0; j < tags2.length; ++ j ) {
    				if( tags2[j].equals(tag1) ) return tag1;
    			}
    		}
    		
    		return "";
    	}
    	
    	@Override
    	public void onDraw(Canvas canvas) {
    		for( int i = 0; i < mCards.size(); ++ i ) {
    			mCards.elementAt(i).draw(canvas);
    		}
    	}
    }
}

DisplayObject.java

package net.swelt.android.shinkeitest;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.view.View;
import android.widget.AbsoluteLayout;

public class DisplayObject extends View {
	private Bitmap mBitmap = null;
	private Rect mSrcRect = null;
	private Rect mDstRect = null;
	
	public DisplayObject(Context context) {
		super(context);
		this.setLayoutParams(new AbsoluteLayout.LayoutParams(-2, -2, 0, 0));
	}
	
	public void setBitmap(Bitmap bmp) {
		if( bmp == null ) return;
		mBitmap = bmp;
		mSrcRect = new Rect(0, 0, mBitmap.width(), mBitmap.height());
		mDstRect = new Rect(mSrcRect);
	}
		
	public void move(int x, int y ) {
		((AbsoluteLayout.LayoutParams) this.getLayoutParams()).x = x;
		((AbsoluteLayout.LayoutParams) this.getLayoutParams()).y = y;
	}
	
	public int getImageWidth() {
		return mBitmap == null ? 0 : mDstRect.width();
	}
	
	public int getImageHeight() {
		return mBitmap == null ? 0 : mDstRect.height();
	}
	
	public void setScale(float scale) {
		if( scale == 1.0f ) {
			mDstRect.set(mSrcRect);
		} else {
			mDstRect.right = (int) (mSrcRect.right * scale);
			mDstRect.bottom = (int) (mSrcRect.bottom * scale);
		}
	}
	
	@Override
	public void onDraw(Canvas canvas) {
		if( mBitmap == null ) return;
		canvas.drawBitmap(mBitmap, mSrcRect, mDstRect, null);
	}
}

Card.java

package net.swelt.android.shinkeitest;

import android.content.Context;
import android.view.MotionEvent;

public class Card extends DisplayObject {
	private ImageInfo mInfo;
	private boolean mClickFlag = false;
	
	public Card(Context context) {
		super(context);
		this.setFocusable(true);
	}
		
	public void setImageInfo(ImageInfo info) {
		if( info == null ) return;
		mInfo = info;
	}
	
	public boolean isClick() {
		boolean b = mClickFlag;
		mClickFlag = false;
		return b;
	}
	
	public Object[] getTags() {
		return mInfo == null ? null : mInfo.getTags();
	}	

	@Override
	public boolean onMotionEvent(MotionEvent event) {    			
		switch( event.getAction() ) {
		case MotionEvent.ACTION_DOWN:
			mClickFlag = true;
			this.requestFocus();
			return true;
		}
		return super.onMotionEvent(event);
	}
}

ImageInfo.java

package net.swelt.android.shinkeitest;

import java.util.Random;
import java.util.StringTokenizer;
import java.util.Vector;

public class ImageInfo {
	private String imageURL;
	private Vector<String> tags;
	
	public void convertToInfo(String str) {		
		StringTokenizer strToken = new StringTokenizer(str, "&");
		
		while( strToken.hasMoreTokens() ) {    				
			String token = strToken.nextToken();
			String key = token.substring(0, token.indexOf("="));
			
			if( key.equals("imageURL")) imageURL = token.substring(token.indexOf("=")+1);
			else if( key.equals("tags") ) {
				tags = new Vector<String>();
				token = token.substring(token.indexOf("=")+1);
				StringTokenizer tagsToken = new StringTokenizer(token, ",");
				while( tagsToken.hasMoreTokens() ) {
					tags.add(tagsToken.nextToken());
				}
			}
		}
	}
	
	public String getImageURL() { return imageURL; };
	public Object[] getTags() { return tags.toArray(); };
	public String getRandomTag() {
		Random rand = new Random();
		return tags.size() == 0 ? "" : tags.elementAt(rand.nextInt(tags.size()));
	}
}

loadimageinfo.php

<?php
	include('xml.php');
	$key = "*****";
	$page = 1;
	$per_page = 100;
	$tags = isset($_GET['tags']) ? $_GET['tags'] : "cat";
	
	$url = "http://www.flickr.com/services/rest?";
	$url .= "method=flickr.photos.search";
	$url .= "&api_key=".$key;
	$url .= "&page=".$page;
	$url .= "&per_page=".$per_page;
	$url .= "&tags=".$tags;
	
	$xml = file_get_contents($url);
	$data = XML_unserialize($xml);
	$photo = $data['rsp']['photos']['photo'];

	$index = rand(0, $per_page);
	$id = $photo[$index.' attr']['id'];
	$server = $photo[$index.' attr']['server'];
	$secret = $photo[$index.' attr']['secret'];
	$imageURL = "http://static.flickr.com/".$server."/".$id."_".$secret."_s.jpg";

	$url = "http://www.flickr.com/services/rest?";
	$url .= "method=flickr.photos.getInfo";
	$url .= "&api_key=".$key;
	$url .= "&photo_id=".$id;
	$url .= "&secret=".$secret;

	$xml = file_get_contents($url);
	$data = XML_unserialize($xml);
	$tags = $data['rsp']['photo']['tags']['tag'];

	$tag = "";
	for( $i = 0; $i < count($tags) / 2; ++ $i ) {
		$tag .= $tags[$i];
		if( $i != count($tags) / 2 - 1 ) {
			$tag .= ",";
		}
	}

	echo "imageURL=".$imageURL;
	echo "&tags=".$tag;
?>