이어서...

Layout에 TextView를 추가하겠습니다.
그리드 뷰에서 스크롤이 이동될때 관련 메세지가 찍히도록 하기 위해서 아래처럼 수정합니다.

[File : main.xml]

	
	
 20개의 이미지를 대상으로 했으나 이제 40개로 하겠습니다.

아래 20개의 URL을 url.xml에 추가해줍니다.
[File : url.xml]

	
			   
	   
	   
			   
	
	
	
			   
	
	
	
	
	
	
	
	
	
	
	


그리고 Activity를 조금 손 보겠습니다.

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

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

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import android.app.Activity;
import android.app.ProgressDialog;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.KeyEvent;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.GridView;
import android.widget.TextView;
import android.widget.Toast;

import com.yeory.gv.common.CommonResources;
import com.yeory.gv.model.Image;
import com.yeory.gv.util.CommonUtil;
import com.yeory.gv.util.ImageAdapter;
import com.yeory.gv.util.ImageDownloader;

/**
 * =============================================================================
/            프로젝트명 :   GridView_Example
/            화  일  명 :   ImageList.java
/            기      능 :   
/            인      수 :   
/            특이  사항 :
/-----------------------------------------------------------------------------
/                               변경 사항				                     
/-----------------------------------------------------------------------------
/    변경일자       	변경자(작성자)                 		변경 내역                 
/   ----------     	--------------------------       -------------------------
/   2010. 11. 10.      jYeory<jyeory@gmail.com>         		최 초 작 성                      
/==============================================================================
 */
public class ImageList extends Activity {
	private final ImageDownloader imageDownloader = ImageDownloader.getInstance();
	
	private GridView gridView;
	private TextView textView;
	private ImageAdapter adapter;
	
	private boolean isNextPage = true;
	boolean needCached = true;
	final int size = 15;			// 한번에 보여줄 이미지 개수
	private int totalSize = 0;		// paging에 필요
	private int currentPage = 0;	// paging에 필요
	private int start = 0;			
	private int end = 0;
	private int endPage = 0;
	private int lastPosition = 0;
	private int firstPosition = 0;
	
