어제에 이어서 계속.....

먼저 이미지 캐싱을 하기전에 캐싱을 안하게되면 어떤 문제점이 발생되는지부터 알아야하기에 일반적인 GridView를 만들겠습니다.

새로운 프로젝트를 만듭니다.
(가급적이면 비슷하게 하면 더 쉽겠죠...)

API 8로 만드세요.. 잘 못 눌렀어요

프로젝트 생성후 AndroidManifest.xml을 아래와 같이 수정합니다.


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.yeory.gv"
      android:versionCode="1"
      android:versionName="1.0">
    <application android:icon="@drawable/icon" 
    			 android:label="@string/app_name"
    			 android:debuggable="true">
    			 
        <activity android:name=".ImageList"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
    
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-sdk android:minSdkVersion="8" />
</manifest> 
android:debuggable="true" -> Eclipse에서 디버그 모드를 가능하게 하는 명령줄입니다.android.permission.INTERNET -> App에서 Internet 접근을 허용하게 하는 권한 입니다.

대충 GridView의 초기 설정은 끝이 났습니다. 
Internet 권한을 준 이유는 당연한 것이겠지만 웹에서 이미지를 가져올 것이기에 그런 것이죠 - 
권한이 없다면 Permission Denied~~~ 에러 납니다.

res 폴더 내에 xml을 저장할 폴더를 만들고 파일을 만들겠습니다.
res 폴더는 App에서 사용할 자원(Resources)을 저장한다고 보시면 됩니다.


그리고 생성된 xml 폴더에 파일을 만듭니다. [ url.xml ] 
이 XML에는 웹에서 불러올 이미지 주소를 적어 줍니다.  

형식은 아래와 같습니다.

<images>
	<image description="" uri="http://yeory.just4fun.co.kr/android/image/image01.jpg"
		   thumb="http://yeory.just4fun.co.kr/android/image/thumb/th_image01.jpg" />
		   
	<image description="" uri="http://yeory.just4fun.co.kr/android/image/image02.jpg"
		   thumb="http://yeory.just4fun.co.kr/android/image/thumb/th_image02.jpg" />
		   
	<image description="" uri="http://yeory.just4fun.co.kr/android/image/image03.jpg"
		   thumb="http://yeory.just4fun.co.kr/android/image/thumb/th_image03.jpg" />
		   
	<image description="" uri="http://yeory.just4fun.co.kr/android/image/image04.jpg"
		   thumb="http://yeory.just4fun.co.kr/android/image/thumb/th_image04.jpg" />
		   
	<image description="" uri="http://yeory.just4fun.co.kr/android/image/image05.jpg"
		   thumb="http://yeory.just4fun.co.kr/android/image/thumb/th_image05.jpg" />
		   
	<image description="" uri="http://yeory.just4fun.co.kr/android/image/image06.jpg"
		   thumb="http://yeory.just4fun.co.kr/android/image/thumb/th_image06.jpg" />
		   
	<image description="" uri="http://yeory.just4fun.co.kr/android/image/image07.jpg"
		   thumb="http://yeory.just4fun.co.kr/android/image/thumb/th_image07.jpg" />
		   
	<image description="" uri="http://yeory.just4fun.co.kr/android/image/image08.jpg"
		   thumb="http://yeory.just4fun.co.kr/android/image/thumb/th_image08.jpg" />
		   
	<image description="" uri="http://yeory.just4fun.co.kr/android/image/image09.jpg"
		   thumb="http://yeory.just4fun.co.kr/android/image/thumb/th_image09.jpg" />
		   
	<image description="" uri="http://yeory.just4fun.co.kr/android/image/image10.jpg"
		   thumb="http://yeory.just4fun.co.kr/android/image/thumb/th_image10.jpg" />
		   
	<image description="" uri="http://yeory.just4fun.co.kr/android/image/image11.jpg"
		   thumb="http://yeory.just4fun.co.kr/android/image/thumb/th_image11.jpg" />
		   
	<image description="" uri="http://yeory.just4fun.co.kr/android/image/image12.jpg"
		   thumb="http://yeory.just4fun.co.kr/android/image/thumb/th_image12.jpg" />
		   
	<image description="" uri="http://yeory.just4fun.co.kr/android/image/image13.jpg"
		   thumb="http://yeory.just4fun.co.kr/android/image/thumb/th_image13.jpg" />
		   
	<image description="" uri="http://yeory.just4fun.co.kr/android/image/image14.jpg"
		   thumb="http://yeory.just4fun.co.kr/android/image/thumb/th_image14.jpg" />
		   
	<image description="" uri="http://yeory.just4fun.co.kr/android/image/image15.jpg"
		   thumb="http://yeory.just4fun.co.kr/android/image/thumb/th_image15.jpg" />
		   
	<image description="" uri="http://yeory.just4fun.co.kr/android/image/image16.jpg"
		   thumb="http://yeory.just4fun.co.kr/android/image/thumb/th_image16.jpg" />
		   
	<image description="" uri="http://yeory.just4fun.co.kr/android/image/image17.jpg"
		   thumb="http://yeory.just4fun.co.kr/android/image/thumb/th_image17.jpg" />
		   
	<image description="" uri="http://yeory.just4fun.co.kr/android/image/image18.jpg"
		   thumb="http://yeory.just4fun.co.kr/android/image/thumb/th_image18.jpg" />
		   
	<image description="" uri="http://yeory.just4fun.co.kr/android/image/image19.jpg"
		   thumb="http://yeory.just4fun.co.kr/android/image/thumb/th_image19.jpg" />
		   
	<image description="" uri="http://yeory.just4fun.co.kr/android/image/image20.jpg"
		   thumb="http://yeory.just4fun.co.kr/android/image/thumb/th_image20.jpg" />
