AndroidとFlickrで神経衰弱

息抜きがてら、前に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を使ったのとかも作るかも。


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

ソース

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;
?>