Android 복수 선택이 가능한 갤러리를 만들자 2

안녕하세요 지헌입니다.
이번시간에는 지난시간에 이어서
복수 선택이 가능한 갤러리 앱의 속도를 개선시키고 실제로 복수 선택 기능을 추가해보도록 하겠습니다.
역시 이번시간 전체 소스도 마지막에 첨부하도록 하겠습니다.

오늘은 Main.java 파일만 건드리면 되겠네요
사실 말은 거창하게 속도 개선입니다만 이것은 구글에서 배포하는 API 데모 예제에 있는
슬로우 어댑터 기법을 약간 응용한 것입니다. 비교적 간단하지만 효과는 만점이지요

먼저 Main.java 파일을 살펴보겠습니다.
파랑색 부분이 수정/추가 된 부분입니다.

먼저 ListView.OnScrollListener, GridView.OnItemClickListener 두개를 구현하고 있습니다.
온스크롤 리스너를 통해 스크롤 상태인지 아닌지를 mBusy 변수에 저장합니다. false이면
스크롤 상태가 아니고 true이면 스크롤중인 상태인것이 됩니다.

그래서 이미지 어댑터에서는 mBusy가 펄스일 경우에만 화면에 이미지들을 뿌려주게 되어 있습니다.

그리고 notifyDataSetChanged 라는 메서드가 중간중간 호출되는곳이 있는데
이것은 어댑터에게 화면을 갱신하라고 알려주는 것입니다. 데이터셋이 변경되었음을 알려주는 것이죠
그러면 어댑터는 자동으로 현재 보이는 화면을 갱신하면서 getView를 호출하게됩니다.
onItemClick 메서드는 아이템을 클릭했을때 체크박스를 체크/언체크 하기 위한 것이구요
코드 자체는 그리 어려운게 없습니다. 간단하죠 ^^
************************************ Main.java ************************************
package com.jeehun.android.mygallery;

import java.io.File;
import java.util.ArrayList;

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.MediaStore;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.CheckBox;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.AbsListView.OnScrollListener;

public class Main extends Activity implements ListView.OnScrollListener, GridView.OnItemClickListener
{
  boolean mBusy = false;
  ProgressDialog mLoagindDialog;
  GridView mGvImageList;
  ImageAdapter mListAdapter;
  ArrayList<ThumbImageInfo> mThumbImageInfoList;
 
  @Override
  public void onCreate(Bundle savedInstanceState)
  {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.image_list_view);
   
    mThumbImageInfoList = new ArrayList<ThumbImageInfo>();
    mGvImageList = (GridView) findViewById(R.id.gvImageList);
    mGvImageList.setOnScrollListener(this);
    mGvImageList.setOnItemClickListener(this);
   