	private ProgressDialog pd;
	private Thread thread;
	
	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event) {
		if(keyCode == 4) CommonUtil.getInstance().requestKillProcess(this);
		return super.onKeyDown(keyCode, event);
	}
	
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        CommonResources.COMMON_CONTEXT = this;
        
        init(1, size);
        
        textView = (TextView) findViewById(R.id.logText);
        
        gridView = (GridView) findViewById(R.id.gridView);
        adapter = new ImageAdapter(this, imageList);
        
        gridView.setOnScrollListener(new OnScrollListener() {
			@Override
			public void onScrollStateChanged(AbsListView view, int scrollState) {
				isNextPage = imageList.size() < totalSize;
				try{
					switch (scrollState) {
					case 1:
						lastPosition = view.getLastVisiblePosition();
						firstPosition = view.getFirstVisiblePosition();
						if( lastPosition == (imageList.size()-1) && isNextPage ){
							pd = ProgressDialog.show(ImageList.this,  null, "데이터 수신중 입니다.\n잠시만 기다려 주세요...", false, true);
							
							thread = new Thread(new Runnable() {
								public void run() {
									start = (++currentPage - 1) * size;
									if(start < totalSize){
										endPage = ((currentPage*size) < totalSize)?(currentPage*size):totalSize;
										imageList.addAll(wholeImageList.subList(start, endPage));
									}
									// 아래 코드를 주석처리할 시 AsyncTask를 확인할 수 있다.
//									downloadImages(imageList);
									handler.sendEmptyMessage(0);
								}
							});
							thread.start();
						}
						break;
					default:
						break;
					}
				}catch(Exception e){
					Log.i("error", "gridView.setOnScrollListener : "+e.getCause().toString());
				}
			}
			
//			firstVisibleItem : 화면 최상단 이미지의 IDX
//			visibleItemCount : 현재 화면에 보여지는 이미지 개수
			@Override
			public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
				textView.setText("onScroll : "+"view.getLastVisiblePosition() - "+view.getLastVisiblePosition()+" / "+totalItemCount);
			}
		});
        
        gridView.setAdapter(adapter);
        
        Toast.makeText(this, "Yeory - www.yeroy.com", Toast.LENGTH_LONG).show();
    }
    
    /**
	 * ProgressDialog 표시 중 해당 이미지들을 다운받는다.
	 * AsyncTask를 사용할 경우 호출할 필요 없음. 
	 * @param images
	 */
	private void downloadImages(ArrayList images){
		for(Image image : images){
			imageDownloader.callDownloadImage(image.getThumbNail());
		}
	}
	
	private Handler handler= new Handler() {
		public void handleMessage(Message msg) {
			if(adapter == null) adapter = new ImageAdapter(ImageList.this, imageList);
			else{
				adapter.setList(imageList);
			}
			gridView.setAdapter(adapter);
			
			gridView.setSelection(firstPosition);
			pd.dismiss();
		}
	};
    
    private ArrayList wholeImageList = new ArrayList();	// 전체 이미지 담을 것.
	private ArrayList imageList = new ArrayList();	// 부분 이미지 담을 것.
	private XmlPullParser xpp;
	
    /**
	 * 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();
		}
	}
}

새롭게 그리드 뷰 스크롤에 관련된 이벤트가 추가되었습니다.
또한 이미지 캐시 기능을 제공하는 클래스인 ImageDownloader도 추가되어있습니다.

먼저 ImageDownloader는 아래 주소에 번역된 글이 있으니 참조 바랍니다.
번역 : 
http://huewu.blog.me/110090363656
원본 : http://android-developers.blogspot.com/2010/07/multithreading-for-performance.html

저는 위 코드를 입맛에 맞게 뜯어고쳐(?)서 사용하고 있습니다.

ImageDownloader.java의 코드는 너무 길어 적지 않고 글의 흐름상 필요한 메서드만 기술합니다.

Activity에서 GridView의 adapter를 set해주는 곳이 있었는데 실제 그 부분은 ImageAdapter.java 였습니다.
캐시 기능을 위해 아래처럼 변경되었습니다.

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

import java.util.ArrayList;

import android.content.Context;
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;
import com.yeory.gv.util.ImageDownloader.Mode;
/**
 * =============================================================================
/            프로젝트명 :   GridView_Example
/            화  일  명 :   ImageAdapter.java
/            기      능 :   
/            인      수 :   
/            특이  사항 :
/-----------------------------------------------------------------------------
/                               변경 사항				                     
/-----------------------------------------------------------------------------
/    변경일자       	변경자(작성자)                 		변경 내역                 
/   ----------     	--------------------------       -------------------------
/   2010. 11. 10.      jYeory<jyeory@gmail.com>         		최 초 작 성                      
/==============================================================================
 */
public class ImageAdapter extends BaseAdapter{
	private final ImageDownloader imageDownloader = ImageDownloader.getInstance();
	private final String LOG_TAG = "ImageAdapter";
	private Context mContext;
    private ImageView imageView;
    private ArrayList list;
    
    public ImageAdapter(Context c) {
        mContext = c;
    }
    
    public ImageAdapter(Context c, ArrayList list){
    	mContext = c;
    	this.setList(list);
    }
	
	
	@Override
	public int getCount() {
		Log.i(LOG_TAG, "called getCount()");
		return getList().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);
        } else {
            imageView = (ImageView) convertView;
        }
		
		imageDownloader.setMode(Mode.CORRECT);
		imageDownloader.download(getList().get(position).getThumbNail(), imageView);
		return imageView;
	}

	public void setList(ArrayList list) {
		this.list = list;
	}

	public ArrayList getList() {
		return list;
	}
}
코드에 살펴보시면 Mode.CORRECT 가 있는데 이 코드가 Async로 할 것인지에 대한 값입니다.

CORRECT로 할 경우 각 이미지를 개별로 다운받아 바로 사용자에게 표시합니다.
(주의 : ImageList.java에서 PrgoressDialog 표시 중일때 downloadImages(imageList); 를 주석처리 하여야 합니다.)

즉 CORRECT로 한다면 20개의 이미지를 먼저 다운로드 되는 것부터 표시할 수 있고,
주석처리할 경우 20개의 이미지가 모두 다운로드 된 후에야 사용자에게 표시하는 차이가 있습니다.

코드가 너무 많아져서 다 올리지 못하여 역시나 소스를 첨부합니다.
캐시 기능을 하는 것에 대해서는 해당 링크를 참조하시면 될 듯합니다.