</images>
20개 정도만 하겠습니다. 나중에 더 추가해야합니다.

이제 실제 GridView를 layout에 정의하겠습니다.

[ file : res/layout/main.xml]

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
	<GridView xmlns:android="http://schemas.android.com/apk/res/android" 
		android:id="@+id/gridView"
		android:layout_width="fill_parent"
		android:layout_height="400dp"
		android:numColumns="auto_fit"
		android:verticalSpacing="10dp"
		android:horizontalSpacing="10dp"
		android:columnWidth="90dp"
		android:fastScrollEnabled="true"
		android:smoothScrollbar="true"
		android:scrollingCache="true"
		android:stretchMode="columnWidth"
		android:gravity="center"
	/>
	<!-- 스크롤에 관련된 정보 보여줄 TextView -->
	<TextView xmlns:android="http://schemas.android.com/apk/res/android" 
		android:id="@+id/logText"
		android:layout_width="fill_parent"
		android:layout_height="fill_parent"
	/>
</LinearLayout>

속성들은 대충 보셔도 알거라 생각하기에 그냥 넘깁니다. 
궁금한건 API 보시고 그래도 모르겠으면 구글링 하시고 그게 귀찮으면 댓글로...... ;;

이제 Activity에서 정의한 Layout을 불러와야 합니다.

[ file : ImageList.java ]
package com.yeory.gv;

import android.app.Activity;
import android.os.Bundle;
import android.widget.GridView;

/**
 * =============================================================================
/            프로젝트명 :   GridView_Example
/            화  일  명 :   ImageList.java
/            기      능 :   
/            인      수 :   
/            특이  사항 :
/-----------------------------------------------------------------------------
/                               변경 사항				                     
/-----------------------------------------------------------------------------
/    변경일자       	변경자(작성자)                 		변경 내역                 
/   ----------     	--------------------------       -------------------------
/   2010. 11. 10.       jYeory<jyeory@gmail.com>         	최 초 작 성                      
/==============================================================================
 */
public class ImageList extends Activity {    
	/** Called when the activity is first created. */    

	@Override    
	public void onCreate(Bundle savedInstanceState) {        
		super.onCreate(savedInstanceState);        
		setContentView(R.layout.main);                
		GridView gridView = (GridView) findViewById(R.id.gridView);    
	}
}
여기까지 한 것을 실행시켜 보도록 하죠..

먼저 왼쪽의 +를 눌러서 새로운 실행 환경을 설정해 주어야 합니다. 
눌러서 그림과 같이 적고 선택해 줍니다.

구글 가이드 라인은 휴대폰을 연결하여 작업하는 것을 권장합니다. 그러므로 Automatic이 아닌 Manual로 실행합시다.

