Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?

This is an extension of CursorRecyclerViewAdapter that supports Section Header (not supported sticky yet).

There are two files if you need to use this HeaderCursorRecyclerViewAdapter:

CursorRecyclerViewAdapter.java HeaderCursorRecyclerViewAdapter.java

I keep CursorRecyclerViewAdapter.java in this gist to appreciate the original author Jason Yu - skyfishjy.

/*
* Copyright (C) 2014 skyfish.jy@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import android.content.Context;
import android.database.Cursor;
import android.database.DataSetObserver;
import android.support.v7.widget.RecyclerView;
/**
* Created by skyfishjy on 10/31/14.
* <a href="https://gist.github.com/skyfishjy/443b7448f59be978bc59">Gist</a>
*/
public abstract class CursorRecyclerViewAdapter<VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH> {
private Context mContext;
private Cursor mCursor;
private boolean mDataValid;
private int mRowIdColumn;
private DataSetObserver mDataSetObserver;
public CursorRecyclerViewAdapter(Context context, Cursor cursor) {
mContext = context;
mCursor = cursor;
mDataValid = cursor != null;
mRowIdColumn = mDataValid ? mCursor.getColumnIndex("_id") : -1;
mDataSetObserver = newDataSetObserver();
if (mCursor != null) {
mCursor.registerDataSetObserver(mDataSetObserver);
}
}
protected DataSetObserver newDataSetObserver(){
return new NotifyingDataSetObserver();
}
public DataSetObserver getDataSetObserver() {
return mDataSetObserver;
}
public Cursor getCursor() {
return mCursor;
}
public Context getContext() {
return mContext;
}
@Override
public int getItemCount() {
if (mDataValid && mCursor != null) {
return mCursor.getCount();
}
return 0;
}
@Override
public long getItemId(int position) {
if (mDataValid && mCursor != null && mCursor.moveToPosition(position)) {
return mCursor.getLong(mRowIdColumn);
}
return 0;
}
@Override
public void setHasStableIds(boolean hasStableIds) {
super.setHasStableIds(true);
}
public abstract void onBindViewHolder(VH viewHolder, Cursor cursor);
@Override
public void onBindViewHolder(VH viewHolder, int position) {
if (!mDataValid) {
throw new IllegalStateException("this should only be called when the cursor is valid");
}
if (!mCursor.moveToPosition(position)) {
throw new IllegalStateException("couldn't move cursor to position " + position);
}
onBindViewHolder(viewHolder, mCursor);
}
/**
* Change the underlying cursor to a new cursor. If there is an existing cursor it will be
* closed.
*/
public void changeCursor(Cursor cursor) {
Cursor old = swapCursor(cursor);
if (old != null) {
old.close();
}
}
/**
* Swap in a new Cursor, returning the old Cursor. Unlike
* {@link #changeCursor(Cursor)}, the returned old Cursor is <em>not</em>
* closed.
*/
public Cursor swapCursor(Cursor newCursor) {
if (newCursor == mCursor) {
return null;
}
final Cursor oldCursor = mCursor;
if (oldCursor != null && mDataSetObserver != null) {
oldCursor.unregisterDataSetObserver(mDataSetObserver);
}
mCursor = newCursor;
if (mCursor != null) {
if (mDataSetObserver != null) {
mCursor.registerDataSetObserver(mDataSetObserver);
}
mRowIdColumn = newCursor.getColumnIndexOrThrow("_id");
mDataValid = true;
notifyDataSetChanged();
} else {
mRowIdColumn = -1;
mDataValid = false;
notifyDataSetChanged();
//There is no notifyDataSetInvalidated() method in RecyclerView.Adapter
}
return oldCursor;
}
private class NotifyingDataSetObserver extends DataSetObserver {
@Override
public void onChanged() {
super.onChanged();
mDataValid = true;
notifyDataSetChanged();
}
@Override
public void onInvalidated() {
super.onInvalidated();
mDataValid = false;
notifyDataSetChanged();
//There is no notifyDataSetInvalidated() method in RecyclerView.Adapter
}
}
}
/*
* Copyright (C) 2015 tuanchauict@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import android.content.Context;
import android.database.Cursor;
import android.support.v7.widget.RecyclerView;
import android.util.SparseArray;
import android.view.ViewGroup;
import java.util.List;
/**
* Created by tuanchauict on 11/4/15.
*/
public abstract class HeaderCursorRecyclerViewAdapter<VH extends RecyclerView.ViewHolder> extends
CursorRecyclerViewAdapter<VH> {
public static final int TYPE_HEADER = -1;
public static final int TYPE_NORMAL_ITEM = 0;
public static class Section {
public int itemCount;
public String text;
/**
* @param itemCount number item of section. Last section can be anything.
* @param text
*/
public Section(int itemCount, String text) {
this.itemCount = itemCount;
this.text = text;
}
}
private SparseArray<String> mSectionsIndexer;
public HeaderCursorRecyclerViewAdapter(Context context, Cursor cursor, List<Section> sections) {
super(context, cursor);
mSectionsIndexer = new SparseArray<>();
convertSectionList(sections);
}
private void convertSectionList(List<Section> sections) {
mSectionsIndexer.clear();
if (sections != null) {
int count = 0;
for (Section section : sections) {
mSectionsIndexer.put(count, section.text);
count += section.itemCount + 1;
}
}
}
public void changeCursor(Cursor cursor, List<Section> sections) {
super.changeCursor(cursor);
convertSectionList(sections);
}
public void setSections(List<Section> sections) {
convertSectionList(sections);
}
/**
* If you have to custom this function, remember to avoid return the value of TYPE_HEADER (-1) for none header position.
* @param position
* @return
*/
@Override
public int getItemViewType(int position) {
if (mSectionsIndexer.indexOfKey(position) >= 0) {
return TYPE_HEADER;
} else {
return TYPE_NORMAL_ITEM;
}
}
@Override
public void onBindViewHolder(VH viewHolder, int position) {
if (viewHolder.getItemViewType() == TYPE_HEADER) {
onBindSectionHeaderViewHolder(viewHolder, mSectionsIndexer.get(position));
} else {
getCursor().moveToPosition(position - countNumberSectionsBefore(position));
onBindItemViewHolder(viewHolder, getCursor());
}
}
private int countNumberSectionsBefore(int position) {
int count = 0;
for (int i = 0; i < mSectionsIndexer.size(); i++) {
if (position > mSectionsIndexer.keyAt(i))
count++;
}
return count;
}
public abstract void onBindSectionHeaderViewHolder(VH headerHolder, String header);
public abstract void onBindItemViewHolder(VH itemHolder, Cursor cursor);
@Override
public final VH onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == TYPE_HEADER) {
return onCreateSectionHeaderViewHolder(parent);
} else {
return onCreateItemViewHolder(parent, viewType);
}
}
public abstract VH onCreateSectionHeaderViewHolder(ViewGroup parent);
public abstract VH onCreateItemViewHolder(ViewGroup parent, int viewType);
@Override
public final void onBindViewHolder(VH viewHolder, Cursor cursor) {
//do nothing
}
@Override
public int getItemCount() {
int count = super.getItemCount();
if (count > 0) {
return count + mSectionsIndexer.size();
} else {
return 0;
}
}
}
/*
* Copyright (C) 2015 tuanchauict@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
//IMPORT CLASSES HERE
/**
* Created by tuanchauict on 11/4/15.
*/
public class TestAdapter extends HeaderCursorRecyclerViewAdapter<RecyclerView.ViewHolder>{
class HeaderHolder extends RecyclerView.ViewHolder{
TextView mTextView;
public HeaderHolder(View itemView) {
super(itemView);
mTextView = (TextView) itemView.findViewById(R.id.text);
}
public void setHeader(String header){
mTextView.setText(header);
}
}
class ItemHolder extends RecyclerView.ViewHolder{
TextView mTextView;
public ItemHolder(View itemView) {
super(itemView);
mTextView = (TextView) itemView.findViewById(R.id.text);
}
public void setName(String name){
mTextView.setText("Item - " + name);
}
}
public TestAdapter(Context context, Cursor cursor, List<Section> sections) {
super(context, cursor, sections);
}
@Override
public void onBindSectionHeaderViewHolder(RecyclerView.ViewHolder headerHolder, String header) {
((HeaderHolder) headerHolder).setHeader(header);
}
@Override
public void onBindItemViewHolder(RecyclerView.ViewHolder itemHolder, Cursor cursor) {
((ItemHolder) itemHolder).setName("Position = " + cursor.getPosition());
}
@Override
public RecyclerView.ViewHolder onCreateSectionHeaderViewHolder(ViewGroup parent) {
View view = LayoutInflater.from(getContext()).inflate(R.layout.header_text, parent, false);
return new HeaderHolder(view);
}
@Override
public RecyclerView.ViewHolder onCreateItemViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(getContext()).inflate(R.layout.item_layout, parent, false);
return new ItemHolder(view);
}
}
@scottbiggs

This comment has been minimized.

Copy link

commented Jun 4, 2018

Am I missing something? It seems that every time any part of the data has changed, this code calls notifyDataSetChanged(). That is not only inefficient (suppose we're displaying a RecyclerView for a phonebook), but it eliminates the automatic insert-delete-swipe animations that come for free when using RecyclerViews. Try it: if you call notifyItemRemoved(pos), a very elegant animation of the item is removed with the rest of the items animated to close the gap. Using notifyDataSetChanged() does nothing. So if you didn't want to use animations, why not use a ListView instead? It has CursorAdapters built in!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.