-
-
Save SymbolixAU/47b4c950d775d0cc68e8bc6e24e9a536 to your computer and use it in GitHub Desktop.
A single adapter that supports Cursor + an optional header + optional footer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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<U, V extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<RecyclerView.ViewHolder> 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<U> 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<U> 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 <em>not</em> 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<U> { | |
public void onAdd(U item); | |
public void onRemove(U item); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment