Android画像付きリストの設定(ListView)

Android画像付きリストの設定(ListView)

Androidでの開発で、リストビューに項目毎に画像を置くケースでの対応です。
メモリ使用量を抑える為、スクロールさせて実際に必要になったときだけ表示する対処を行う。

● 読込中と読込完了時の設定
AsyncTaskでバックグラウンド処理を行い、読込中の場合はプログレスバーを表示させる。

● 画像データのキャッシュの使用
AsyncTaskで一度読み込んだ画像をキャッシュに保持し、スクロールにより再表示する時にwebからの読込を省略する事で負荷を軽減する。

● 画像データのキャッシュのクリア
キャッシュを保持し続けるとメモリ使用量が増える為、リスト画面終了時にキャッシュをクリアする処理を呼び出す。
list

以上の事を踏まえたソースコードを以下に示します。

画面レイアウト設定用XMLファイル

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:padding="0dp"
android:layout_margin="0dp"
>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/TextTitleMain"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="12dp"
android:textStyle="bold"
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp"
android:layout_marginLeft="4dp"
android:text="スタンプ選択 - 定型文"
/>
</LinearLayout>
<ListView
android:id="@+id/listGroup"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_gravity="fill"
android:background="#ddffffff"
android:padding="0dp"
android:layout_margin="0dp"
>
</ListView>
</LinearLayout>

リストレイアウト設定用XMLファイル

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
<!-- 非表示項目(選択時に呼び出し元に渡す画像URL) -->
<TextView
android:id="@+id/TextUrl"
android:layout_width="0dp"
android:layout_height="0dp"
android:text="a"
android:visibility="gone"
/>
<LinearLayout
android:id="@+id/viewWait"
android:layout_width="fill_parent"
android:layout_height="60dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:gravity="center"
>
<!-- リスト画像読込中に表示するプログレスバー -->
<ProgressBar
android:id="@+id/WaitBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleSmallInverse"
android:layout_gravity="center_vertical|center_horizontal"
/>
<!-- リスト画像 -->
<ImageView
android:id="@+id/ImageThumb"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:visibility="gone"
/>
</LinearLayout>
</LinearLayout>

リスト表示設定
Activity内にSimpleAdapter 継承クラスを作成する
画像の情報がURLなどの文字情報からデータを取得して表示するようにカスタマイズする必要がある。その時に呼び出されるメソッドがgetViewメソッドで、新しいデータが表示されるタイミングで呼び出される。ListViewなら、スクロールして、画面外から新しいデータが表示されるタイミングである。