다음은 그리드 뷰에서 아이템 클릭시 크게 보는 것과 드래그입니다...

  1. 2011.03.11 10:26

    비밀댓글입니다

  2. 안드로메이드 2012.02.15 11:47 신고

    그리드뷰를 어떻게 페이징으로 구현 할지 몰랐는데
    도움이 많이 되었습니다
    감사합니다 ^^

  3. 안드로이드 2012.10.17 15:55 신고

    소스다운받아서 올리면 앱이 죽네요 ㅠ

  4. 마앙 2012.11.12 13:19 신고

    썸네일 클릭시 확대하는건 언제 수정하시나요 ㅜㅠ? 제가 필요해서,....
    거기에 뷰플리퍼까지 넣는 기능을 넣어야 되는데 힘드네요

  5. 감사합니다 2013.07.06 20:01 신고

    이런 소스를 공개하긴 쉽지않을텐데. 덕분에 처리할 수 있을 것 같습니다.
    감사합니다.

    • 이직과 업무 변경으로 현재 포스트 이후 써내려 가야할 소스가 사라져 더이상 포스팅을 못하는게 아쉽네요..

      도움 되셨다면 그걸로 만족입니다.

  6. 감사합니다 2013.07.14 17:16 신고

    그런데...제가 한 100여개의 인터넷 url 을 받아와서 뿌려주고있는데요

    스크롤을 막내리다보면 죽어버리는 경우가 있습니다.(과격하게 사용하는경우)
    어찌해야할까요..........................ㅠㅠ

    현재 100장이미지인데 페이지를 100장을 초기값 잡았습니다.
    그래서 스크롤 없이 한번에 다보여준식으로....일단 이렇게 쓰면될거같긴한데

    혹시 페이지넘길때 생기는방식(카카오 스토리와같이)
    이 안정적으로 되는 추가방법 생각나시면 댓글 부탁드려요 (__*

    • 캐시를 이용하시나요??
      제 경우엔 Web 이미지를 표시를 해야할 때 캐시가 필수라 생각했었어요.

      1 ~ 20 까지 처음이고 스크롤을 내릴 시 5개씩의 이미지를 추가로 로드한다고 할 때,

      6 ~ 25 까지 이미지를 표시하고 (스크롤을 한번 내린 상태)
      기존 1 ~5의 이미지를 다시 보고자 스크롤을 올릴때 캐시를 이용하지 않으면 또 URL을 이용해서 이미지를 가지고 왔었습니다.
      이게 계속적으로 반복이 되면 메모리 부족으로 앱이 사망했구요.

      캐시를 이용할 경우로 500장까지 스크롤 테스트 했지만 문제 없었고...
      리스트에 표시되는 이미지도 미리 로컬로 다운로드 된 이미지를 가지고 오는 것이기에 즉각적으로 표시 되었구요.

  7. 감사합니다 2013.07.16 15:52 신고

    올려주신 소스가 캐시 사용을 기본으로 하는게 아닌가요?
    지금 경우 100장을 기준으로 한다고해도 3G 환경에서 실험결과,
    한 페이지내에 그림이 몇개 뜨다가 제가 스크롤 내렸다가 다시 돌아오면 전부 다시 로딩하는거같던데요....

    주석처리하는부분이 캐싱인가요?

    • 기본적으로 캐시를 합니다.
      다운로드 디렉토리만 정상적이라면 거기에 이미지를 넣어두고 가져오니깐요..

      제가 테스트 한 결과는
      첫 20여개의 이미지 로딩 완료 되고 스크롤 이동 후 다시 표시하고자 할때는 캐시된 이미지가 나왔기 때문에 바로 화면에 표시가 되었구요.

      주석처리 하는 부분은 화면상의 이미지를 한꺼번에 다 보여줄것이냐,
      다운로드 완료 된 것부터 보여줄 것이냐 하는 부분이기 때문에 해당 부분은 아닌 것 같네요..

      이미지도 썸네일을 불러오시는거라면.. 제 코드에 문제가 있을거라 보여지네요.

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

먼저 이미지 캐싱을 하기전에 캐싱을 안하게되면 어떤 문제점이 발생되는지부터 알아야하기에 일반적인 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한거 실행시켜보니까 프로그램이 죽어버리네요ㅜ.ㅜ
    와이파이도 연결 잘 돼있는데... 왜 그런지 아시나요?ㅎㅎ

- 편의상 편하게 쓸랍니다 -

안드로이드에서 제공하는 모든 이미지 관련 API는 기기내의 MediaStorage를 기반으로 한다. 

ThumbNail을 생성해주는 Images.Thumbnails.getThumbnail 메서드 또한 기기내의 파일을 위한 것이다.

구글에서 제공하는 Demo에서 사용되는 것 역시 기기(이하 로컬)내의 파일이다.

Activity에 GridView를 불러들이고 gridView에 ImageAdapter를 사용하여 그리드를 구현한다는 것인데 - 
(Adapater 사용은 구글에서 권고하는 가이드 라인에 포함되어 있는 코딩 방법이다)

Demo에 나와있는 아주 표준적인 ImageAdapter를 먼저 살펴보자...


public class ImageAdapter extends BaseAdapter {
    private Context mContext;

    public ImageAdapter(Context c) {
        mContext = c;
    }

    public int getCount() {
        return mThumbIds.length;
    }

    public Object getItem(int position) {
        return null;
    }

    public long getItemId(int position) {
        return 0;
    }

    // create a new ImageView for each item referenced by the Adapter
    public View getView(int position, View convertView, ViewGroup parent) {
        ImageView imageView;
        if (convertView == null) {  // if it's not recycled, initialize some attributes
            imageView = new ImageView(mContext);
            imageView.setLayoutParams(new GridView.LayoutParams(85, 85));
            imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
            imageView.setPadding(8, 8, 8, 8);
        } else {
            imageView = (ImageView) convertView;
        }

        imageView.setImageResource(mThumbIds[position]);
        return imageView;
    }

    // references to our images
    private Integer[] mThumbIds = {
            R.drawable.sample_2, R.drawable.sample_3,
            R.drawable.sample_4, R.drawable.sample_5,
            R.drawable.sample_6, R.drawable.sample_7,
            R.drawable.sample_0, R.drawable.sample_1,
            R.drawable.sample_2, R.drawable.sample_3,
            R.drawable.sample_4, R.drawable.sample_5,
            R.drawable.sample_6, R.drawable.sample_7,
            R.drawable.sample_0, R.drawable.sample_1,
            R.drawable.sample_2, R.drawable.sample_3,
            R.drawable.sample_4, R.drawable.sample_5,
            R.drawable.sample_6, R.drawable.sample_7
    };
}

간단한 구조로 되어있다. 또한 이미지를 res/drawable 폴더에 다 넣어서 직접 가져다 쓰고 있다.
이건 로컬의 이미지를 구현하기 위한 방법이므로 아주 적당하다고 할 수 있다. 
(하지만 100장이 넘는 이미지라면..? ID 다 적어줄 건가..? 당신은 어찌할 것인가??)

우선 그리드를 구성하는 하나의 이미지는 ImageView로 구성이 된다.
그리고 이 ImageView는 getView()에서 생성이 되는데 ImageView의 핵심인 이미지 파일의 정보 또한 getView()에서 처리되고 있다.

ImageAdapter를 사용하는 방법은 다음과 같다.

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    GridView gridview = (GridView) findViewById(R.id.gridview);
    gridview.setAdapter(new ImageAdapter(this));
}