    new DoFindImageList().execute();
  }
 
  private long findThumbList()
  {
    long returnValue = 0;
   
    // Select 하고자 하는 컬럼
    String[] projection = { MediaStore.Images.Media._ID, MediaStore.Images.Media.DATA };
   
    // 쿼리 수행
    Cursor imageCursor = managedQuery(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection, null, null, MediaStore.Images.Media.DATE_ADDED + " desc ");

    if (imageCursor != null && imageCursor.getCount() > 0)
    {
      // 컬럼 인덱스
      int imageIDCol = imageCursor.getColumnIndex(MediaStore.Images.Media._ID);
      int imageDataCol = imageCursor.getColumnIndex(MediaStore.Images.Media.DATA);

      // 커서에서 이미지의 ID와 경로명을 가져와서 ThumbImageInfo 모델 클래스를 생성해서
      // 리스트에 더해준다.
      while (imageCursor.moveToNext())
      {
        ThumbImageInfo thumbInfo = new ThumbImageInfo();

        thumbInfo.setId(imageCursor.getString(imageIDCol));
        thumbInfo.setData(imageCursor.getString(imageDataCol));
        thumbInfo.setCheckedState(false);
       
        mThumbImageInfoList.add(thumbInfo);
        returnValue++;
      }
    }
    imageCursor.close();
    return returnValue;
  }
 
  // 화면에 이미지들을 뿌려준다.
  private void updateUI()
  {
    mListAdapter = new ImageAdapter (this, R.layout.image_cell, mThumbImageInfoList);
    mGvImageList.setAdapter(mListAdapter);
  }
  
  
  
  public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)
  {}
  
  // 스크롤 상태를 판단한다.
  // 스크롤 상태가 IDLE 인 경우(mBusy == false)에만 이미지 어댑터의 getView에서
  // 이미지들을 출력한다.
  public void onScrollStateChanged(AbsListView view, int scrollState)
  {
    switch (scrollState)
    {
      case OnScrollListener.SCROLL_STATE_IDLE:
        mBusy = false;
        mListAdapter.notifyDataSetChanged();
        break;
      case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
        mBusy = true;
        break;
      case OnScrollListener.SCROLL_STATE_FLING:
        mBusy = true;
        break;
    }
  }
  
  // 아이템 체크시 현재 체크상태를 가져와서 반대로 변경(true -> false, false -> true)시키고
  // 그 결과를 다시 ArrayList의 같은 위치에 담아준다
  // 그리고 어댑터의 notifyDataSetChanged() 메서드를 호출하면 리스트가 현재 보이는
  // 부분의 화면을 다시 그리기 시작하는데(getView 호출) 이러면서 변경된 체크상태를 
  // 파악하여 체크박스에 체크/언체크를 처리한다.
  @Override
  public void onItemClick(AdapterView<?> arg0, View arg1, int position, long arg3)
  {
    ImageAdapter adapter = (ImageAdapter) arg0.getAdapter();
    ThumbImageInfo rowData = (ThumbImageInfo)adapter.getItem(position);
    boolean curCheckState = rowData.getCheckedState();
   
    rowData.setCheckedState(!curCheckState);
   
    mThumbImageInfoList.set(position, rowData);
    adapter.notifyDataSetChanged();
  }
  
  
  // ***************************************************************************************** //
  // Image Adapter Class
  // ***************************************************************************************** //
  static class ImageViewHolder
  {
    ImageView ivImage;
    CheckBox chkImage;
  }
 
  private class ImageAdapter extends BaseAdapter
  {
    private Context mContext;
    private int mCellLayout;
    private LayoutInflater mLiInflater;
    private ArrayList<ThumbImageInfo> mThumbImageInfoList;
   
    public ImageAdapter(Context c, int cellLayout, ArrayList<ThumbImageInfo> thumbImageInfoList)
    {
      mContext = c;
      mCellLayout = cellLayout;
      mThumbImageInfoList = thumbImageInfoList;
     
      mLiInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }
   
    public int getCount()
    {
      return mThumbImageInfoList.size();
    }

    public Object getItem(int position)
    {
      return mThumbImageInfoList.get(position);
    }

    public long getItemId(int position)
    {
      return position;
    }
   
    public View getView(int position, View convertView, ViewGroup parent)
    {
      if (convertView == null)
      {
        convertView = mLiInflater.inflate(mCellLayout, parent, false);
        ImageViewHolder holder = new ImageViewHolder();
       
        holder.ivImage = (ImageView) convertView.findViewById(R.id.ivImage);
        holder.chkImage = (CheckBox) convertView.findViewById(R.id.chkImage);
       
        convertView.setTag(holder);
      }

      final ImageViewHolder holder = (ImageViewHolder) convertView.getTag();
     
      if (((ThumbImageInfo) mThumbImageInfoList.get(position)).getCheckedState())
        holder.chkImage.setChecked(true);
      else
        holder.chkImage.setChecked(false);

      if (!mBusy)
      {
        try
        {
          String path = ((ThumbImageInfo) mThumbImageInfoList.get(position)).getData();
         
          BitmapFactory.Options option = new BitmapFactory.Options();
         
          if (new File(path).length() > 100000)
            option.inSampleSize = 10;
          else
            option.inSampleSize = 2;
         
          Bitmap bmp = BitmapFactory.decodeFile(path, option);
          holder.ivImage.setImageBitmap(bmp); 
          holder.ivImage.setVisibility(0x00000000);
          setProgressBarIndeterminateVisibility(false);
        }
        catch (Exception e)
        {
          e.printStackTrace();
          setProgressBarIndeterminateVisibility(false);
        }
      }
      else
      {
        setProgressBarIndeterminateVisibility(true);
        holder.ivImage.setVisibility(0x00000004);
      }
       
      return convertView;
    }
  }
  // ***************************************************************************************** //
  // Image Adapter Class End
  // ***************************************************************************************** //
 
  // ***************************************************************************************** //
  // AsyncTask Class
  // ***************************************************************************************** //
  private class DoFindImageList extends AsyncTask<String, Integer, Long>
  {
    @Override
    protected void onPreExecute()
    {
      mLoagindDialog = ProgressDialog.show(Main.this, null, "로딩중...", true, true);
      super.onPreExecute();
    }
   
    @Override
    protected Long doInBackground(String... arg0)
    {
      long returnValue = 0;
      returnValue = findThumbList();
      return returnValue;
    }

    @Override
    protected void onPostExecute(Long result)
    {
      updateUI();
      mLoagindDialog.dismiss();
    }

    @Override
    protected void onCancelled()
    {
      super.onCancelled();
    }
  }
  // ***************************************************************************************** //
  // AsyncTask Class End
  // ***************************************************************************************** //
}
***********************************************************************************

