태그 보관물: Android ListView

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