실행 되겠죠?

XML을 읽어와 각 정보를 bean에 담아야 합니다.
그러므로 class를 하나 만들겠습니다.
<패키지 명 확인하세요>

[ file : Image.java ]
package com.yeory.gv.model;

/**
 * =============================================================================
/            프로젝트명 :   GridView_Example
/            화  일  명 :   Image.java
/            기      능 :   
/            인      수 :   
/            특이  사항 :
/-----------------------------------------------------------------------------
/                               변경 사항				                     
/-----------------------------------------------------------------------------
/    변경일자       	변경자(작성자)                 		변경 내역                 
/   ----------     	--------------------------       -------------------------
/   2010. 11. 10.      jYeory<jyeory@gmail.com>         		최 초 작 성                      
/==============================================================================
 */
public class Image {
	private String description;
	private String thumbNail;
	private String uri;
	
	public Image() {}
	
	public Image(String d, String u, String t) {
		this.description = d;
		this.uri = u;
		this.thumbNail = t;
	}
	
	public String getDescription() {
		return description;
	}
	public void setDescription(String description) {
		this.description = description;
	}
	public String getUri() {
		return uri;
	}
	public void setUri(String uri) {
		this.uri = uri;
	}

	public void setThumbNail(String thumbNail) {
		this.thumbNail = thumbNail;
	}

	public String getThumbNail() {
		return thumbNail;
	}
}



이젠 xml로 정의한 내용(url)을 가져오도록 하겠습니다.
Activity가 실행되면 가장 먼저 해야할 작업입니다. 그리고 이를 가공하여 다음에 만들 Adapter에 넘겨줘야 하죠.

