package com.simplecity.amp_library.ui.adapters; import android.support.annotation.Nullable; import android.support.v7.util.DiffUtil; import android.support.v7.widget.RecyclerView; import android.view.ViewGroup; import com.simplecity.amp_library.model.AdaptableItem; import com.simplecity.amp_library.model.ContentsComparator; import java.util.ArrayList; import java.util.List; import rx.Observable; import rx.Subscription; import rx.android.schedulers.AndroidSchedulers; import rx.schedulers.Schedulers; /** * A custom RecyclerView.Adapter used for adapting {@link AdaptableItem}'s. * <p> * To allow the RecyclerView to perform its animations, use {@link #setItems(List)} */ public abstract class ItemAdapter extends RecyclerView.Adapter { private static final String TAG = "ItemAdapter"; /** * The dataset for this RecyclerView Adapter */ public List<AdaptableItem> items = new ArrayList<>(); /** * This function exposes the viewHolder to allow subclasses to attach custom listeners to * viewHolder items. To do so, subclass ItemAdapter and override this function. * <p> * Note: This should be called prior to setting the data for this adapter, to ensure a listener is attached * to all View Holders. */ protected void attachListeners(final RecyclerView.ViewHolder viewHolder) { } @Override public int getItemViewType(int position) { return items.get(position).getViewType(); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { for (AdaptableItem item : items) { if (viewType == item.getViewType()) { RecyclerView.ViewHolder viewHolder = item.getViewHolder(parent); attachListeners(viewHolder); return viewHolder; } } throw new IllegalStateException("No ViewHolder found for viewType: " + viewType); } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { items.get(position).bindView(holder); } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List payloads) { if (payloads.isEmpty()) { onBindViewHolder(holder, position); } else { items.get(position).bindView(holder, position, payloads); } } @Override public int getItemCount() { return items.size(); } /** * This method is used to transform the current dataset ({@link #items}) into the passed in list of items, performing * logic to remove, add and rearrange items in a way that allows the RecyclerView to animate properly. * * @param items the new dataset ({@link List<AdaptableItem>}) */ public synchronized Subscription setItems(List<AdaptableItem> items) { if (this.items == items) { return null; } return Observable.fromCallable(() -> DiffUtil.calculateDiff(new DiffCallback(this.items, items))) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(diffResult -> { ItemAdapter.this.items = items; diffResult.dispatchUpdatesTo(ItemAdapter.this); }); } /** * Add a single item to the dataset ({@link #items}), notifying the adapter of the insert * * @param position int * @param item the {@link AdaptableItem} to add */ public void addItem(int position, AdaptableItem item) { items.add(position, item); notifyItemInserted(position); } /** * Add a single item to the dataset ({@link #items}), notifying the adapter of the insert * * @param item the {@link AdaptableItem} to add */ public void addItem(AdaptableItem item) { items.add(item); notifyItemInserted(items.size()); } /** * Remove & return the item at items[position] * * @param position int * @return the {@link AdaptableItem} at items[position[ */ public AdaptableItem removeItem(int position) { if (getItemCount() == 0 || position < 0 || position >= items.size()) { return null; } final AdaptableItem model = items.remove(position); notifyItemRemoved(position); return model; } /** * Moves an item from {@param fromPosition} to {@param toPosition} * * @param fromPosition int * @param toPosition int */ public void moveItem(int fromPosition, int toPosition) { final AdaptableItem model = items.remove(fromPosition); items.add(toPosition, model); notifyItemMoved(fromPosition, toPosition); } public void setEmpty(AdaptableItem emptyView) { List<AdaptableItem> items = new ArrayList<>(1); items.add(emptyView); setItems(items); } private static class DiffCallback extends DiffUtil.Callback { private List<AdaptableItem> oldList; private List<AdaptableItem> newList; DiffCallback(List<AdaptableItem> oldList, List<AdaptableItem> newList) { this.oldList = oldList; this.newList = newList; } @Override public int getOldListSize() { return oldList != null ? oldList.size() : 0; } @Override public int getNewListSize() { return newList != null ? newList.size() : 0; } Object getOldItem(int oldItemPosition) { return oldList.get(oldItemPosition); } Object getNewItem(int newItemPosition) { return newList.get(newItemPosition); } @Override public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { Object oldItem = getOldItem(oldItemPosition); Object newItem = getNewItem(newItemPosition); return !(oldItem == null || newItem == null) && oldItem.equals(newItem); } @Override public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { Object oldItem = getOldItem(oldItemPosition); Object newItem = getNewItem(newItemPosition); if (oldItem instanceof ContentsComparator) { return ((ContentsComparator) oldItem).areContentsEqual(newItem); } else { return areItemsTheSame(oldItemPosition, newItemPosition); } } @Nullable @Override public Object getChangePayload(int oldItemPosition, int newItemPosition) { return 0; } } }