이제 실행시켜보겠습니다.
실행시키면 처음엔 그대로 사진들이 뜨구요
이제 스크롤 시키면 다음처럼 스크롤 중에는 사진들이 뜨지 않게 됩니다.


그리고 사진을 여러개 선택할 수도 있죠

그런데 실행시키셔서 사진을 선택해보시면 아시겠지만 체크하고 나서 한~참 있다가 체크박스가 체크되는게 보이실겁니다.
스크롤시에 버벅임은 해결했지만 사진 선택시에는 아직도 느려서 쓰기가 힘듭니다.
이것은 getView에서 매번 파일을 직접 불러와서 샘플링을 거쳐서 비트맵 객체를 만들고 있기 때문입니다. 화면갱신하면서
이렇게 비트맵 객체를 새로 만든느 일이 매번 일어나기때문에 느려지는건데요
그래서 다음시간에는 이점을 개선해보도록 하겠습니다.
전체소스 : MyGallery_02.zip

도움 되셨다면 리플 하나정도 달아주시면 감사하겠습니다. ^^

by 선지헌 | 2011/06/30 00:12 | Android | 트랙백 | 덧글(11)

트랙백 주소 : http://jeehun.egloos.com/tb/4078992
☞ 내 이글루에 이 글과 관련된 글 쓰기 (트랙백 보내기) [도움말]
Commented by 소서노 at 2011/07/12 19:42
감사합니다. 많은 도움이 됐습니다.
Commented by 선지헌 at 2011/07/12 19:49
도움되셨다니 다행입니다. ^^
Commented by 댄디열이 at 2011/08/06 00:44
공부하는데 많은 도움이 되었습니다.
정말 감사합니다.^^
Commented by mmk at 2011/08/23 15:16
수정하여서 유용하게 잘썼습니다! 나중에 제가 수정한 정보도 공유할께요ㅎ
Commented by 늑대군 at 2012/02/20 18:54
감사합니다 ^^
Commented by 준휘 at 2012/07/21 13:16
ㅎㅎㅎ 유용하게 잘 쓰겠습니다.
Commented by 맑은물 at 2013/06/20 17:03
잘 사용하겠습니다~~~~ *^^*
Commented by 우왕굿 at 2013/07/12 16:10
정말 도움 많이 되었습니다! 감사해요~^^
Commented by 플룩스 at 2013/08/15 13:33
너무 도움이 많이 되었씁니다.
혹시 체크박스가 한개만 클릭되게 하려면 어느부분을 수정 해야 할까요?
Commented by 이창배 at 2014/08/12 14:12
감사합니다~
Commented by 당근 at 2015/04/09 11:44
좋은 자료 감사합니다^^

:         :

:

비공개 덧글

◀ 이전 페이지다음 페이지 ▶