import android.database.Cursor; import android.support.annotation.Nullable; import android.support.v7.widget.RecyclerView; import android.view.View; import android.view.ViewGroup; public abstract class RecyclerCursorAdapter extends RecyclerView.Adapter implements OnSwipeListener { //The number of headers to be displayed by default if child classes want a header public static final int HEADER_COUNT = 1; //The number of footers to be displated by default if child classes want a footer public static final int FOOTER_COUNT = 1; //A listener that is triggered when items are added or removed to the Recycler View. protected OnChangedListener onChangedListener; private View mEmptyView; //The Cursor object that will contain all the rows that you want to display inside the Recycler View private Cursor mCursor; //A variable indicating if the data contained in the Cursor above is valid private boolean isValid; //The index of the column containing _id of an SQLite database table from which you want to load data inside the Cursor above private int indexColumnId; public Cursor getCursor() { return mCursor; } /** * @param onChangedListener A class that wishes to get notified when items are added or removed from the Recycler View. When items are added, subclasses must take responsibility of controlling when to fire the 'onAdd' event and when items are swiped to the right in an LTR environment or to the left in an RTL environment, the 'onRemove' event is fired. */ public void setModifiedListener(OnChangedListener onChangedListener) { this.onChangedListener = onChangedListener; } /** * If the data source is set for the first time, create a Cursor object from scratch else swap the existing Cursor object with a new cursor object. Depending on whether the Cursor has any rows at all, update the empty view if set. * * @param cursor containing the rows from your SQLite table that you want to display inside your RecyclerView */ public void setDataSource(Cursor cursor) { if (mCursor == null) { createCursor(cursor); } else { swapCursor(cursor); } toggleEmptyView(); } /** * Check if the data contained by the mCursor is valid and try to extract the value of the column index _id * To indicate that your RecyclerView needs to refresh what it displays, call notifyDataSetChanged * * @param cursor the rows from a database table that you want to display inside your RecyclerView */ private void createCursor(Cursor cursor) { mCursor = cursor; isValid = (cursor != null); indexColumnId = isValid ? this.mCursor.getColumnIndex(BaseColumns._ID) : -1; notifyItemRangeInserted(getPositionForNotifyItemRangeXXX(), getCount()); } /** * Change the underlying mCursor to a new mCursor. If there is an existing mCursor it will be * closed. If the new and old mCursor are same, do nothing, otherwise store the old and new cursors respectively. If the new mCursor is not null, notify that data has changed and mark data as valid. Swap in a new Cursor, returning the old Cursor. Unlike {@link #swapCursor(Cursor)}, the returned old Cursor is not closed. */ public void swapCursor(Cursor newCursor) { if (newCursor == mCursor) { return; } final Cursor oldCursor = mCursor; mCursor = newCursor; if (mCursor != null) { indexColumnId = newCursor.getColumnIndexOrThrow("_id"); isValid = true; notifyDataSetChanged(); } else { indexColumnId = -1; isValid = false; notifyItemRangeRemoved(getPositionForNotifyItemRangeXXX(), getCount()); //There is no notifyDataSetInvalidated() method in RecyclerView.Adapter } if (oldCursor != null) { oldCursor.close(); } } public int getPositionForNotifyItemRangeXXX() { return hasHeader() ? HEADER_COUNT : 0; } public void setEmptyView(View emptyView) { this.mEmptyView = emptyView; } public void toggleEmptyView() { if (mEmptyView != null) mEmptyView.setVisibility(getCount() == 0 ? View.VISIBLE : View.GONE); } public final int getCount() { return isValid ? mCursor.getCount() : 0; } /** * @param position of the current item within the RecyclerView whose item id we need to specify. * @return the index of the column _id from the SQLite database table whose rows you are trying to display inside the RecyclerView, 0 if you dont have valid data in your Cursor */ @Override public long getItemId(int position) { if (isItem(position) && isValid && mCursor.moveToPosition(position)) { return mCursor.getLong(indexColumnId); } return RecyclerView.NO_ID; } @Override public void setHasStableIds(boolean hasStableIds) { super.setHasStableIds(true); } @Override public final int getItemCount() { int itemCount = 0; if (hasHeader()) { itemCount += HEADER_COUNT; } if (hasFooter()) { itemCount += FOOTER_COUNT; } itemCount += getCount(); return itemCount; } @Override public final int getItemViewType(int position) { if (isHeader(position)) { return Type.HEADER.ordinal(); } else if (isFooter(position)) { return Type.FOOTER.ordinal(); } else { return Type.ITEM.ordinal(); } } public boolean isHeader(int position) { if (hasHeader()) { return position == 0 ? true : false; } else { return false; } } public boolean isFooter(int position) { int headerCount = hasHeader() ? HEADER_COUNT : 0; if (hasFooter()) { return position > (getCount() + headerCount) ? true : false; } else { return false; } } public boolean isItem(int position) { if (!isHeader(position) && !isFooter(position)) { return true; } else { return false; } } @Override public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == Type.HEADER.ordinal()) { return onCreate(parent, Type.HEADER.ordinal()); } else if (viewType == Type.FOOTER.ordinal()) { return onCreate(parent, Type.FOOTER.ordinal()); } else { return onCreate(parent, Type.ITEM.ordinal()); } } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if (isItem(position)) { int headerCount = hasHeader() ? HEADER_COUNT : 0; U object = getObjectAt(position - headerCount); onBind((V) holder, object, getItemViewType(position)); } } @Nullable public U getObjectAt(int position) { U object = null; if (isValid && mCursor.moveToPosition(position)) { object = extractFromCursor(mCursor); } return object; } @Override public void onSwipe(int position) { int headerCount = hasHeader() ? HEADER_COUNT : 0; U object = getObjectAt(position - headerCount); if (onChangedListener != null) { onChangedListener.onRemove(object); } } public abstract boolean hasHeader(); public abstract boolean hasFooter(); public abstract U extractFromCursor(Cursor cursor); public abstract V onCreate(ViewGroup parent, int viewType); public abstract void onBind(V holder, U item, int type); public enum Type { HEADER, ITEM, FOOTER; } public interface OnChangedListener { public void onAdd(U item); public void onRemove(U item); } }