ListView를 개량하고 속도 등 성능을 개선했다고 해서 공부겸해서 적용해보았다. 예전에 구현했던 simplelist 대로 모델 클래스 T 를 처리하는 adapter를 매번 구현하지 않도록 generic으로 VIewHolder와 Adaper를 현하고, 사용하는 쪽에서는 ViewHolder만 구현한다.
RecyclerViewHolder<T>
package com.mdiwebma.base.simplelist;
import android.support.v7.widget.RecyclerView;
import android.view.View;
/**
* @author djkim
*/
public abstract class RecyclerViewHolder<T> extends RecyclerView.ViewHolder {
public RecyclerViewHolder(View itemView) {
super(itemView);
}
abstract public void bindView(T data);
}
SimpleListRecyclerAdapter<T>
ArrayAdapter를 참조했고, SimpleListAdapter와 비슷하게 구현하려했으나 생성자에 어쩔 수 없이 R.layout.XXX를 파라미터로 받을 수 밖에 없었다. RecyclerView#ViewHolder는 이미 inflate된 view를 생성자에서 입력받아야 하기에 ViewHolder 내에서 inflate시킬 방법이 없었다.
package com.mdiwebma.base.simplelist;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import android.app.Activity;
import android.support.annotation.LayoutRes;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.mdiwebma.base.R;
import com.mdiwebma.base.logging.Dlog;
import com.mdiwebma.base.utils.ViewUtils;
/**
* @author djkim
*/
public class SimpleListRecyclerAdapter<T> extends RecyclerView.Adapter<RecyclerViewHolder<T>> {
List<T> itemList = new ArrayList<>();
private static final int VIEWTYPE_ITEM = 0;
private static final int VIEWTYPE_MORE = 1;
@NonNull
final SimpleListController simpleListController;
@LayoutRes
final int viewHolderResouceId;
@NonNull
final Class<? extends RecyclerViewHolder<T>> viewHolderClass;
RecyclerViewHolder<T> moreLoadingViewHolder;
boolean notifyOnChange = true;
Object lock = new Object();
public SimpleListRecyclerAdapter(@NonNull SimpleListController simpleListController,
Class<? extends RecyclerViewHolder<T>> viewHolderClass, int viewHolderResouceId) {
this.simpleListController = simpleListController;
this.viewHolderClass = viewHolderClass;
this.viewHolderResouceId = viewHolderResouceId;
}
public void setMoreLoadingViewHolder(RecyclerViewHolder<T> moreLoadingViewHolder) {
this.moreLoadingViewHolder = moreLoadingViewHolder;
}
@Override
public RecyclerViewHolder<T> onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = (LayoutInflater)parent.getContext().getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
if (viewType == VIEWTYPE_ITEM) {
try {
View itemView = inflater.inflate(viewHolderResouceId, null);
return viewHolderClass.getDeclaredConstructor(View.class).newInstance(itemView);
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException
| InvocationTargetException ex) {
Dlog.assertException(ex);
return null;
}
} else {
if (moreLoadingViewHolder == null) {
View itemView = inflater.inflate(R.layout.read_more_view, null);
moreLoadingViewHolder = new DefaultMoreLoadingRecyclerViewHolder<T>(simpleListController, itemView);
}
return moreLoadingViewHolder;
}
}
@Override
public void onBindViewHolder(RecyclerViewHolder<T> holder, int position) {
final int viewType = getItemViewType(position);
if (viewType == VIEWTYPE_ITEM) {
final T item = getItem(position);
holder.bindView(item);
} else { // VIEWTYPE_MORE
holder.bindView(null);
}
}
@Override
public int getItemCount() {
return itemList.size() + (simpleListController.hasMore() ? 1 : 0);
}
@Override
public int getItemViewType(int position) {
if (position < itemList.size()) {
return VIEWTYPE_ITEM;
}
else {
return VIEWTYPE_MORE;
}
}
public void setNotifyOnChange(boolean notifyOnChange) {
this.notifyOnChange = notifyOnChange;
}
public T getItem(int position) {
return itemList.get(position);
}
public int getPosition(T item) {
return itemList.indexOf(item);
}
/**
* Adds the specified object at the end of the array.
*
* @param object The object to add at the end of the array.
*/
public void add(T object) {
synchronized (lock) {
itemList.add(object);
}
if (notifyOnChange)
notifyDataSetChanged();
}
/**
* Adds the specified Collection at the end of the array.
*
* @param collection The Collection to add at the end of the array.
*/
public void addAll(Collection<? extends T> collection) {
synchronized (lock) {
itemList.addAll(collection);
}
if (notifyOnChange)
notifyDataSetChanged();
}
/**
* Adds the specified items at the end of the array.
*
* @param items The items to add at the end of the array.
*/
public void addAll(T... items) {
synchronized (lock) {
Collections.addAll(itemList, items);
}
if (notifyOnChange)
notifyDataSetChanged();
}
/**
* Inserts the specified object at the specified index in the array.
*
* @param object The object to insert into the array.
* @param index The index at which the object must be inserted.
*/
public void insert(T object, int index) {
synchronized (lock) {
itemList.add(index, object);
}
if (notifyOnChange)
notifyDataSetChanged();
}
/**
* Removes the specified object from the array.
*
* @param object The object to remove.
*/
public void remove(T object) {
synchronized (lock) {
itemList.remove(object);
}
if (notifyOnChange)
notifyDataSetChanged();
}
/**
* Remove all elements from the list.
*/
public void clear() {
synchronized (lock) {
itemList.clear();
}
if (notifyOnChange)
notifyDataSetChanged();
}
/**
* Sorts the content of this adapter using the specified comparator.
*
* @param comparator The comparator used to sort the objects contained
* in this adapter.
*/
public void sort(Comparator<? super T> comparator) {
synchronized (lock) {
Collections.sort(itemList, comparator);
}
if (notifyOnChange)
notifyDataSetChanged();
}
///
/// Default MoreLoadingViewHolder
///
class DefaultMoreLoadingRecyclerViewHolder<T> extends RecyclerViewHolder<T> {
@NonNull
final SimpleListController simpleListController;
@NonNull
View loadingView;
@NonNull
View retryView;
public DefaultMoreLoadingRecyclerViewHolder(@NonNull SimpleListController simpleListController1, View baseView) {
super(baseView);
this.simpleListController = simpleListController1;
loadingView = baseView.findViewById(R.id.loading_layout);
retryView = baseView.findViewById(R.id.retry_layout);
baseView.findViewById(R.id.retry).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ViewUtils.setVisible(loadingView);
ViewUtils.setGone(retryView);
simpleListController.requestMoreLoading(true);
}
});
}
@Override
public void bindView(Object item) {
if (simpleListController.getMoreLoadingState() == SimpleListController.LoadingState.NORMAL) {
ViewUtils.setVisible(loadingView);
ViewUtils.setGone(retryView);
simpleListController.requestMoreLoading(false);
} else if (simpleListController.getMoreLoadingState() == SimpleListController.LoadingState.FAILED) {
ViewUtils.setGone(loadingView);
ViewUtils.setVisible(retryView);
}
}
}
}
테스트 코드
TestRecyclerViewHolder
기존과 거의 비슷하다. inflate는 adapter에서 하기에 빠졌고, view초기화는 생성자에서 해야 한다.
public static class TestRecyclerViewHolder extends RecyclerViewHolder<TestData> {
public static final int LAYOUT = R.layout.test_view;
TextView textView;
ImageView imageView;
TestData testData;
public TestRecyclerViewHolder(View baseView) {
super(baseView);
textView = (TextView)baseView.findViewById(R.id.text_view);
imageView = (ImageView)baseView.findViewById(R.id.image);
baseView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ToastUtils.show(String.valueOf(testData.number));
}
});
}
@Override
public void bindView(TestData item) {
testData = item;
textView.setText(String.valueOf(testData.number));
ImageLoader.getInstance().displayImage(getUrl(testData.number), imageView, LearningHabitApp.getImageCacheOptions());
}
}
RecyclerView초기화
귀찮지만 ViewHolder의 inlfate할 리소스 아이디를 넘겨야 한다.
simpleListController.setOnLoadingListener(new SimpleListController.OnLoadingListener() {
@Override
public void onLoading(boolean retry) {
new MyTask(0, retry).setThreadName("requestLoading").execute().showProgressDialog(SplashActivity.this, 1);
}
@Override
public void onMoreLoading(boolean retry) {
new MyTask(lastData, retry).setThreadName("readMore").execute();
}
});
adapter = new SimpleListRecyclerAdapter<>(simpleListController, TestRecyclerViewHolder.class, TestRecyclerViewHolder.LAYOUT);
listView.setAdapter(adapter);
LinearLayoutManager llm = new LinearLayoutManager(this);
//llm.setOrientation(LinearLayoutManager.HORIZONTAL);
listView.setLayoutManager(llm);
simpleListController.requestLoading(false);