[ method : init() ]
	private ArrayList wholeImageList = new ArrayList();	// 전체 이미지 담을 것.
	private ArrayList imageList = new ArrayList();	// 부분 이미지 담을 것.
	private XmlPullParser xpp;
	final int size = 15;			// 한번에 보여줄 이미지 개수
	private int totalSize = 0;		// paging에 필요
	private int currentPage = 0;	// paging에 필요
	private int start = 0;			
	private int end = 0;
	
        /**
	 * XML을 읽어들여 전체 이미지를 ArrayList에 넣는다.
	 * 처음으로 호출 되기에 15개만 다른 list에 담아 캐싱. 
	 * @param page
	 * @param size
	 */
	private void init(final int page, final int size) {
		xpp = getResources().getXml(R.xml.url);
		try{
			while ( xpp.getEventType() != XmlPullParser.END_DOCUMENT ){  // 마지막 문서까지
				if ( xpp.getEventType() == XmlPullParser.START_TAG ){
					if ( xpp.getName().equals("image") ){
						wholeImageList.add(new Image(xpp.getAttributeValue(0), xpp.getAttributeValue(1), xpp.getAttributeValue(2)));
					}
				}
				xpp.next();
			}
			
			totalSize = wholeImageList.size();
			currentPage = page;

			start = (page - 1) * size; 
			end = (page*size);
			imageList.addAll(wholeImageList.subList(start, end));
			
		}catch(XmlPullParserException xppe){
			Log.i("error", "init()"+xppe);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
XML을 읽어 <image></image>의 내용을 wholeImageList에 add 합니다.
10개든 100개든 url만 저장하는거죠.
그리고 정해진 개수에 맞게 페이징 처리를 해서 실제 불러올 url만 따로 imageList에 저장하는 것이죠.
Adapter에 넘겨줄 list는 ImageList입니다.

이젠 Grid를 구성할 수 있게 ImageAdapter를 만들겠습니다.

GridView에서 작은 박스들로 그리드를 구성하여 리스팅하게 되는데 이때 사용하는 방법은 Adapter 방식을 사용합니다.
가이드 라인에서도 이미지 처리는 가능하다면 Adapter를 사용하라고 되어 있습니다. 
(참고로 지금 만들 ImageAdapter는 PHP의 Iterator class와 비슷합니다.)

패키지 명이 다릅니다. 확인하세요.


[ file : ImageAdapter.java ]
package com.yeory.gv.util;

import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
/**
 * =============================================================================
/            프로젝트명 :   GridView_Example
/            화  일  명 :   ImageAdapter.java
/            기      능 :   
/            인      수 :   
/            특이  사항 :
/-----------------------------------------------------------------------------
/                               변경 사항				                     
/-----------------------------------------------------------------------------
/    변경일자       	변경자(작성자)                 		변경 내역                 
/   ----------     	--------------------------       -------------------------
/   2010. 11. 10.      jYeory<jyeory@gmail.com>         		최 초 작 성                      
/==============================================================================
 */
public class ImageAdapter extends BaseAdapter{
	private final String LOG_TAG = "ImageAdapter";
	
	@Override
	public int getCount() {
		Log.i(LOG_TAG, "called getCount()");
		return 0;
	}

	@Override
	public Object getItem(int position) {
		Log.i(LOG_TAG, "called getItem()");
		return null;
	}

	@Override
	public long getItemId(int position) {
		Log.i(LOG_TAG, "called getItemId()");
		return 0;
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		Log.i(LOG_TAG, "called getView()");
		return null;
	}

}
기본적인 ImageAdapter의 구조입니다. @Override 메서드는 모두 구현되어야만 합니다.
각 Log는 각 메서드의 호출 순서를 확인하기 위해서입니다만 본인은 다 까먹었습니다.. ㅋ

구글 데모에서는 Activity 내에 클래스를 만들어 사용을 했는데 전 그 방법을 좋아하지 않습니다.
가능하다면 위와 같이 외부 클래스로 작성 할것을 미리 알려드립니다.


기본적인 뼈를 만들었으니 살을 붙여야합니다.

[ file : ImageAdapter.java ]

package com.yeory.gv.util;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;

import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.entity.BufferedHttpEntity;
import org.apache.http.impl.client.DefaultHttpClient;

import android.content.Context;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.GridView;
import android.widget.ImageView;

import com.yeory.gv.model.Image;
/**
 * =============================================================================
/            프로젝트명 :   GridView_Example
/            화  일  명 :   ImageAdapter.java
/            기      능 :   
/            인      수 :   
/            특이  사항 :
/-----------------------------------------------------------------------------
/                               변경 사항				                     
/-----------------------------------------------------------------------------
/    변경일자       	변경자(작성자)                 		변경 내역                 
/   ----------     	--------------------------       -------------------------
/   2010. 11. 10.      jYeory<jyeory@gmail.com>        		최 초 작 성                      
/==============================================================================
 */
public class ImageAdapter extends BaseAdapter{
	private final String LOG_TAG = "ImageAdapter";
	private Context mContext;
    private ImageView imageView;
    private ArrayList lists;
    private BitmapDrawable bd;
    
    public ImageAdapter(Context c) {
        mContext = c;
    }
    
    public ImageAdapter(Context c, ArrayList lists){
    	mContext = c;
    	this.lists = lists;
    }
	
	
	@Override
	public int getCount() {
		Log.i(LOG_TAG, "called getCount()");
		return lists.size();
	}

	@Override
	public Object getItem(int position) {
		Log.i(LOG_TAG, "called getItem()");
		return null;
	}

	@Override
	public long getItemId(int position) {
		Log.i(LOG_TAG, "called getItemId()");
		return 0;
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		Log.i(LOG_TAG, "called getView()");
		if (convertView == null) {  // if it's not recycled, initialize some attributes.
    		imageView = new ImageView(mContext);
    		imageView.setLayoutParams(new GridView.LayoutParams(150, 150));
    		imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
    		imageView.setPadding(4, 4, 4, 4);
    		
    		bd = downloadBitmap(lists.get(position).getThumbNail());
    		
            imageView.setImageBitmap(bd.getBitmap());
        } else {
            imageView = (ImageView) convertView;
        }
		
		return imageView;
	}
	
	private HttpClient client = new DefaultHttpClient();
	private HttpGet httpRequest = null; 
	private int statusCode;
	private HttpResponse response = null; 
	private BufferedHttpEntity bufHttpEntity = null; 
    private InputStream is = null;
	
	private BitmapDrawable downloadBitmap(String url) {
		Log.i("test", "url : "+url);
        // AndroidHttpClient is not allowed to be used from the main thread
        try {
        	httpRequest = new HttpGet(url); 
            response = client.execute(httpRequest);
            statusCode = response.getStatusLine().getStatusCode();
            if (statusCode != HttpStatus.SC_OK) { 
                Log.w("ImageDownloader", "Error " + statusCode + " while retrieving bitmap from " + url); 
                return null;
            }
            
            bufHttpEntity = new BufferedHttpEntity(response.getEntity()); 
            if (bufHttpEntity != null) {
                try {
                	is = bufHttpEntity.getContent();
                    // return BitmapFactory.decodeStream(inputStream);
                    // Bug on slow connections, fixed in future release.
                    return new BitmapDrawable(BitmapFactory.decodeStream(is));
                } finally {
                    if (is != null) {
                    	is.close();
                    }
                    bufHttpEntity.consumeContent();
                }
            }
        } catch (IOException e) {
        	httpRequest.abort();
            Log.w(LOG_TAG, "I/O error while retrieving bitmap from " + url, e);
        } catch (IllegalStateException e) {
        	httpRequest.abort();
            Log.w(LOG_TAG, "Incorrect URL: " + url);
        } catch (Exception e) {
        	httpRequest.abort();
            Log.w(LOG_TAG, "Error while retrieving bitmap from " + url, e);
        }
        return null;
    }
}
ImageAdapter의 생성자를 통해 context와 url이 담겨져 있는 list를 받게 되어 있습니다.
이후 getView가 호출되면 순서에 해당하는 list의 url을 찾아 통신을 하게 되어 있죠.
이부분은 캐싱을 할 때 자세히 다루겠습니다...

글이 점점 길어집니다.. 하...

이렇게 만든 ImageAdpater를 Activity에 등록해주어야 합니다.

[ method : onCreate() ]

	private GridView gridView;
	private TextView textView;
	private ImageAdapter adapter; 
	
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        init(1, size);
        
        textView = (TextView) findViewById(R.id.logText);
        gridView = (GridView) findViewById(R.id.gridView);
        adapter = new ImageAdapter(this, imageList);
        
        gridView.setAdapter(adapter);
    }

이제 실행하여야하는데 와이파이를 이용하길 권장합니다.
실행시 그리드로 구성되고 스크롤도 가능합니다.

하지만 문제가 발생합니다.

1. 스크롤 시 보여지는 thumbNail이 변경됩니다.
GrdiView의 문제점이라고 할 수 있는데 ImageView가 Activity 밖으로 벗어나게 되면 해당 ImageView는 소멸(이라 쓰고 잠수라고 읽습니다..) 됩니다. (screen out)
그리고 다시 Activity안으로 ImageView가 다시 보여지게 된다면 재사용(recycled)이 가능해져 기존 사용한 ImageView를 불러오는데 이때 이미지가 변경된다고 짐작하고 있습니다...
(다 변경되는건 아니고 Activity 밖으로 스크롤된 이미지만!!)
2. 스크롤 시 다음 페이지를 가지고 와야한다.


여기까지 입니다.

풀 소스를 첨부합니다.

아... 힘들다.. 



 2011. 11. 02일 수정

이전에 적었던 1번 문제가 해결되었습니다.
아마 API가 업데이트 되면서 해결된거 같은데 기록을 뒤지자니 끝도 없어 포기;

현재 위 예제는 HTC 넥서스 원, HTC 레이더 LTE 기종에서 실행되는걸 확인 했습니다. 


  1. 이현태 2011.01.20 09:58 신고

    아..좋은내용 감사합니다!^^

    한참을 찾아헤매다 님의 블로그에서 찾게 되었네요.

    아직 실력이 부족해서..매일 구글링으로 자료를 찾아 다니고만 있네요.

    그래도 즐거운 마음으로 안드로이드 공부중입니다^^ 자료 감사합니다~

    캐싱부분도 기대하고 있겠습니다^^

  2. yap 2011.10.26 14:56 신고

    위에 써주신 문제점을 확인해보고 싶은데...
    다운로드 받아서 import한거 실행시켜보니까 프로그램이 죽어버리네요ㅜ.ㅜ
    와이파이도 연결 잘 돼있는데... 왜 그런지 아시나요?ㅎㅎ

+ Recent posts