ListView UI 반응성을 높이기 위한 Adapter 코드

리스트뷰(ListView)는 안드로이드에서 가장 많이 사용하는 위젯(Widget)중에 하나이다. 리스트뷰는 기본적으로 Adater를 사용해서 UI를 그리기 때문에, 일반적으로 리스트뷰는 개별 Adapter를 사용한다. 아래에서 리스트뷰가 Adater에서 어떻게 UI를 그리고 있는지 살펴보고, 다음으로 리스트뷰를 빠르게 그리기 위한 방안을 살펴보자. 아래는 예제코드에서 사용하는 Adapter의 getView 메서드 코드이다.

1. 기본
1.1 이 코드는 화면에 row(position에 해당)가 보일 때마다 컴포넌트를 만들어서 데이터를 바인딩하는 과정을 거치기 때문에 제일 느린 방법이다.

@Override
	public View getView(int position, View cView, ViewGroup pView) {
		cView = inflater.inflate(R.layout.activity_index_item, null);
		ImageView logoView  = (ImageView) cView.findViewById(R.id.activity_index_logoview);
        TextView nameView = (TextView) cView.findViewById(R.id.activity_index_txtview);

		final IndexModel model = getItem(position);
		logoView.setImageResource(model.id);
		nameView.setText(model.name);

		return cView;
	}

2. ViewHolder 패턴 사용
2.1 ViewHolder 패턴은 화면에 row가 보일 때, 전 화면에서 사용했던 View(cView)를 재사용할 수 있는 경우 사용해서 빠른 UI를 만들어 낸다.
2.2 단점은 체크박스나 ImageView등을 동적(네트웍으로 썸네일을 가져오는 등)으로 처리해야 하는 경우, 깜박이는 효과(?)를 발생시킨다.

	
	@Override
	public View getView(int position, View cView, ViewGroup pView) {
		IndexViewHolder holder;

		if (cView == null) {
			holder = new IndexViewHolder();

			cView = inflater.inflate(R.layout.activity_index_item, null);
			holder.logo = (ImageView) cView.findViewById(R.id.activity_index_logoview);
			holder.name = (TextView) cView.findViewById(R.id.activity_index_txtview);
		} else {
			holder = (IndexViewHolder) cView.getTag();
		}

		cView.setTag(holder);

		final IndexModel model = getItem(position);
		holder.logo.setImageResource(model.id);
		holder.name.setText(model.name);

		return cView;
	}

3. 제시방안
3.1 위의 ViewHolder 패턴과 거의 비슷한 성능과, ViewHolder 패턴이 가지고 있는 단점을 해결한다.
3.2 로직
3.2.1. SparseArray에 position별로 getView에서 리턴하는 View를 유지한다.
3.2.2. getView에서 position에 해당하는 View가 SparseArray에 있으면 View를 리턴한다.
3.2.3. 많은 View를 가지면 메모리 부담이 있기에, WeakReference로 View를 래핑한다.

	
package net.sjava.ex.sparseview;

import java.lang.ref.WeakReference;
import java.util.List;

import android.content.Context;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import static android.content.Context.LAYOUT_INFLATER_SERVICE;

public class IndexAdapter extends ArrayAdapter<IndexModel> {
	private LayoutInflater inflater;
	private SparseArray<WeakReference<View>> viewArray;

	public IndexAdapter(Context ctx, int txtViewId, List<IndexModel> models) {
		super(ctx, txtViewId, models);
		this.viewArray = new SparseArray<WeakReference<View>>(models.size());
		this.inflater = (LayoutInflater) ctx.getSystemService(LAYOUT_INFLATER_SERVICE);
	}

	@Override
	public View getView(int position, View cView, ViewGroup pView) {
		if(viewArray != null && viewArray.get(position) != null) {
			cView = viewArray.get(position).get();
            if(cView != null)
			    return cView;
		}

		try {
			cView = inflater.inflate(R.layout.activity_index_item, null);
			ImageView logoView = (ImageView) cView.findViewById(R.id.activity_index_logoview);
			TextView nameView = (TextView) cView.findViewById(R.id.activity_index_txtview);

			final IndexModel model = getItem(position);
			logoView.setImageResource(model.id);
			nameView.setText(model.name);
		} finally {
			viewArray.put(position, new WeakReference<View>(cView));
		}
		return cView;
	}

	@Override
	public void notifyDataSetChanged() {
		super.notifyDataSetChanged();
	}

	public void update() {
		viewArray.clear();
		notifyDataSetChanged();
	}
}

위 update 메서드는 Activity에서 리스트 아이템의 추가, 삭제, 수정등의 액션이 일어나면, 반영하기 위해서 추가했다. 이 코드는 View를 Holder로 재사용하는 방식이 아니기 때문에, 체크박스나 썸네일 이미지가 깜박이는 현상이 없고, View를 SparseArray가 유지하기에, ViewHolder 패턴만큼의 속도를 보여줄 수 있겠다. 이상으로, 썸네일 캐시처럼, View를 캐시해서 ListView 스크롤의 UI 반응성을 높이는 Adapter 코드이다.

* 예제코드
cfile30.uf.216A7244521290750F6D3C.zip

ListView UI 반응성을 높이기 위한 Adapter 코드”에 대한 5개의 생각

  1. mcsong

    앞에 있는 친구의 도움으로, 아래와 같은 코드로 변경합니다..

    if(viewArray != null && viewArray.get(position) != null) {
    cView = viewArray.get(position).get();
    if(cView != null)
    return cView;
    }

    응답
  2. 컴퓨터에 미친 학생

    OnItemClickListener()속성을 넣어서 테스트 하면
    java.lang.NullPointerException
    가 뜹니다..

    OnItemClickListener()이벤트후에 위 혹은 아레로 스크롤시
    NullPointerException가 뜹니다..;;
    원인은 자세하게 모르겟습니다..;;

    정확한 로그에 찍히는것도 아닌
    터치 이벤트 속성이 일어남으로써 그냥 나오는 에러 같은데 어떻게 해결하나요??;;

    응답
  3. mcsongmcsong 글쓴이

    onCreate() 안에 아래의 코드를 추가해서 테스트 했는데 잘 동작하는 것으로 보입니다.
    lv.setOnItemClickListener(new OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView< ?> arg0, View arg1, int arg2, long arg3) {
    Toast.makeText(IndexActivity.this, arg2 + ” 번째”, Toast.LENGTH_SHORT).show();

    }
    });
    혹시, 코드 보내주시면 확인하는데 도움이 될 것 같습니다. ^^

    응답
    1. mcsongmcsong 글쓴이

      ViewHolder는 View를 재 사용해서 inflate()로 뷰를 생성하는 것보다 훨 빠르긴 한데요.. 동적으로 이미지를 바인딩하는 경우에 깜박이는 효과(?)가 발생할 수 있어서 사용하지 않았습니다.

      응답

답글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다.