
Androidの勉強中。
Flickr検索アプリを作ってみた。
だいぶ手探りだったけどとりあえず動いてよかった。
簡単な流れの説明。
まずFlickrAPIにアクセスして画像のリストを取得。
それから画像までのURLを作成し、複数のスレッドを作ってまとめて画像をロードするように作ってます。
AndroidのUIはシングルスレッドなので、画像読み込みなどのバックグラウンド処理は別スレッドでやらないと画面が固まっちゃうので注意。
AsyncTaskってのを使うとそこら辺がうまく出来るらしいけど、今回はとりあえず自分でスレッドを作りました。
以下、今回のソースです。
全部で3つあって、
- MainActivity.java : 検索画面を表示するActivity
- ImageListActivity : 画像の一覧を表示するActivity
- ImageDrawActivity : 選択した画像を表示するActivity
って感じになってます。
ソースコードをまとめたものも置いておきます。
とりあえず動くものが出来てよかったです。
「ここはこうした方がいい」みたいなの見つけた方がいたら、ぜひ教えてください。
MainActivity.java。
package jp.sakef;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.os.Bundle;
import android.text.InputType;
import android.view.Gravity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
public class MainActivity extends Activity implements OnClickListener
{
private final static int FILL = LinearLayout.LayoutParams.FILL_PARENT;
private final static int WRAP = LinearLayout.LayoutParams.WRAP_CONTENT;
private final static int PARAM_ID = 77;
private EditText text;
private LinearLayout layout;
private Button btn;
// Activity作成時に実行
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
layout=new LinearLayout(this);
layout.setOrientation(LinearLayout.VERTICAL);
layout.setGravity(Gravity.CENTER_HORIZONTAL);
layout.setPadding(10, 10, 10, 10);
setContentView(layout);
text = new EditText(this);
layout.addView(text, new LinearLayout.LayoutParams(FILL,WRAP));
text.setHeight(10);
text.setInputType(InputType.TYPE_CLASS_TEXT);
btn = new Button(this);
layout.addView(btn, new LinearLayout.LayoutParams(WRAP, WRAP));
btn.setWidth(200);
btn.setText("Search");
btn.setOnClickListener(this);
}
// クリック時に実行
public void onClick(View v)
{
String query = text.getText().toString();
if(!query.equals(""))
{
Intent intent=new Intent(this, jp.sakef.ImageListActivity.class);
intent.putExtra("query", query);
startActivityForResult(intent, PARAM_ID);
}
else
{
text.setError("Please input query");
}
}
// このActivityに戻ってきた場合に実行
public void onActivityResult(int requestCode,int resultCode, Intent intent)
{
if (requestCode==PARAM_ID && resultCode==RESULT_OK)
{
text.setText("");
}
}
// Activity破棄時に実行
public void onDestroy()
{
super.onDestroy();
layout.removeAllViews();
btn.setOnClickListener(null);
text = null;
btn = null;
layout = null;
}
// Activityの再開時に実行
public void onRestart()
{
super.onRestart();
btn.setOnClickListener(this);
layout.setEnabled(true);
btn.setEnabled(true);
text.setEnabled(true);
}
// Activityの停止時に実行
public void onStop()
{
super.onStop();
btn.setOnClickListener(null);
layout.setEnabled(false);
btn.setEnabled(false);
text.setEnabled(false);
}
}
ImageListActivity.java。
package jp.sakef.FlickrSearch;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserFactory;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.Display;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.LinearLayout;
public class ImageListActivity extends Activity implements OnClickListener
{
// 諸々の定数
private final static int FILL = LinearLayout.LayoutParams.FILL_PARENT;
private final static int WRAP = LinearLayout.LayoutParams.WRAP_CONTENT;
private final static int PARAM_ID = 88;
private final static String NUM_IMG = "8";
private final static int NUM_IMG_W = 4;
private final static int NUM_IMG_H = 2;
// 検索用クエリ・ImageViewを保持するリスト・画像数
private String query;
private ArrayList<ImageView> imageViewList;
private ArrayList<Bitmap> bitmapList;
private int numImages;
// その他変数
private int imageViewWidth;
private int imageViewHeight;
private AlertDialog alert;
private Handler handler;
private LoadingView view;
private LinearLayout layout;
private controlThread cThread;
private ParallelLoaderThread pThread;
// Activity作成時に実行
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
setResult(Activity.RESULT_CANCELED);
// パラメータ取得
Bundle params = getIntent().getExtras();
if (params!=null) query=params.getString("query");
// ImageViewのサイズを取得
WindowManager windowmanager = (WindowManager)getSystemService(WINDOW_SERVICE);
Display disp = windowmanager.getDefaultDisplay();
imageViewWidth = disp.getWidth()/NUM_IMG_W;
imageViewHeight = (disp.getHeight()-80)/NUM_IMG_H;
// 画像がない場合に表示するアラートの作成
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this);
alertDialogBuilder.setTitle("Image not found.");
alertDialogBuilder.setMessage("We cannot find image on tag you choosed.");
alertDialogBuilder.setPositiveButton("OK", new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int which)
{
Intent intent = new Intent();
setResult(Activity.RESULT_OK, intent);
finish();
}
});
alert = alertDialogBuilder.create();
// layout
layout=new LinearLayout(this);
layout.setOrientation(LinearLayout.VERTICAL);
setContentView(layout);
// その他
handler = new Handler();
imageViewList = new ArrayList<ImageView>();
bitmapList = new ArrayList<Bitmap>();
numImages = 0;
view = new LoadingView(this);
// Viewの追加
layout.addView(view, new LinearLayout.LayoutParams(FILL, FILL));
// 画像のロードを開始
cThread = new controlThread();
pThread = new ParallelLoaderThread();
cThread.start();
pThread.start();
}
// Activityの破棄時に実行
public void onDestroy()
{
super.onDestroy();
layout.removeAllViews();
imageViewList.clear();
bitmapList.clear();
if(pThread != null)
{
pThread.interrupt();
pThread = null;
}
if(cThread != null)
{
cThread.interrupt();
cThread = null;
}
alert = null;
handler = null;
imageViewList = null;
bitmapList = null;
view = null;
layout = null;
numImages = 0;
}
// Activityの再開時に実行
public void onRestart()
{
super.onRestart();
layout.setEnabled(true);
if(view != null) view.setEnabled(true);
ImageView v;
for(int i=0 ; i<numImages ; i++)
{
v = imageViewList.get(i);
v.setOnClickListener(this);
v.setEnabled(true);
}
}
// Activityの停止時に実行
public void onStop()
{
super.onStop();
layout.setEnabled(false);
if(view != null) view.setEnabled(false);
ImageView v;
for(int i=0 ; i<numImages ; i++)
{
v = imageViewList.get(i);
v.setOnClickListener(null);
v.setEnabled(false);
}
}
// クリック時に実行
public void onClick(View v)
{
for(int i=0 ; i<numImages ; i++)
{
if(v == imageViewList.get(i))
{
Bitmap bmp = bitmapList.get(i);
Intent intent=new Intent(this, jp.sakef.FlickrSearch.ImageDrawActivity.class);
intent.putExtra("image", bmp);
startActivityForResult(intent, PARAM_ID);
break;
}
}
}
// ImageViewを追加
public void setImageView(int i, LinearLayout sub)
{
ImageView v = imageViewList.get(i);
v.setOnClickListener(this);
sub.addView(v, new LinearLayout.LayoutParams(imageViewWidth, imageViewHeight));
}
// スレッド内からArrayListにデータをセット
public synchronized void setImageAndView(Bitmap bitmap)
{
ImageView v = new ImageView(this);
v.setImageBitmap(bitmap);
v.setPadding(5,0,5,0);
v.setScaleType(ImageView.ScaleType.FIT_XY);
bitmapList.add(bitmap);
imageViewList.add(v);
numImages = imageViewList.size();
}
// 入れ子用のLinearLayoutを作成
public LinearLayout createSubLayout()
{
LinearLayout l = new LinearLayout(this);
l.setOrientation(LinearLayout.HORIZONTAL);
l.setPadding(0,5,0,5);
layout.addView(l, new LinearLayout.LayoutParams(WRAP, WRAP));
return l;
}
// ロードをコントロールするスレッド
public class controlThread extends Thread
{
public controlThread(){}
public void run()
{
while(true)
{
if(view != null) view.drawLoading();
if(pThread != null)
{
if(!pThread.isAlive())
{
handler.post(new Runnable()
{
public void run()
{
layout.removeView(view);
if(numImages == 0)alert.show();
else
{
LinearLayout sub=null;
for(int i=0 ; i<numImages ; i++)
{
if(i%NUM_IMG_W==0) sub = createSubLayout();
setImageView(i, sub);
}
}
}
});
break;
}
}
}
}
}
// ローディングを表示するView
public class LoadingView extends SurfaceView implements SurfaceHolder.Callback
{
private SurfaceHolder sholder;
private Paint paint;
private float width;
private float height;
private float theta;
private boolean enable=false;
public LoadingView(Context c)
{
super(c);
getHolder().addCallback(this);
}
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h)
{
width = w;
height = h;
enable = true;
}
public void surfaceDestroyed(SurfaceHolder holder)
{
sholder.addCallback(null);
sholder = null;
paint = null;
enable = false;
}
public void surfaceCreated(SurfaceHolder holder)
{
sholder = holder;
paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.WHITE);
theta = 0.0f;
}
public void drawLoading()
{
if(enable)
{
float x = (float) (50 * Math.cos(theta) + width*0.5);
float y = (float) (50 * Math.sin(theta) + height*0.5);
theta += 30*Math.PI/180.0;
Canvas canvas = sholder.lockCanvas();
if(canvas != null)
{
canvas.drawColor(Color.argb(100, 0, 0, 0));
canvas.drawCircle(x, y, 10, paint);
sholder.unlockCanvasAndPost(canvas);
}
}
}
}
// 複数の画像をFlickrからロードするためのスレッド
public class ParallelLoaderThread extends Thread
{
public ParallelLoaderThread(){}
public void run()
{
ArrayList<ImageLoadThread> threadList = new ArrayList<ImageLoadThread>();
String result = "";
try
{
// API keyは適当
String API_KEY="api_key=0000000000000000000000000000000";
String API_URL="http://api.flickr.com/services/rest/?method=flickr.photos.search";
String API_TAG="tags="+query;
String PER_PAGE="per_page=" + NUM_IMG;
URL url = new URL(API_URL + "&" + API_KEY + "&" + API_TAG + "&" + PER_PAGE);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setRequestMethod("POST");
conn.setDoInput(true);
conn.setUseCaches(false);
conn.connect();
InputStream in = new BufferedInputStream(conn.getInputStream());
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
final XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
final XmlPullParser parser = factory.newPullParser();
parser.setInput(reader);
for(int type = parser.getEventType(); type != XmlPullParser.END_DOCUMENT; type = parser.next())
{
if(type == XmlPullParser.START_TAG)
{
String tagName = parser.getName();
if(tagName.equals("rsp")) result = parser.getAttributeValue(0);
else if (result.equals("ok") && tagName.equals("photo"))
{
String id = parser.getAttributeValue(0);
String secret = parser.getAttributeValue(2);
String server = parser.getAttributeValue(3);
String farm = parser.getAttributeValue(4);
String img = "http://farm" + farm + ".static.flickr.com/" + server + "/" + id + "_" + secret + "_m.jpg";
threadList.add(new ImageLoadThread(img));
}
}
}
reader.close();
in.close();
conn.disconnect();
int size = threadList.size();
if(size != 0)
{
for(int i= 0; i<size ; i++) threadList.get(i).start();
for(int i= 0; i<size ; i++) threadList.get(i).join();
}
threadList.clear();
}
catch(Exception e)
{
Log.d("Program Error", "ParallelLoaderThread Error : " + e.getMessage());
}
}
}
// 画像を読み込むためのスレッド
public class ImageLoadThread extends Thread
{
private String imgPath;
public ImageLoadThread(String path)
{
imgPath = path;
}
public void run()
{
try
{
URL imgUrl = new URL(imgPath);
HttpURLConnection imgConn = (HttpURLConnection) imgUrl.openConnection();
imgConn.setDoInput(true);
imgConn.connect();
InputStream imgIn = imgConn.getInputStream();
ByteArrayOutputStream imgOut = new ByteArrayOutputStream();
byte[] w=new byte[1024];
while (true)
{
int ss=imgIn.read(w);
if (ss<=0) break;
imgOut.write(w,0,ss);
};
Bitmap bitmap = BitmapFactory.decodeByteArray(imgOut.toByteArray(), 0, imgOut.size());
setImageAndView(bitmap);
imgOut.close();
imgIn.close();
imgConn.disconnect();
}
catch(Exception e)
{
Log.d("Program Error", "ImageLoadThread Error : " + e.getMessage());
}
}
}
}
ImageDrawActivity.java。
package jp.sakef;
import android.app.Activity;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.os.Bundle;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class ImageDrawActivity extends Activity
{
private Bitmap bitmap;
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
setResult(Activity.RESULT_CANCELED);
// パラメータ取得
Bundle b =getIntent().getExtras();
bitmap = (Bitmap) b.get("image");
if(bitmap != null) setContentView(new ImageView(this));
}
// Activity破棄時に実行
public void onDestroy()
{
super.onDestroy();
bitmap = null;
}
// 画像を表示するView
public class ImageView extends SurfaceView implements SurfaceHolder.Callback
{
public ImageView(Context c)
{
super(c);
getHolder().addCallback(this);
}
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h)
{
Canvas canvas = holder.lockCanvas();
float scale = (float)w/(float)bitmap.getWidth();
canvas.scale(scale, scale);
canvas.drawBitmap(bitmap,0,0, null);
holder.unlockCanvasAndPost(canvas);
}
public void surfaceDestroyed(SurfaceHolder holder)
{
holder.addCallback(null);
}
public void surfaceCreated(SurfaceHolder holder){}
}
}