기본적인 Demo에 관련된 자세한 사항은 이 글보다는 아래 글을 참고하길 바란다.


로컬의 이미지들을 처리할 것이라면 정말 이보다 좋은 방법은 있을 수 없다.
또한 이미 생성된 ImageView는 재사용(recycled)이 가능하기 때문에 자원(resource) 관리에도 도움이 된다.

로컬에서의 이미지 처리시 생각해야할 부분은 이미지 정보들을 어떻게 가져올 것인가에 대한 것이다.

가장 극단적인 예로 SD 카드의 이미지를 처리할려고 하면 정말 코드 몇줄이면 끝나지만 - 
자신이 만들고 있는 어플에서 쓸 이미지를 부를거라면? 
근데 그 이미지들이 1~2장이 아니라 100장이 된다면?  언제 R.id.xx 해서 100개 다 찍고 있을건가.?

즉 GridView를 이용하여 웹(또는 로컬)에서 이미지를 가져와 작업을 하려면 위 사항을 최소한으로 생각해보아야 한다.





계속 쭉쭉 이어 적을려고 했으나 퇴근시간이고 맥주타임이 기다리고 있기에 이까지 적고 - 
기본적인 웹에서 이미지 불러오는 것을 단계적으로 해결해 나가는 글을 차례로 적어 보렵니다... 

아마.. 다음글은  웹 이미지를 GridView로 불러와 보기 및 문제점 인식이 되겠네요.

+ Recent posts