private class ListTemplateAdapter extends SimpleAdapter {
private Context context;
private LayoutInflater inflater;
private ArrayList<Map<String, Object>> items;

//コンストラクタ
public ListTemplateAdapter(Context context, List<? extends Map<String, ?>> data, int resource, String[] from, int[] to) {
super(context, data, resource, from, to);
this.context = context;
this.inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}

private void setListData(ArrayList<Map<String, Object>> data){
//データ内容を保持しておく
items = data;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {

View v = convertView;
if(v == null){
v = inflater.inflate(R.layout.listview_template, null);
}

Map<String, Object> settingData = items.get(position);

TextView textUrl = (TextView)v.findViewById(R.id.TextUrl);
ImageView imageView = (ImageView)v.findViewById(R.id.ImageThumb);
ProgressBar waitBar = (ProgressBar)v.findViewById(R.id.WaitBar);

//画像を隠し、プログレスバーを表示
waitBar.setVisibility(View.VISIBLE);
imageView.setVisibility(View.GONE);

//実際に使用する画像のURLを保持
textUrl.setText(settingData.get("url").toString());
//リスト表示用画像のURLを取得
String thumbUrl = settingData.get("viewImage").toString();

//仮の画像設定
imageView.setImageDrawable(context.getResources().getDrawable(R.drawable.blank));

//画像読込
try{
imageView.setTag(thumbUrl);
// AsyncTaskは1回しか実行できない為、毎回インスタンスを生成
ImageGetTask task = new ImageGetTask(imageView, waitBar);
task.execute(thumbUrl);
}
catch(Exception e){
imageView.setImageDrawable(context.getResources().getDrawable(R.drawable.x));
waitBar.setVisibility(View.GONE);
imageView.setVisibility(View.VISIBLE);
}

return v;
}

class ImageGetTask extends AsyncTask<String,Void,Bitmap> {
//以下に記載
}
}

Image取得用スレッドクラス(AsyncTask)
SimpleAdapter継承クラス内に作成
AsyncTaskによりバックグラウンド処理で画像データの取得をし、
画像以外は先に表示しておく。

class ImageGetTask extends AsyncTask<String,Void,Bitmap> {
private ImageView image;
private ProgressBar progress;
private String tag;

public ImageGetTask(ImageView _image, ProgressBar _progress) {
//対象の項目を保持しておく
image = _image;
progress = _progress;
tag = image.getTag().toString();
}

@Override
protected Bitmap doInBackground(String... params) {
// ここでHttp経由で画像を取得します。取得後Bitmapで返します。
synchronized (context){
try {
//キャッシュより画像データを取得
Bitmap image = ImageCache.getImage(params[0]);
if (image == null) {
//キャッシュにデータが存在しない場合はwebより画像データを取得
URL imageUrl = new URL(params[0]);
InputStream imageIs;
imageIs = imageUrl.openStream();
image = BitmapFactory.decodeStream(imageIs);
//取得した画像データをキャッシュに保持
ImageCache.setImage(params[0], image);
}
return image;
} catch (MalformedURLException e) {
return null;
} catch (IOException e) {
return null;
}
}
}
@Override
protected void onPostExecute(Bitmap result) {
// Tagが同じものか確認して、同じであれば画像を設定する
// (Tagの設定をしないと別の行に画像が表示されてしまう)
if(tag.equals(image.getTag())){
if(result!=null){
//画像の設定
image.setImageBitmap(result);
}
else{
//エラーの場合は×印を表示
image.setImageDrawable(context.getResources().getDrawable(R.drawable.x));
}
//プログレスバーを隠し、取得した画像を表示
progress.setVisibility(View.GONE);
image.setVisibility(View.VISIBLE);
}
}
}

画像データのキャッシュ
リスト表示用画像はwebから読み込む為、
一度読み込んだ画像をスクロールしてもう一度表示させるときに
再度web接続をするという非効率な操作となってしまう。
その為、キャッシュに一度読み込んだ画像を保持し、
スクロール操作での画像表示の効率化を行う。

public class ImageCache {
private static HashMap<String,Bitmap> cache = new HashMap<String,Bitmap>();

//キャッシュより画像データを取得
public static Bitmap getImage(String key) {
if (cache.containsKey(key)) {
return cache.get(key);
}
//存在しない場合はNULLを返す
return null;
}

//キャッシュに画像データを設定
public static void setImage(String key, Bitmap image) {
cache.put(key, image);
}

//キャッシュの初期化(リスト選択終了時に呼び出し、キャッシュで使用していたメモリを解放する)
public static void clearCache(){
cache = null;
cache = new HashMap<String,Bitmap>();
}
}

初めはキャッシュのクリア(clearCache)を設置していなかったが、
多くのグループのリストを見ているうちに落ちてしまった為、
リスト画面の終了時にキャッシュをクリアするようにした。

Activityの設定

ArrayList<Map<String, Object>> data;
ListTemplateAdapter adapter = null; //独自のアダプタを作成

String[] from_template = {"url","viewImage"};
int[] to_template = {R.id.TextUrl, R.id.ImageThumb};

//画面のリストビュー
ListView list_group = (ListView) Activity.findViewById(R.id.listGroup);

//テンプレートのリスト取得
data = new ArrayList<Map<String, Object>>();
Map<String, Object> map;

//データの設定(仮・データ件数毎の設定)
map = new HashMap<String, Object>();
map.put("url", <呼び出し元に渡す画像のURL>);
map.put("viewImage", <リスト表示用画像のURL>);
data.add(map);

adapter = new ListTemplateAdapter(Activity, data, R.layout.listview_template, from_template, to_template);

if(data != null && data.size() > 0){
adapter.setListData(data); //テンプレートにデータ内容を保持

list_group.setFocusable(false);
list_group.setAdapter(adapter);
list_group.setScrollingCacheEnabled(false);
list_group.setTextFilterEnabled(true);

list_group.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
//リスト選択時の処理

ImageCache.clearCache(); //キャッシュのクリア

//終了処理
}
});

list_group.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
//リスト選択時の処理

ImageCache.clearCache(); //キャッシュのクリア

//終了処理
}
@Override
public void onNothingSelected(AdapterView<?> parent) {

}
});
}

● 参考文献
Adapterの高速化
ListViewをカスタマイズする
AsyncTaskでユーザビリティを向上させる
AsyncTaskは使い捨て

TAG

  • このエントリーをはてなブックマークに追加
やまま
スペシャリスト やまま yamama

マンガとアニメとゲームから錬成された宇宙大好きエンジニア。 軌道エレベーターで行ける静止軌道上のコロニーに住まいを移し、ゲームやってマンガ読んでアニメ見て爆睡、ゲームやってマンガ読んでアニメ見て爆睡、という生活を夢見ながら今日もコードを書き続けるのだった。